¿Cómo comparar correctamente y de forma estándar las carrozas?

Cada vez que comienzo un nuevo proyecto y cuando necesito comparar algunas variables flotantes o dobles escribo el código como este:

if (fabs(prev.min[i] - cur->min[i]) max[i]) < 0.000001) { continue; } 

Entonces quiero deshacerme de estas variables mágicas 0.000001 (y 0.00000000001 para el doble) y fábulas, así que escribo una función en línea y algunas definen:

 #define FLOAT_TOL 0.000001 

Entonces, me pregunto si hay alguna forma estándar de hacer esto. Puede ser un archivo de encabezado estándar? También sería bueno tener flotante y límites dobles (valores mínimo y máximo)

De la guía flotante de puntos :

Esta es una mala forma de hacerlo porque un épsilon fijo elegido porque “parece pequeño” en realidad podría ser demasiado grande cuando los números que se comparan también son muy pequeños. La comparación sería “verdadera” para números que son bastante diferentes. Y cuando los números son muy grandes, el épsilon podría terminar siendo más pequeño que el error de redondeo más pequeño, de modo que la comparación siempre devuelve “falso”.

El problema con el “número mágico” aquí no es que esté codificado, sino que sea “mágico”: en realidad no tienes una razón para elegir 0.000001 sobre 0.000005 o 0.0000000000001, ¿o sí? Tenga en cuenta que float puede representar aproximadamente el último y aún valores más pequeños: ¡son apenas 7 decimales de precisión después del primer dígito distinto de cero!

Si vas a utilizar un épsilon fijo, debes elegirlo de acuerdo con los requisitos del código en particular donde lo usas. La alternativa es usar un margen de error relativo (ver el enlace en la parte superior para más detalles) o, mejor aún, o comparar los flotantes como enteros .

El estándar proporciona un valor épsilon. Está en y puede acceder al valor mediante std::numeric_limits::epsilon y std::numeric_limits::epsilon . Hay otros valores allí, pero no revisé qué es exactamente.

Debe tener en cuenta que si compara dos flotadores para la igualdad, intrínsecamente está haciendo lo incorrecto. Agregar un factor de pendiente a la comparación no es lo suficientemente bueno.

Puede usar std::nextafter para probar dos double con el épsilon más pequeño en un valor (o un factor del épsilon más pequeño).

 bool nearly_equal(double a, double b) { return std::nextafter(a, std::numeric_limits::lowest()) <= b && std::nextafter(a, std::numeric_limits::max()) >= b; } 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; } 

Debe usar la definición estándar en float.h:

 #define DBL_EPSILON 2.2204460492503131e-016 /* smallest float value such that 1.0+DBL_EPSILON != 1.0 */ 

o la clase numeric_limits:

 // excerpt template<> class numeric_limits : public _Num_float_base { public: typedef float T; // return minimum value static T (min)() throw(); // return smallest effective increment from 1.0 static T epsilon() throw(); // return largest rounding error static T round_error() throw(); // return minimum denormalized value static T denorm_min() throw(); }; 

[EDIT: lo hizo un poco más legible.]

Pero además, depende de lo que estás buscando.

Aquí hay una implementación de c ++ 11 de la solución de @geotavros. Hace uso de la nueva función std::numeric_limits::epsilon() y el hecho de que std::fabs() y std::fmax() ahora tienen sobrecargas para float , double y long float .

 template static bool AreEqual(T f1, T f2) { return (std::fabs(f1 - f2) <= std::numeric_limits::epsilon() * std::fmax(fabs(f1), fabs(f2))); } 

Gracias por sus respuestas, me ayudaron mucho. He leído estos materiales: primero y segundo

La respuesta es usar mi propia función para la comparación relativa:

 bool areEqualRel(float a, float b, float epsilon) { return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b))); } 

Esta es la solución más adecuada para mis necesidades. Sin embargo, he escrito algunas pruebas y otros métodos de comparación. Espero que esto sea útil para alguien. areEqualRel pasa estas pruebas, otros no.

 #include  #include  #include  using std::cout; using std::max; bool areEqualAbs(float a, float b, float epsilon) { return (fabs(a - b) <= epsilon); } bool areEqual(float a, float b, float epsilon) { return (fabs(a - b) <= epsilon * std::max(1.0f, std::max(a, b))); } bool areEqualRel(float a, float b, float epsilon) { return (fabs(a - b) <= epsilon * std::max(fabs(a), fabs(b))); } int main(int argc, char *argv[]) { cout << "minimum: " << FLT_MIN << "\n"; cout << "maximum: " << FLT_MAX << "\n"; cout << "epsilon: " << FLT_EPSILON << "\n"; float a = 0.0000001f; float b = 0.0000002f; if (areEqualRel(a, b, FLT_EPSILON)) { cout << "are equal a: " << a << " b: " << b << "\n"; } a = 1000001.f; b = 1000002.f; if (areEqualRel(a, b, FLT_EPSILON)) { cout << "are equal a: " << a << " b: " << b << "\n"; } } 

Esta publicación tiene una explicación exhaustiva de cómo comparar números de coma flotante: http://www.altdevblogaday.com/2012/02/22/comparing-floating-point-numbers-2012-edition/

Extracto:

  • Si está comparando contra cero, las comparaciones basadas en épsilon relativo y ULP generalmente no tienen sentido. Tendrá que usar un épsilon absoluto, cuyo valor podría ser un pequeño múltiplo de FLT_EPSILON y las entradas para su cálculo. Tal vez.
  • Si está comparando con un número distinto de cero, entonces las épsilon relativas o comparaciones basadas en ULP son probablemente lo que usted desea. Es probable que desee un pequeño múltiplo de FLT_EPSILON para su épsilon relativo, o un pequeño número de ULP. Se podría usar un épsilon absoluto si supieras exactamente con qué número estás comparando.