¿Por qué no podemos usar ‘==’ para comparar dos números flotantes o dobles

Estoy leyendo Effective Java por Joshua Bloch y en el ítem 8: Obedece el contrato general cuando prevalece sobre equals , esta statement está escrita

para los campos flotantes, use el método Float.compare; y para campos dobles, use Double.compare. El tratamiento especial de los campos flotantes y dobles se hace necesario por la existencia de Float.NaN, -0.0f y las constantes dobles análogas;

¿Alguien puede explicarme con un ejemplo por qué no podemos usar == para la comparación flotante o doble?

De apidoc, Float.compare :

Compara los dos valores de flotación especificados. El signo del valor entero devuelto es el mismo que el del entero que la llamada devolverá:

nuevo Float (f1) .compareTo (nuevo Float (f2))

Float.compareTo :

Compara dos objetos Float numéricamente. Hay dos formas en que las comparaciones realizadas por este método difieren de las realizadas por los operadores de comparación numérica del lenguaje Java (<, <=, ==,> =>) cuando se aplican a los valores float primitivos:

  • Por este método, se considera que Float.NaN es igual a sí mismo y mayor que todos los demás valores de flotación (incluido Float.POSITIVE_INFINITY).
  • 0.0f es considerado por este método como mayor que -0.0f.

Esto asegura que el ordenamiento natural de los objetos Float impuestos por este método sea consistente con los iguales.

Considera el siguiente código:

  System.out.println(-0.0f == 0.0f); //true System.out.println(Float.compare(-0.0f, 0.0f) == 0 ? true : false); //false System.out.println(Float.NaN == Float.NaN);//false System.out.println(Float.compare(Float.NaN, Float.NaN) == 0 ? true : false); //true System.out.println(-0.0d == 0.0d); //true System.out.println(Double.compare(-0.0d, 0.0d) == 0 ? true : false);//false System.out.println(Double.NaN == Double.NaN);//false System.out.println(Double.compare(Double.NaN, Double.NaN) == 0 ? true : false);//true 

La salida no es correcta, ya que algo que no es un número, simplemente no es un número, y debe tratarse como igual desde el punto de vista de comparación numérica. También está claro que 0=-0 .

Veamos qué hace Float.compare :

 public static int compare(float f1, float f2) { if (f1 < f2) return -1; // Neither val is NaN, thisVal is smaller if (f1 > f2) return 1; // Neither val is NaN, thisVal is larger int thisBits = Float.floatToIntBits(f1); int anotherBits = Float.floatToIntBits(f2); return (thisBits == anotherBits ? 0 : // Values are equal (thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN) 1)); // (0.0, -0.0) or (NaN, !NaN) } 

Float.floatToIntBits :

Devuelve una representación del valor de punto flotante especificado de acuerdo con el diseño de bits de "formato único" de punto flotante IEEE 754. El bit 31 (el bit que es seleccionado por la máscara 0x80000000) representa el signo del número de coma flotante. Los bits 30-23 (los bits que son seleccionados por la máscara 0x7f800000) representan el exponente. Los bits 22-0 (los bits que se seleccionan con la máscara 0x007fffff) representan el significado (a veces llamado mantisa) del número de coma flotante.

Si el argumento es infinito positivo, el resultado es 0x7f800000.

Si el argumento es infinito negativo, el resultado es 0xff800000.

Si el argumento es NaN, el resultado es 0x7fc00000.

En todos los casos, el resultado es un número entero que, cuando se le da al método intBitsToFloat (int), producirá un valor de punto flotante igual que el argumento a floatToIntBits ( excepto que todos los valores de NaN se contraen a un solo valor de NaN "canónico") )

De JLS 15.20.1. Operadores de comparación numérica <, <=,> y> =

El resultado de una comparación en coma flotante, según lo determinado por la especificación del estándar IEEE 754, es:

  • Si cualquiera de los operandos es NaN, entonces el resultado es falso.

  • Todos los valores distintos de NaN se ordenan, con infinito negativo menor que todos los valores finitos, e infinito positivo mayor que todos los valores finitos.

  • El cero positivo y el cero negativo se consideran iguales. Por ejemplo, -0.0 <0.0 es falso, pero -0.0 <= 0.0 es verdadero.

  • Sin embargo, tenga en cuenta que los métodos Math.min y Math.max tratan el cero negativo como estrictamente más pequeño que el cero positivo.

Para comparaciones estrictas donde los operandos son cero positivo y cero negativo, el resultado será incorrecto.

De JLS 15.21.1. Operadores de Igualdad Numérica == y! = :

El resultado de una comparación en coma flotante, según lo determinado por la especificación del estándar IEEE 754, es:

La prueba de igualdad de punto flotante se realiza de acuerdo con las reglas del estándar IEEE 754:

  • Si cualquiera de los operandos es NaN, entonces el resultado de == es falso pero el resultado de! = Es verdadero. De hecho, la prueba x! = X es verdadera si y solo si el valor de x es NaN. Los métodos Float.isNaN y Double.isNaN también se pueden usar para probar si un valor es NaN.

  • El cero positivo y el cero negativo se consideran iguales. Por ejemplo, -0.0 == 0.0 es verdadero.

  • De lo contrario, los operadores de igualdad consideran dos valores diferentes de punto flotante. En particular, hay un valor que representa el infinito positivo y un valor que representa el infinito negativo; cada uno compara igual a sí mismo, y cada uno compara desigual con todos los otros valores.

Para las comparaciones de igualdad donde ambos operandos son NaN, el resultado será incorrecto.

Debido a que muchos algoritmos importantes usan ordenamiento total ( = , < , > , <= , >= ) (vea todas las clases que implementan la interfaz Comparable ) es mejor usar el método de comparación porque dará un comportamiento más consistente.

La consecuencia del orden total en el contexto del estándar IEEE-754 es la diferencia entre el cero positivo y el negativo.

Por ejemplo, si usa el operador de igualdad en lugar del método de comparación, y tiene una colección de valores y su lógica de código toma algunas decisiones basadas en el orden de los elementos, y de alguna manera comienza a obtener un excedente de valores de NaN, todos lo harán ser tratado como valores diferentes en su lugar como los mismos valores.

Eso puede producir un error en el comportamiento del progtwig proporcional a la cantidad / tasa de valores NaN. Y si tiene muchos ceros positivos y negativos, es solo un par que afecta su lógica con error.

Float utiliza el formato IEEE-754 de 32 bits y el doble usa el formato IEEE-754 de 64 bits.

float (y double ) tienen algunas secuencias de bit especiales que están reservadas para significados especiales que no son “números”:

  • 0xff800000 negativo, representación interna 0xff800000
  • 0x7f800000 positivo, representación interna 0x7f800000
  • No es un número, representación interna 0x7fc00000

Cada uno de estos devuelve 0 (lo que significa que son “lo mismo”) cuando se compara con Float.compare() , pero las siguientes comparaciones con == difieren de Float.NaN :

 Float.NEGATIVE_INFINITY == Float.NEGATIVE_INFINITY // true Float.POSITIVE_INFINITY == Float.POSITIVE_INFINITY // true Float.NaN == Float.NaN // false 

Por lo tanto, al comparar los valores float , para que sean consistentes para todos los valores, incluido el valor especial Float.NaN , Float.compare() es la mejor opción.

Lo mismo aplica para el double .

Hay dos razones para comparar objetos de coma flotante:

  • Estoy haciendo matemáticas, así que quiero comparar sus valores numéricos. Numéricamente, -0 es igual a +0, y un NaN no es igual a nada, ni siquiera a sí mismo, porque “igual” es una propiedad que solo tienen los números, y NaN no es un número.
  • Estoy trabajando con objetos en una computadora, así que necesito distinguir diferentes objetos y colocarlos en orden. Esto es necesario para ordenar objetos en un árbol u otro contenedor, por ejemplo.

El operador == proporciona comparaciones matemáticas. Devuelve falso para NaN == NaN y verdadero para -0.f == +0.f

Las rutinas compare y compare proporcionan comparaciones de objetos. Al comparar un NaN consigo mismo, indican que es el mismo (devolviendo cero). Al comparar -0.f a +0.f , indican que son diferentes (al devolver un valor distinto de cero).