¿Por qué la comparación de flotantes es inconsistente en Java?

class Test{ public static void main(String[] args){ float f1=3.2f; float f2=6.5f; if(f1==3.2){ System.out.println("same"); }else{ System.out.println("different"); } if(f2==6.5){ System.out.println("same"); }else{ System.out.println("different"); } } } 

salida:

 different same 

¿Por qué es la salida así? Esperaba lo same que el resultado en el primer caso.

La diferencia es que 6.5 puede representarse exactamente tanto en flotante como en doble, mientras que 3.2 no se puede representar exactamente en ningún tipo. y las dos aproximaciones más cercanas son diferentes.

Una comparación de igualdad entre flotante y doble primero convierte el flotador en un doble y luego compara los dos. Entonces la pérdida de datos.


Nunca deberías comparar carrozas o dobles por igualdad; porque realmente no se puede garantizar que el número que asignas al flotador o al doble sea exacto.

Este error de redondeo es una característica de la computación en coma flotante .

Exprimir infinitamente muchos números reales en un número finito de bits requiere una representación aproximada. Aunque hay infinitos enteros, en la mayoría de los progtwigs el resultado de los cálculos enteros se puede almacenar en 32 bits.

Por el contrario, dado un número fijo de bits, la mayoría de los cálculos con números reales producirán cantidades que no se pueden representar exactamente utilizando tantos bits. Por lo tanto, el resultado de un cálculo de punto flotante a menudo debe redondearse para volver a su representación finita. Este error de redondeo es la característica del cálculo en coma flotante.

¡Compruebe lo que todo científico informático debería saber sobre la aritmética de coma flotante para obtener más información!

Ambas son implementaciones de diferentes partes del estándar de coma flotante IEEE . Un float tiene 4 bytes de ancho, mientras que un double tiene 8 bytes de ancho.

Como regla general, probablemente prefiera usar el double en la mayoría de los casos, y solo use el float cuando tenga una buena razón para hacerlo. (Un ejemplo de una buena razón para usar float en lugar de un double es ” que no necesito tanta precisión y necesito almacenar un millón de ellos en la memoria”). También vale la pena mencionar que es difícil probarlo. no necesitas double precisión.

Además, al comparar valores de coma flotante para igualdad, normalmente querrá usar algo como Math.abs(ab) < EPSILON donde a y b son los valores de punto flotante que se comparan y EPSILON es un pequeño valor de coma flotante como 1e-5 . La razón de esto es que los valores de coma flotante rara vez codifican el valor exacto que "deberían", sino que codifican un valor muy cercano, por lo que debe "entrecerrar los ojos" cuando determine si dos valores son iguales.

EDITAR : Todos deberían leer el enlace @Kugathasan Abimaran publicado a continuación: ¡ Lo que todo científico informático debería saber sobre la aritmética de punto flotante para obtener más información!

Para ver con lo que está tratando, puede usar el método toHexString de Float y Double :

 class Test { public static void main(String[] args) { System.out.println("3.2F is: "+Float.toHexString(3.2F)); System.out.println("3.2 is: "+Double.toHexString(3.2)); System.out.println("6.5F is: "+Float.toHexString(6.5F)); System.out.println("6.5 is: "+Double.toHexString(6.5)); } } $ java Test 3.2F is: 0x1.99999ap1 3.2 is: 0x1.999999999999ap1 6.5F is: 0x1.ap2 6.5 is: 0x1.ap2 

En general, un número tiene una representación exacta si es igual a A * 2 ^ B, donde A y B son enteros cuyos valores permitidos están establecidos por la especificación del lenguaje (y el doble tiene más valores permitidos).

En este caso,
6.5 = 13/2 = (1 + 10/16) * 4 = (1 + a / 16) * 2 ^ 2 == 0x1.ap2, mientras
3.2 = 16/5 = (1 + 9/16 + 9/16 ^ 2 + 9/16 ^ 3 +..) * 2 ^ 1 == 0x1.999. . . p1.
Pero Java solo puede contener un número finito de dígitos, por lo que corta el .999. . . apagado en algún punto. (Puede recordar de las matemáticas que 0.999.. = 1. Eso está en la base 10. En la base 16, sería 0.fff.. = 1.)

 class Test { public static void main(String[] args) { float f1=3.2f; float f2=6.5f; if(f1==3.2f) System.out.println("same"); else System.out.println("different"); if(f2==6.5f) System.out.println("same"); else System.out.println("different"); } } 

Prueba así y funcionará. Sin ‘f’ está comparando un flotante con otro tipo flotante y precisión diferente que puede causar un resultado inesperado como en su caso.

No es posible comparar valores de tipo float y double directamente. Antes de poder comparar los valores, es necesario convertir el double en float o convertir el float en double . Si uno hace la comparación anterior, la conversión preguntará “¿El float contiene la mejor representación de float posible del valor del double ?” Si uno hace la última conversión, la pregunta será “¿Tiene el float una representación perfecta del valor del double ?”. En muchos contextos, la primera pregunta es la más significativa, pero Java supone que todas las comparaciones entre float y double pretenden formular la última pregunta.

Yo sugeriría que, independientemente de lo que un idioma esté dispuesto a tolerar, los estándares de encoding deberían prohibir absolutamente las comparaciones directas entre operandos de tipo float y double . Código dado como:

 float f = function1(); double d = function2(); ... if (d==f) ... 

es imposible decir qué comportamiento se pretende en los casos en que d representa un valor que no es precisamente representable en float . Si la intención es que f se convierta en un double , y el resultado de esa conversión se compare con d , uno debe escribir la comparación como

 if (d==(double)f) ... 

Aunque el tipo de transmisión no cambia el comportamiento del código, deja en claro que el comportamiento del código es intencional. Si la intención era que la comparación indicara si f tiene la mejor representación float de d , debería ser:

 if ((float)d==f) 

Tenga en cuenta que el comportamiento de esto es muy diferente de lo que sucedería sin el elenco. Si tu código original emitiera el double operando de cada comparación para float , entonces ambas pruebas de igualdad habrían pasado.

En general, no es una buena práctica utilizar el operador == con número de puntos flotantes, debido a problemas de aproximación.

6.5 puede representarse exactamente en binario, mientras que 3.2 no puede. Es por eso que la diferencia en precisión no importa para 6.5, entonces 6.5 == 6.5f .

Para actualizar rápidamente cómo funcionan los números binarios:

 100 -> 4 10 -> 2 1 -> 1 0.1 -> 0.5 (or 1/2) 0.01 -> 0.25 (or 1/4) 

etc.

6.5 en binario: 110.1 (resultado exacto, el rest de los dígitos son solo ceros)

3.2 en binario: 11.001100110011001100110011001100110011001100110011001101... (¡aquí la precisión importa!)

Un flotador solo tiene una precisión de 24 bits (el rest se usa para signo y exponente), entonces:

3.2f en binario: 11.0011001100110011001100 (no es igual a la aproximación de doble precisión)

Básicamente es lo mismo que cuando escribes 1/5 y 1/7 en números decimales:

 1/5 = 0,2 1,7 = 0,14285714285714285714285714285714. 

El flotador tiene menos precisión que el doble, el flotador bcoz usa 32 bits en los que 1 se usa para Signo, 23 precisión y 8 para Exponente. Donde el doble usa 64 bits en los que se usan 52 para la precisión, 11 para el exponente y 1 para el signo … La precisión es una cuestión importante. Un número decimal representado como flotante y el doble puede ser igual o desigual. de números después del punto decimal puede variar). Saludos S. ZAKIR