tipos resueltos que punto programacion numero normalizada mantisa informatica flotante ejercicios ejemplos datos coma c++ floating-point equality

c++ - resueltos - que es coma flotante en informatica



Si el operador<funciona correctamente para los tipos de punto flotante, ¿por qué no podemos usarlo para las pruebas de igualdad? (5)

Probar correctamente la igualdad de dos números de punto flotante es algo que muchas personas, incluyéndome a mí, no entienden completamente. Hoy, sin embargo, pensé en cómo algunos contenedores estándar definen la igualdad en términos de operator< . Siempre veo personas con problemas relacionados con la igualdad, pero nunca con las otras comparaciones relacionales. Incluso hay versiones silenciosas de ellos para usar, que incluyen todo excepto la igualdad y la desigualdad.

Asumiendo que el operator< funciona "correctamente", a diferencia del operator== , ¿por qué no pudimos hacer esto?

bool floateq(float a, float b) { //check NaN return !(a < b) && !(b < a); }

De hecho, realicé una prueba con una sobrecarga adicional para los dobles, como se ve here , y parece tener los mismos inconvenientes que compararlos con el operator== :

std::cout << "float->double vs double: " << floateq(static_cast<double>(0.7f), 0.7) << " " << (static_cast<double>(0.7f) == 0.7) << "/n";

Salida:

flotador-> doble vs doble: 0 0

¿Debo preocuparme por el uso de todos los operadores de comparación o hay algún otro aspecto de la comparación de números de punto flotante que no estoy entendiendo correctamente?


Cuando se usan números de punto flotante, los operadores relacionales tienen significados, pero sus significados no necesariamente se alinean con el comportamiento de los números reales.

Si los valores de punto flotante se utilizan para representar números reales (su propósito normal), los operadores tienden a comportarse de la siguiente manera:

  • x > y y x >= y ambos implican que la cantidad numérica que se supone que x representa es probablemente mayor que y , en el peor de los casos, probablemente no mucho menor que y .

  • x < y y x <= y ambos implican que la cantidad numérica que se supone que x representa es probablemente menor que y , en el peor de los casos, probablemente no sea mucho mayor que y .

  • x == y implica que las cantidades numéricas que x y y representan son indistinguibles unas de otras

Tenga en cuenta que si x es de tipo float , y y es de tipo double , los significados anteriores se lograrán si el double argumento se convierte en float . Sin embargo, en ausencia de un reparto específico, C y C ++ (y también muchos otros idiomas) convertirán un operando float al double antes de realizar una comparación. Dicha conversión reducirá en gran medida la probabilidad de que los operandos se notifiquen como "indistinguibles", pero aumentará en gran medida la probabilidad de que la comparación arroje un resultado contrario a lo que realmente indican los números previstos. Consideremos, por ejemplo,

float f = 16777217; double d = 16777216.5;

Si ambos operandos se convierten en float , la comparación indicará que los valores son indistinguibles. Si se convierten al double , la comparación indicará que d es más grande, aunque el valor que se supone que representa es un poco más grande. Como un ejemplo más extremo:

float f = 1E20f; float f2 = f*f; double d = 1E150; double d2 = d*d;

Float f2 contiene la mejor representación float de 1E40. Doble d2 contiene la mejor representación double de 1E400. La cantidad numérica representada por d2 is hundreds of orders of magnitude greater than that represented by f2 , but (doble) f2> d2 . By contrast, converting both operands to float would yield . By contrast, converting both operands to float would yield f2 == (float) d2`, informando correctamente que los valores son indistinguibles .

PD: soy consciente de que los estándares IEEE requieren que los cálculos se realicen como si los valores de punto flotante representaran fracciones precisas de potencia de dos, pero pocas personas ven el código float f2 = f1 / 10.0; como "Establezca f2 en la fracción de potencia de dos representable que está más cerca de ser una décima parte de la de f1". El propósito del código es hacer que f2 sea una décima parte de f1. Debido a la imprecisión, el código no puede cumplir ese propósito a la perfección, pero en la mayoría de los casos es más útil considerar que los números de punto flotante representan cantidades numéricas reales que considerarlos como fracciones de la potencia de dos.


El siguiente código (que cambié para que floateq : Específicamente, la llamada a floateq se cambió a floatcmp ) imprime float->double vs double: 1 0 , no 0 0 (como uno esperaría al comparar esos dos valores como flotantes).

#include <iostream> bool floatcmp(float a, float b) { //check NaN return !(a < b) && !(b < a); } int main() { std::cout << "float->double vs double: " << floatcmp(static_cast<double>(0.7f), 0.7) << " " << (static_cast<double>(0.7f) == 0.7) << "/n"; }

Sin embargo, lo que importa para la biblioteca estándar es que el operator< define un estricto ordenamiento débil, que de hecho lo hace para los tipos de punto flotante.

El problema con la igualdad es que dos valores pueden verse iguales cuando se redondean para decir 4 o 6 lugares pero, de hecho, son totalmente diferentes y se comparan como no iguales.


En general, todas las operaciones de comparación en números de punto flotante deben realizarse dentro de un límite de precisión específico. De lo contrario, puede ser mordido por un error de redondeo acumulado que no se ve con baja precisión, pero que los operadores de comparación lo tendrán en cuenta. Simplemente a menudo no importa mucho para la clasificación.

Otro ejemplo de código que muestra que su comparación no funciona ( http://ideone.com/mI4S76 ).

#include <iostream> bool floatcmp(float a, float b) { //check NaN return !(a < b) && !(b < a); } int main() { using namespace std; float a = 0.1; float b = 0.1; // Introducing rounding error: b += 1; // Just to be sure change is not inlined cout << "B after increase = " << b << endl; b -= 1; cout << "B after decrease = " << b << endl; cout << "A " << (floatcmp(a, b) ? "equals" : "is not equal to") << "B" << endl; }

Salida:

B after increase = 1.1 B after decrease = 0.1 A is not equal toB


Flotante y doble están en el equivalente binario de la notación científica, con un número fijo de bits significativos. Si el resultado de precisión infinita de un cálculo no es exactamente representable, el resultado real es el más cercano que es exactamente representable.

Hay dos grandes escollos con esto.

  1. Muchas expansiones decimales cortas simples, como 0.1, no se pueden representar exactamente en flotación o doble.
  2. Dos resultados que serían iguales en aritmética de números reales pueden ser diferentes en aritmética de punto flotante. Por ejemplo, la aritmética de punto flotante no es asociativa - (a + b) + c no es necesariamente lo mismo que a + (b + c)

Debe elegir una tolerancia para las comparaciones que sea mayor que el error de redondeo esperado, pero lo suficientemente pequeño como para que sea aceptable en su programa tratar los números que están dentro de la tolerancia como iguales.

Si no existe tal tolerancia, significa que está utilizando el tipo de punto flotante incorrecto, o que no debería usarlo en absoluto. IEEE 754 de 32 bits tiene una precisión tan limitada que puede ser realmente difícil encontrar una tolerancia adecuada. Por lo general, 64 bits es una opción mucho mejor.


Los operadores == , < , > , <= , >= y != Funcionan bien con los números de punto flotante.

Parece que tiene la premisa de que alguna implementación razonable de < debería comparar (doble) 0.7f igual a 0.7. Este no es el caso. Si 0.7f a un double , obtienes 0x1.666666p-1 . Sin embargo, 0.7 es igual a 0x1.6666666666666p-1 . Estos no son numéricamente iguales; de hecho, (double)0.7f es considerablemente más pequeño que 0.7 --- sería ridículo para ellos comparar iguales.

Cuando se trabaja con números de punto flotante, es importante recordar que son números de punto flotante, en lugar de números reales o números racionales o cualquier otra cosa similar. Debes tener en cuenta sus propiedades y no las propiedades que todos quieren que tengan. Haga esto y automáticamente evitará la mayoría de las "trampas" citadas comúnmente de trabajar con números de punto flotante.