¿Deberíamos comparar los números de coma flotante para la igualdad con un error * relativo *?

Hasta ahora, he visto muchas publicaciones relacionadas con la igualdad de números de coma flotante. La respuesta estándar a una pregunta como “¿cómo deberíamos decidir si xey son iguales?” es

abs(x - y) < epsilon 

donde épsilon es una constante pequeña y fija . Esto se debe a que los “operandos” xey son a menudo el resultado de algunos cálculos donde se trata de un error de redondeo, por lo tanto, el operador de igualdad estándar == no es lo que queremos decir, y lo que realmente deberíamos preguntar es si xey están cerca , no es igual.

Ahora, siento que si x es “casi igual” a y, entonces también x * 10 ^ 20 debería ser “casi igual” a y * 10 ^ 20, en el sentido de que el error relativo debería ser el mismo (pero “relativo ” ¿a qué?). Pero con estos números grandes, la prueba anterior fallaría, es decir, esa solución no “escala”.

¿Cómo lidiarías con este problema? ¿Deberíamos reescalar los números o reestructurar épsilon? ¿Cómo? (¿O es incorrecta mi intuición?)

Aquí hay una pregunta relacionada , pero no me gusta su respuesta aceptada, porque la cosa reinterpretar_cast me parece un poco complicada, no entiendo lo que está pasando. Intente proporcionar una prueba simple.

Todo depende del dominio del problema específico. Sí, usar el error relativo será más correcto en el caso general, pero puede ser significativamente menos eficiente ya que involucra una división adicional de coma flotante. Si conoce la escala aproximada de los números en su problema, es aceptable usar un error absoluto.

Esta página describe una serie de técnicas para comparar flotadores. También repasa una serie de cuestiones importantes, como las que tienen subnormales, infinitos y NaN. Es una gran lectura, recomiendo leerlo todo el tiempo.

Como solución alternativa, ¿por qué no solo redondear o truncar los números y luego hacer una comparación directa? Al establecer el número de dígitos significativos por adelantado, puede estar seguro de la precisión dentro de ese límite.

El problema es que con números muy grandes, la comparación con epsilon fallará.

Quizás una solución mejor (pero más lenta) sería usar división, ejemplo:

 div(max(a, b), min(a, b)) < eps + 1 

Ahora el 'error' será relativo.

Usar el error relativo es al menos no tan malo como usar errores absolutos, pero tiene problemas sutiles para valores cercanos a cero debido a problemas de redondeo. Un algoritmo lejos de ser perfecto, pero algo robusto combina enfoques de error absoluto y relativo:

 boolean approxEqual(float a, float b, float absEps, float relEps) { // Absolute error check needed when comparing numbers near zero. float diff = abs(a - b); if (diff < = absEps) { return true; } // Symmetric relative error check without division. return (diff <= relEps * max(abs(a), abs(b))); } 

Adapté este código del excelente artículo de Bruce Dawson, Comparación de números de coma flotantes, edición de 2012 , una lectura obligatoria para cualquier persona que haga comparaciones en coma flotante, un tema increíblemente complejo con muchas trampas.

La mayoría de las veces, cuando el código compara valores, lo hace para responder algún tipo de pregunta. Por ejemplo:

  1. Si sé qué función devuelve cuando se le da un valor de X, ¿puedo suponer que devolverá lo mismo si se le da Y?

  2. Si tengo un método para calcular una función que es lenta pero precisa, estoy dispuesto a aceptar alguna inexactitud a cambio de velocidad, y quiero probar una función candidata que parece ajustarse a la factura, los resultados de esa función son lo suficientemente cercanos al conocido-exacto para ser considerado “correcto”.

Para responder a la primera pregunta, el código idealmente debería hacer una comparación bit a bit del valor, aunque a menos que un lenguaje soporte los nuevos operadores agregados a IEEE-754 en 2009 que pueden ser menos eficientes que ideales. Para responder a la segunda pregunta, uno debe definir qué grado de precisión se requiere y probar en contra de eso.

No creo que haya mucho mérito en un método de propósito general que considere cosas similares que están cerca, ya que las diferentes aplicaciones tendrán diferentes requisitos para la tolerancia absoluta y relativa, basadas en las preguntas exactas que se supone deben responder las pruebas.