¿Es seguro verificar los valores de coma flotante para igualdad a 0?
-
20-08-2019 - |
Pregunta
Sé que normalmente no puede confiar en la igualdad entre valores de tipo doble o decimal, pero me pregunto si 0 es un caso especial.
Si bien puedo entender las imprecisiones entre 0.00000000000001 y 0.00000000000002, 0 en sí mismo parece bastante difícil de confundir, ya que no es nada. Si no eres preciso en nada, ya no es nada.
Pero no sé mucho sobre este tema, así que no me corresponde decirlo.
double x = 0.0;
return (x == 0.0) ? true : false;
¿Eso siempre será cierto?
Solución
Es seguro esperar que la comparación arroje true
si y solo si la variable doble tiene un valor exactamente 0.0
(que en su fragmento de código original es, por supuesto, el caso). Esto es consistente con la semántica del operador ==
. a == b
significa " a
es igual a b
" ;.
no es seguro (porque es incorrecto ) esperar que el resultado de algún cálculo sea cero en aritmética doble (o más generalmente, de coma flotante) siempre que el resultado del mismo cálculo en Matemática pura sea cero. Esto se debe a que cuando los cálculos llegan al suelo, aparece un error de precisión de coma flotante, un concepto que no existe en la aritmética de números reales en Matemáticas.
Otros consejos
Si necesita hacer mucho " igualdad " comparaciones, podría ser una buena idea escribir una pequeña función auxiliar o método de extensión en .NET 3.5 para comparar:
public static bool AlmostEquals(this double double1, double double2, double precision)
{
return (Math.Abs(double1 - double2) <= precision);
}
Esto podría usarse de la siguiente manera:
double d1 = 10.0 * .1;
bool equals = d1.AlmostEquals(0.0, 0.0000001);
Para su muestra simple, esa prueba está bien. Pero qué pasa con esto:
bool b = ( 10.0 * .1 - 1.0 == 0.0 );
Recuerde que .1 es un decimal repetido en binario y no se puede representar exactamente. Luego compare eso con este código:
double d1 = 10.0 * .1; // make sure the compiler hasn't optimized the .1 issue away
bool b = ( d1 - 1.0 == 0.0 );
Te dejaré que realices una prueba para ver los resultados reales: es más probable que lo recuerdes de esa manera.
De la entrada de MSDN para Double.Equals :
Precisión en las comparaciones
El método Equals debe usarse con precaución, porque aparentemente dos los valores equivalentes pueden ser desiguales debido a la precisión diferente de los dos valores. El siguiente ejemplo informa que el valor doble .3333 y el El doble devuelto dividiendo 1 por 3 son desigual.
...
En lugar de comparar por igualdad, una técnica recomendada implica definiendo un margen aceptable de diferencia entre dos valores (como .01% de uno de los valores). Si el valor absoluto de la diferencia entre los dos valores es menor o igual a ese margen, la diferencia es probable que se deba a diferencias en precisión y, por lo tanto, los valores Es probable que sean iguales. El seguimiento ejemplo utiliza esta técnica para comparar .33333 y 1/3, los dos valores dobles que el ejemplo de código anterior encontró ser desigual.
Además, consulte Double.Epsilon .
El problema surge cuando se comparan diferentes tipos de implementación de valores de coma flotante, p. comparando flotador con doble. Pero con el mismo tipo, no debería ser un problema.
float f = 0.1F;
bool b1 = (f == 0.1); //returns false
bool b2 = (f == 0.1F); //returns true
El problema es que el programador a veces olvida que está ocurriendo la conversión implícita de tipo (doble a flotante) para la comparación y resulta en un error.
Si el número se asignó directamente al flotante o al doble, entonces es seguro realizar una prueba contra cero o cualquier número entero que pueda representarse en 53 bits para un doble o 24 bits para un flotante.
O, para decirlo de otra manera, siempre puede asignar un valor entero a un doble y luego comparar el doble con el mismo entero y garantizar que será igual.
También puede comenzar asignando un número entero y hacer que las comparaciones simples continúen trabajando sumando, restando o multiplicando por números enteros (suponiendo que el resultado sea inferior a 24 bits para un valor flotante y 53 bits para un doble) . Por lo tanto, puede tratar los flotadores y los dobles como enteros bajo ciertas condiciones controladas.
No, no está bien. Los denominados valores desnormalizados (subnormal), cuando se comparan igual a 0.0, se compararían como falsos (distintos de cero), pero cuando se usan en una ecuación se normalizarían (se convertirían en 0.0). Por lo tanto, usar esto como un mecanismo para evitar una división por cero no es seguro. En su lugar, agregue 1.0 y compárelo con 1.0. Esto asegurará que todas las subnormales sean tratadas como cero.
Pruebe esto, y encontrará que == no es confiable para double / float.
double d = 0.1 + 0.2;
bool b = d == 0.3;
Aquí está el respuesta de Quora.
En realidad, creo que es mejor usar los siguientes códigos para comparar un valor doble contra 0.0:
double x = 0.0;
return (Math.Abs(x) < double.Epsilon) ? true : false;
Lo mismo para flotante:
float x = 0.0f;
return (Math.Abs(x) < float.Epsilon) ? true : false;