2010-08-06 17 views
16

Estoy escribiendo pruebas unitarias que verifican los cálculos en una base de datos y hay una gran cantidad de redondeos y truncamientos y cosas que significan que a veces las cifras están un poco apagadas.¿Cómo puedo saber si dos variables son aproximadamente iguales?

Al verificar, estoy encontrando un montón de veces cuando las cosas pasarán, pero decir que fallar - por ejemplo, la cifra será 1 y me estoy poniendo 0.999999

quiero decir, que podía a la vuelta de todo en un entero, pero ya que estoy usando un montón de muestras al azar, con el tiempo voy a conseguir algo como esto

10,5 10,4999999999

uno va a redondear a 10, y el otro será redonda a 11

¿Cómo debo resolver este problema donde necesito que algo sea aproximadamente correcto?

+1

Comparando números de coma flotante, Edición 2012: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ - No es una versión de C# ya que obtendrá todo lo que necesita Necesito –

Respuesta

35

Definir un valor de tolerancia (también conocido como un 'Epsilon'), por ejemplo, 0,00001, y luego usar para comparar la diferencia de este modo:

if (Math.Abs(a - b) < epsilon) 
{ 
    // Values are within specified tolerance of each other.... 
} 

[Usted podría utilizar Double.Epsilon pero se tendría que utilizar una factor multiplicador.]

Mejor aún, escriba un método de extensión para hacer lo mismo. Tenemos algo así como Assert.AreSimiliar(a,b) en nuestras pruebas unitarias.

+8

Microsoft proporciona una sobrecarga de la clase Assert.AreEqual que proporciona un delta. 'public static void AreEqual (double expected, double real, double delta);' Parece que ha existido desde al menos Visual Studio 2005 https://msdn.microsoft.com/en-us/library/ms243458(v=vs. 80) .aspx – user2023861

+0

NUnit también proporciona una sobrecarga a su método Assert.AreEqual que permite que se proporcione un delta. – BrettMStory

14

Puede proporcionar una función que incluya un parámetro para una diferencia aceptable entre dos valores. Por ejemplo

// close is good for horseshoes, hand grenades, nuclear weapons, and doubles 
static bool CloseEnoughForMe(double value1, double value2, double acceptableDifference) 
{ 
    return Math.Abs(value1 - value2) <= acceptableDifference; 
} 

Y luego llamarlo

double value1 = 24.5; 
double value2 = 24.4999; 

bool equalValues = CloseEnoughForMe(value1, value2, 0.001); 

Si quisiera ser un poco profesional al respecto, se puede llamar a la función ApproximatelyEquals o algo por el estilo.

static bool ApproximatelyEquals(this double value1, double value2, double acceptableDifference) 
+5

+1 para una respuesta correcta, +1 para sugerir una extensión, -1 por no ser lo suficientemente esnob para usar una letra griega. :-) –

+8

Lo llamaría 'CloseEnoughForGovernmentWork()', personalmente – kyoryu

3

Una forma de comparar los números de coma flotante es comparar cuántas representaciones de coma flotante los separan. Esta solución es indiferente al tamaño de los números y, por lo tanto, no tiene que preocuparse por el tamaño de "épsilon" mencionado en otras respuestas.

Una descripción del algoritmo se puede encontrar here (la función AlmostEqual2sComplement al final) y aquí está mi versión de C#.

ACTUALIZACIÓN: El enlace proporcionado está desactualizado. La nueva versión que incluye algunas mejoras y correcciones de errores es here

public static class DoubleComparerExtensions 
{ 
    public static bool AlmostEquals(this double left, double right, long representationTolerance) 
    { 
     long leftAsBits = left.ToBits2Complement(); 
     long rightAsBits = right.ToBits2Complement(); 
     long floatingPointRepresentationsDiff = Math.Abs(leftAsBits - rightAsBits); 
     return (floatingPointRepresentationsDiff <= representationTolerance); 
    } 

    private static unsafe long ToBits2Complement(this double value) 
    { 
     double* valueAsDoublePtr = &value; 
     long* valueAsLongPtr = (long*)valueAsDoublePtr; 
     long valueAsLong = *valueAsLongPtr; 
     return valueAsLong < 0 
      ? (long)(0x8000000000000000 - (ulong)valueAsLong) 
      : valueAsLong; 
    } 
} 

Si desea comparar los flotadores, cambie todas double a float, todo long a int y 0x8000000000000000 a 0x80000000.

Con el parámetro representationTolerance puede especificar qué tan grande se tolera un error. Un valor más alto significa que se acepta un error más grande. Normalmente uso el valor 10 como predeterminado.

+0

¿Qué tal si comparamos valores negativos cero y muy pequeños? Parece que no funciona. p.ej. compare 0 y -1.1102230246251565E-16 –

+0

El enlace provisto está desactualizado. Nueva ubicación es https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ –

+0

No entiendo por qué restas valores negativos de '0x800 ...' Podría por favor, elabora sobre esto? – m93a

8

No he comprobado en qué versión de prueba de MS se agregaron, pero en la versión 10.0.0.0 Assert. Los métodos de ReqEqual tienen sobrecargas, acepte un parámetro delta y haga una comparación aproximada.

I.e.

// 
// Summary: 
//  Verifies that two specified doubles are equal, or within the specified accuracy 
//  of each other. The assertion fails if they are not within the specified accuracy 
//  of each other. 
// 
// Parameters: 
// expected: 
//  The first double to compare. This is the double the unit test expects. 
// 
// actual: 
//  The second double to compare. This is the double the unit test produced. 
// 
// delta: 
//  The required accuracy. The assertion will fail only if expected is different 
//  from actual by more than delta. 
// 
// Exceptions: 
// Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException: 
//  expected is different from actual by more than delta. 
public static void AreEqual(double expected, double actual, double delta); 
1

La pregunta estaba preguntando cómo hacer valer algo era casi iguales en las pruebas unitarias. Usted afirma que algo es casi igual al usar la función incorporada Assert.AreEqual. Por ejemplo:

Assert.AreEqual(expected: 3.5, actual : 3.4999999, delta:0.1);

Esta prueba pasará. ¡Problema resuelto y sin tener que escribir su propia función!

Cuestiones relacionadas