Comparando valores dobles en C #

Tengo una variable double llamada x . En el código, a x se le asigna un valor de 0.1 y lo verifico en una afirmación ‘if’ que compara x y 0.1

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

Desafortunadamente no ingresa la statement if

  1. ¿Debo usar Double o double ?

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

Es un problema estándar debido a cómo la computadora almacena los 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 precisión. Siempre estará un poco apagado.

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


Querías saber el motivo:

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

12.34 en notación decimal (lo que usamos) significa

  1 * 10 1 + 2 * 10 0 + 3 * 10 -1 + 4 * 10 -2 

La computadora almacena los números de punto flotante de la misma manera, excepto que usa la base 2 : 10.01 significa

  1 * 2 1 + 0 * 2 0 + 0 * 2 -1 + 1 * 2 -2 

Ahora, probablemente sepa que hay algunos números que no se pueden representar completamente 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 notación binaria que es 0.000110011001100…

Como la notación binaria no puede almacenarla con precisión, se almacena de forma redondeada. De ahí tu problema.

double y Double son lo mismo ( double es un alias para Double ) y pueden usarse indistintamente.

El problema con la comparación de un doble con otro valor es que los dobles son valores aproximados, no valores exactos. Entonces, cuando configura x a 0.1 , en realidad puede almacenarse como 0.100000001 o algo así.

En lugar de verificar la igualdad, debe verificar que la diferencia sea menor a una diferencia mínima definida (tolerancia). Algo como:

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

Necesita una combinación de Math.Abs en XY y un value para comparar.

Puede usar el siguiente enfoque de método de extensión

 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; } ... 

Como raramente llamas a los métodos en doble excepto a ToString , creo que es una extensión bastante segura.

Entonces puedes comparar x y y como

if(x.Equals4DigitPrecision(y))

Comparar el número de coma flotante no siempre se puede hacer precisamente debido al redondeo. Comparar

 (x == .1) 

la computadora realmente compara

 (x - .1) vs 0 

El resultado de la simulación no siempre se puede representar de forma precisa debido a cómo se representa el número de punto flotante en la máquina. Por lo tanto, obtiene un valor distinto de cero y la condición se evalúa como false .

Para superar esto compare

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

De la documentación :

Precisión en las comparaciones El método Equals debe usarse con precaución, ya que 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 igualdad, una técnica recomendada implica definir un margen aceptable de diferencia entre dos valores (como el 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.

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

Doble y doble son idénticos.

Por la razón, mira http://www.yoda.arachsys.com/csharp/floatingpoint.html . En resumen: un doble no es un tipo exacto y una diferencia de minutos entre “x” y “0.1” lo arrojará.

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

Intenta una comparación imprecisa:

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

La otra alternativa es usar el tipo de datos decimales.

Usa decimal No tiene este “problema”.

1) ¿Debería 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 )

Consulte también Tabla de tipos incorporados (referencia C #)

El doble (llamado flotación en algunos idiomas) presenta problemas debido a problemas de redondeo, solo es bueno si necesita valores aproximados.

El tipo de datos Decimal hace lo que quiere.

Para decimal de referencia y Decimal son los mismos en .NET C #, al igual que 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, así que úselo con precaución si está mirando loops, etc.

Las representaciones de números en coma flotante son notoriamente inexactas (debido a la forma en que se almacenan los flotadores internamente), por ejemplo, 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.

es decir

 if(x - 0.1 < tol) 

Como regla general:

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

La mayoría de los problemas con los dobles no proviene de la comparación directa, sino que es 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).

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

 x = 0.1 if (x == 0.1) 

no debe fallar, es simple de fallar, si el valor X se calcula mediante medios u operaciones más complejos, es muy posible que 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") 

Un .GetHashCode() truco que encontré es usar el método .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 ya lo notaste, podemos usar algo como esto

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

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

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

Lo intenté en varios casos y parece funcionar bien.

Tomando un consejo de la base de código 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 debería volverse true .

La mayoría de las formas anteriores o siguiendo el método de extensión estúpido!

 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; }