Igualdad y tolerancias del punto flotante

Comparar dos números de coma flotante por algo como a_float == b_float está buscando problemas ya que a_float / 3.0 * 3.0 podría no ser igual a a_float debido a un error de redondeo.

Lo que normalmente se hace es algo así como fabs(a_float - b_float) < tol .

¿Cómo se calcula tol ?

Idealmente, la tolerancia debería ser mayor que el valor de una o dos de las figuras menos significativas. Entonces, si se usa el número de punto flotante de precisión simple, tol = 10E-6 debería ser aproximadamente correcto. Sin embargo, esto no funciona bien para el caso general donde a_float puede ser muy pequeño o muy grande.

¿Cómo se calcula tol para todos los casos generales? Estoy interesado en casos C o C ++ específicamente.

    Este blogpost contiene un ejemplo, una implementación bastante infalible y una teoría detallada detrás de él http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ y también es uno de una serie , por lo que siempre puedes leer más. En resumen: use ULP para la mayoría de los números, use épsilon para números cercanos a cero, pero todavía hay advertencias. Si quiere estar seguro acerca de su matemática de punto flotante, le recomiendo leer toda la serie.

    Hasta donde yo sé, uno no.

    No existe una “respuesta correcta” general, ya que puede depender de los requisitos de precisión de la aplicación.

    Por ejemplo, una simulación de física 2D que trabaje en píxeles de pantalla podría decidir que 1/4 de un píxel es lo suficientemente bueno, mientras que un sistema de CAD 3D utilizado para diseñar las partes internas de la planta nuclear podría no hacerlo.

    No veo una forma de decidir desde el punto de vista programático desde el exterior.

    El archivo de encabezado C le proporciona las constantes FLT_EPSILON y DBL_EPSILON , que es la diferencia entre 1.0 y el número más pequeño que 1.0 que un float / double puede representar. Puede escalar eso por el tamaño de sus números y el error de redondeo que desea tolerar:

     #include  #ifndef DBL_TRUE_MIN /* DBL_TRUE_MIN is a common non-standard extension for the minimum denorm value * DBL_MIN is the minimum non-denorm value -- use that if TRUE_MIN is not defined */ #define DBL_TRUE_MIN DBL_MIN #endif /* return the difference between |x| and the next larger representable double */ double dbl_epsilon(double x) { int exp; if (frexp(x, &exp) == 0.0) return DBL_TRUE_MIN; return ldexp(DBL_EPSILON, exp-1); } 

    Bienvenido al mundo de las trampas, las trampas y las lagunas. Como se mencionó en otra parte, no existe una solución de propósito general para igualdad de puntos flotantes y tolerancias. Dado eso, hay herramientas y axiomas que un progtwigdor puede usar en casos seleccionados.

    fabs(a_float - b_float) < tol tiene el defecto OP mencionado: "no funciona bien para el caso general donde a_float puede ser muy pequeño o muy grande". fabs(a_float - ref_float) < = fabs(ref_float * tol) hace frente a los rangos de variante mucho mejor.

    El "número de coma flotante de precisión simple de OP es tol = 10E-6" es un poco preocupante para C y C ++, por lo que fácilmente promueve la aritmética float para double y luego es la "tolerancia" del double , no del float , lo que entra en juego. Considere la float f = 1.0; printf("%.20f\n", f/7.0); float f = 1.0; printf("%.20f\n", f/7.0); Muchos progtwigdores nuevos no se dan cuenta de que el 7.0 causó un cálculo de double precisión. Recomienda usar double aunque su código, excepto cuando grandes cantidades de datos necesitan el float menor tamaño.

    C99 proporciona nextafter() que puede ser útil para ayudar a medir la "tolerancia". Utilizándolo, uno puede determinar el próximo número representable. Esto ayudará con el OP "... el número completo de dígitos significativos para el tipo de almacenamiento menos uno ... para permitir el error de redondeo". if ((nextafter(x, -INF) < = y && (y <= nextafter(x, +INF))) ...

    El tipo de tol o "tolerancia" utilizada suele ser el quid de la cuestión. Muy a menudo (en mi humilde opinión) una tolerancia relativa es importante. por ejemplo, "¿Son x e y dentro del 0,0001%"? Algunas veces se necesita una tolerancia absoluta . por ejemplo, "¿Están xey dentro de 0.0001"?

    El valor de la tolerancia es a menudo discutible ya que el mejor valor a menudo depende de la situación. Comparar dentro de 0.01 puede funcionar para una aplicación financiera para dólares pero no para yenes. (Sugerencia: asegúrese de usar un estilo de encoding que permita actualizaciones fáciles).

    El error de redondeo varía según los valores utilizados para las operaciones.

    En lugar de una tolerancia fija, probablemente puedas usar un factor de épsilon como:

     bool nearly_equal(double a, double b, int factor /* a factor of epsilon */) { double min_a = a - (a - std::nextafter(a, std::numeric_limits::lowest())) * factor; double max_a = a + (std::nextafter(a, std::numeric_limits::max()) - a) * factor; return min_a < = b && max_a >= b; } 

    Aunque el valor de la tolerancia depende de la situación, si busca una comparación de precisión, puede usar como tolerancia el valor de épsilon de la máquina, numeric_limits :: epsilon () (límites de la biblioteca). La función devuelve la diferencia entre 1 y el valor más pequeño mayor que 1 representable para el tipo de datos. http://msdn.microsoft.com/en-us/library/6x7575x3.aspx

    El valor de épsilon es diferente si compara flotadores o dobles. Por ejemplo, en mi computadora, si se comparan los flotadores, el valor de epsilon es 1.1920929e-007 y si se compara el valor de épsilon es 2.2204460492503131e-016.

    Para una comparación relativa entre xey, multiplique el épsilon por el valor absoluto máximo de xey.

    El resultado anterior podría multiplicarse por los ulps (unidades en el último lugar) que le permiten jugar con la precisión.

     #include  #include  #include  template bool are_almost_equal(T x, T y, int ulp) { if( std::abs(xy) < = std::numeric_limits::epsilon() * std::max(std::abs(x), std::abs(y)) * ulp ){ return true; }else{ return false; } } 

    Cuando necesito comparar carrozas, uso un código como este

     bool same( double a, double b, double error ) { double x; if( a == 0 ) { x = b; } else if( b == 0 ) { x = a; } else { x = (ab) / a; } return fabs(x) < error; }