2009-09-09 18 views
41

Estoy usando C#.Comparando valores dobles en C#

Tengo una variable doble llamada x. En el código, x se le asigna un valor de 0,1 y lo compruebe en una sentencia 'if' comparando xy 0,1

if(x==0.1) 
{ 
---- 
} 

Por desgracia, no entra en la sentencia if

1) ¿Debo usar ¿Doble o doble?

2) ¿Cuál es la razón detrás de esto? ¿Puedes sugerir una solución para esto?

+2

¿Puede agregar su declaración del doble, por favor? –

+0

http://stackoverflow.com/questions/753948/why-is-floating-point-arithmetic-in-c-imprecise –

+0

Una lectura exhaustiva sobre el tema: [Lo que todo científico de la computación debe saber sobre la aritmética de coma flotante] (http://docs.sun.com/source/806-3568/ncg_goldberg.html) –

Respuesta

66

Es un problema estándar debido a la forma en la computadora almacena valores de coma flotante. Busque aquí el "problema de punto flotante" y encontrará toneladas de información.

En resumen: un flotador/doble no puede almacenar 0,1 con precisión. Siempre estará un poco apagado.

Puede intentar usar el tipo decimal que almacena números en notación decimal. Por lo tanto, 0.1 será representable con precisión.


usted quería saber la razón:

flotador/doble se almacenan como fracciones binarias, no en fracciones decimales. Para ilustrar:

12.34 en notación decimal (lo que usamos) significa 1 * 10 + 2 * 10 + 3 * 10 -1 + 4 * 10 -2. La computadora almacena los números de coma flotante de la misma manera, excepto que usa la base 2: 10.01 medios 1 * 2 + 0 * 2 + 0 * 2 -1 + 1 * 2 -2

Ahora, usted probablemente sabe que hay algunos números que no pueden ser representados plenamente con nuestra notación decimal Por ejemplo, 1/3 en notación decimal es 0.3333333 ... Lo mismo sucede en la notación binaria, excepto que los números que no se pueden representar con precisión son diferentes. Entre ellos está el número 1/10. En la notación binaria que es ,000110011001100 ...

Desde la notación binaria no puede almacenar de forma precisa, se almacena de forma redondeada. De ahí tu problema.

+10

Entiendo completamente lo que dice arriba. PERO, por qué, si escribía 'x = 0.01;' para asignar 'x' y luego se comparaba con el literal' 0.01', ¿no sería el mismo valor binario subyacente de 'x' y' 0.01' el mismo (de nuevo, suponiendo que no se usaron cálculos para asignar 'x'). Por lo tanto, '(x == 0.01)' debería funcionar, ¿no es así? Una vez que realice algo de aritmética con 'x', todas las apuestas estarán desactivadas. Además, habría sido más explícito: 'if (x == 0.01d)' para declarar explícitamente que '0.01' es un doble (y no convertir' 0.01' de 'float' a' double') - que puede ha sido el problema – fourpastmidnight

+0

@fourpastmidnight: no puedo reproducir lo que dices (.NET 4). Obtengo 'verdadero' si comparo' 0.01' con '0.01'. –

+1

Sí, si lo hace 'x = 0.01; if (x == 0.01) 'entonces la comparación debería ser verdadera. Bueno, suponiendo que no haya conversiones implícitas de tipo de datos de flotación a doble (aunque tal vez incluso entonces). Pero el OP no especificó cómo obtuvo el primer '0.01'. Es bastante probable que provenga de un cálculo. –

10

La comparación del número de coma flotante no siempre se puede hacer precisamente debido al redondeo. Para comparar

(x == .1) 

el equipo realmente se compara

(x - .1) vs 0 

Resultado de sybtraction no siempre se puede represeted precisamente a causa de la forma en número de coma flotante se representan en la máquina. Por lo tanto, obtiene un valor distinto de cero y la condición se evalúa como false.

Para superar esta comparar

Math.Abs(x- .1) vs some very small threshold (like 1E-9) 
+0

¿Podría ilustrar claramente con un ejemplo? ¿Cuál es la solución para que tenga que cambiar mi declaración? –

3

Uso decimal. No tiene este "problema".

+3

Las operaciones con valores decimales son mucho más lentas. No siempre debemos usar el tipo decimal solo porque "no tiene este problema". Ambos tipos tienen sus propios usos. –

0

las representaciones de números de coma flotante son notoriamente inexactas (debido a la forma en que las flotantes se almacenan internamente), p. x puede ser realmente 0.0999999999 o 0.100000001 y su condición fallará. Si quiere determinar si las carrozas son iguales, necesita especificar si son iguales dentro de cierta tolerancia.

decir

if(x - 0.1 < tol) 
+8

Y arroje Math.Abs, en caso de que x sea un poco menor que 0.1. Tu código aceptará x == -10. –

3

La comparación exacta de valores de coma flotante se sabe que no siempre funciona debido al problema de redondeo y representación interna.

Trate comparación imprecisa:

if (x >= 0.099 && x <= 0.101) 
{ 
} 

La otra alternativa es utilizar el tipo de datos decimal.

26

double y Double son los mismos (double es un alias para Double) y se pueden utilizar indistintamente.

El problema con la comparación de un doble con otro valor es que los dobles son valores aproximados, no valores exactos. Por lo tanto, cuando configure x en 0.1, en realidad se puede almacenar como 0.100000001 o algo así.

En lugar de comprobar la igualdad, debe comprobar que la diferencia es menor que una diferencia mínima definida (tolerancia). Algo así como:

if (Math.Abs(x - 0.1) < 0.0000001) 
{ 
    ... 
} 
+2

Esto debería ser 'if (Math.Abs ​​(x - 0.1) <0.0001) ' – trev

+0

¡Buen punto! Actualizaré mi respuesta –

1

1) ¿Debo usar doble o doble ???

Double y double es lo mismo. double es solo una palabra clave C# que funciona como alias para la clase System.Double ¡Lo más común es usar los alias! Lo mismo para string (System.String), int (System.Int32)

Véase también Built-In Types Table (C# Reference)

0

doble (denominado flotador en algunos idiomas) se fraut con problemas debido a problemas de redondeo, es bueno sólo si necesita valores aproximados.

El tipo de datos Decimal hace lo que quiere.

Para el decimal de referencia y el decimal son los mismos en .NET C#, como los tipos doble y doble, ambos se refieren al mismo tipo (decimal y doble son muy diferentes, como has visto).

Tenga en cuenta que el tipo de datos decimal tiene algunos costos asociados con ella, a fin de utilizarlo con precaución si usted está buscando en bucles etc.

4

Desde el documentation:

precisión en las comparaciones El El método Equals debe usarse con precaución, porque dos valores aparentemente equivalentes pueden ser desiguales debido a la diferente precisión de los dos valores. El siguiente ejemplo informa que el valor Doble .3333 y el Doble devuelto al dividir 1 por 3 son desiguales.

...

En lugar de comparar por la igualdad, una técnica recomendada implica la definición de un margen aceptable de diferencia entre dos valores (por ejemplo, 0,01% de uno de los valores). Si el valor absoluto de la diferencia entre los dos valores es menor o igual a ese margen, es probable que la diferencia se deba a diferencias en la precisión y, por lo tanto, es probable que los valores sean iguales. El siguiente ejemplo usa esta técnica para comparar .33333 y 1/3, los dos valores dobles que el ejemplo de código anterior encontró que eran desiguales.

Si realmente necesita una doble, debe utilizar la técnica descrita en la documentación. Si puede, cámbielo a un decimal. Será más lento, pero no tendrá este tipo de problema.

0

Como regla general:

doble representación es suficiente en la mayoría de los casos, pero puede fallar estrepitosamente en algunas situaciones. Use valores decimales si necesita precisión completa (como en aplicaciones financieras).

La mayoría de los problemas con dobles no proviene de la comparación directa, solía ser el resultado de la acumulación de varias operaciones matemáticas que alteran exponencialmente el valor debido al redondeo y los errores fraccionarios (especialmente con multiplicaciones y divisiones).

Compruebe su lógica, si el código es:

x = 0.1 

if (x == 0.1) 

y no será traspasado, es de sencilla a fallar, si el valor de X se calcula por medios más complejos u operaciones que es muy posible el método ToString utilizado por el depurador está utilizando un redondeo inteligente, tal vez usted puede hacer lo mismo (si eso es demasiado arriesgado volver a usar decimal):

if (x.ToString() == "0.1") 
9

se necesita una combinación de Math.Abs en X-Y y una value para comparar con.

Usted puede utilizar después de Extensión enfoque método

public static class DoubleExtensions 
    { 
     const double _3 = 0.001; 
     const double _4 = 0.0001; 
     const double _5 = 0.00001; 
     const double _6 = 0.000001; 
     const double _7 = 0.0000001; 

     public static bool Equals3DigitPrecision(this double left, double right) 
     { 
      return Math.Abs(left - right) < _3; 
     } 

     public static bool Equals4DigitPrecision(this double left, double right) 
     { 
      return Math.Abs(left - right) < _4; 
     } 

     ... 

Dado que rara vez se llama a métodos de matrimonio, excepto en ToString Creo que su extensión bastante seguro.

A continuación, puede comparar x y y como

if(x.Equals4DigitPrecision(y))

+0

¡Podría ser una buena extensión para Double struct! –

-7

formas siguientes o método de extensión estúpida más arriba!

public static bool EqualsTo(this double value, double value2) 
{ 
    var bytes1 = BitConverter.GetBytes(value); 
    var bytes2 = BitConverter.GetBytes(value2); 

    var long1 = BitConverter.ToInt64(bytes1, 0); 
    var long2 = BitConverter.ToInt64(bytes2, 0); 

    return long1 == long2; 
} 
+0

Esto comprueba si dos dobles tienen la misma representación binaria, lo que no responde a la pregunta en absoluto. –

+0

¡Todos los datos en el mundo de la computadora se guardan en forma binaria! –

+1

Su código no intenta responder la pregunta "¿por qué estos valores no se comparan igual a pesar de que parecen serlo, y cómo lo resuelvo?" A pesar del título, la pregunta no es "dame todas las formas posibles de comparar dobles". –

0

un corte fresco que he encontrado es utilizar el método de .GetHashCode() que devuelve un int que representa el doble, es decir.

(0.4d + 0.3d + 0.2d + 0.1d).GetHashCode() //returns -1072693248

1d.GetHashCode() //returns 1072693248

así como se ha señalado por ahora podemos usar algo como este uso

public static bool AccurateEquality(double first,double second) 
{ 
    return Math.Abs(first.GetHashCode()) == Math.Abs(second.GetHashCode()); 
} 

: AccurateEquality((0.4d + 0.3d + 0.2d + 0.1d),1) //returns true

mientras: (0.4d + 0.3d + 0.2d + 0.1d) == 1d //returns false

Lo probé en varios casos y parece funcionar bien.

0

Tomando una sugerencia del código base de Java, intente usar .CompareTo y pruebe la comparación cero. Esto supone que la función .CompareTo toma en cuenta la igualdad de punto flotante de forma precisa. Por ejemplo,

System.Math.PI.CompareTo(System.Math.PI) == 0 

Este predicado debe devolver true.