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
yx >= y
ambos implican que la cantidad numérica que se supone quex
representa es probablemente mayor quey
, en el peor de los casos, probablemente no mucho menor quey
.x < y
yx <= y
ambos implican que la cantidad numérica que se supone quex
representa es probablemente menor quey
, en el peor de los casos, probablemente no sea mucho mayor quey
.x == y
implica que las cantidades numéricas quex
yy
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.
- Muchas expansiones decimales cortas simples, como 0.1, no se pueden representar exactamente en flotación o doble.
- 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 quea + (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.