¿Por qué Double.NaN == Double.NaN devuelve falso?

155

Estaba estudiando preguntas de OCPJP y encontré este código extraño:

public static void main(String a[]) {
    System.out.println(Double.NaN==Double.NaN);
    System.out.println(Double.NaN!=Double.NaN);
}

Cuando ejecuté el código, obtuve:

false
true

¿Cómo es la salida falsecuando estamos comparando dos cosas que se parecen entre sí? Que NaNsignifica

Disidente
fuente
8
Esto es muy extraño. Como Double.NaN es estático final, la comparación con == debería ser verdadera. +1 para la pregunta.
Stephan
2
Lo mismo es cierto en python:In [1]: NaN==NaN Out[1]: False
tdc
58
Lo mismo es cierto en todos los idiomas que siguen correctamente el estándar IEEE 754.
zzzzBov
44
Intuición: "Hola" no es un número, verdadero (booleano) tampoco es un número. NaN! = NaN por la misma razón "Hola"! = Verdadero
Kevin
3
@Stephan: La comparación con Double.NaN==Double.NaNdebería volver verdadero si Double.NaNfueran de tipo java.lang.Double. Sin embargo, su tipo es el primitivo double, y las reglas del operador para doubleaplicar (que exigen esta desigualdad para cumplir con IEEE 754, como se explica en las respuestas).
sleske

Respuestas:

139

NaN significa "No es un número".

Java Language Specification (JLS) Third Edition dice :

Una operación que se desborda produce un infinito con signo, una operación que se desborda produce un valor desnormalizado o un cero con signo, y una operación que no tiene un resultado matemáticamente definido produce NaN. Todas las operaciones numéricas con NaN como operando producen NaN como resultado. Como ya se ha descrito, NaN no está ordenado, por lo que una operación de comparación numérica que involucra uno o dos retornos de NaN falsey cualquier !=comparación que involucre retornos de NaN true, incluso x!=xcuando xes NaN.

Adrian Mitev
fuente
44
@ nibot: mayormente cierto . Cualquier comparación con un flotador conforme a IEEE producirá false. Entonces, ese estándar difiere de Java en que IEEE lo exige (NAN != NAN) == false.
Drew Dormann el
2
Al abrir esta caja de Pandora, ¿dónde ves que "IEEE exige que (NAN! = NAN) == falso"?
Supervisor
62

NaN no es, por definición, igual a ningún número, incluido NaN. Esto es parte del estándar IEEE 754 e implementado por la CPU / FPU. No es algo a lo que la JVM tenga que agregar ninguna lógica para soportar.

http://en.wikipedia.org/wiki/NaN

Una comparación con un NaN siempre devuelve un resultado desordenado, incluso cuando se compara con sí mismo. ... Los predicados de igualdad y desigualdad no son señalización, por lo que x = x devuelve falso se puede utilizar para probar si x es un NaN silencioso.

Java trata todo NaN como NaN silencioso.

Peter Lawrey
fuente
1
¿Está implementado por la CPU o está cableado en la JVM como menciona Bohemian?
Naweed Chougle
3
La JVM tiene que llamar a lo que sea que lo implemente correctamente. En una PC, la CPU hace todo el trabajo como tal. En una máquina sin este soporte, la JVM tiene que implementarlo. (No conozco ninguna máquina de este tipo)
Peter Lawrey
En el día en que el 8087 era una opción, la biblioteca C contenía un emulador FP. Los programas como la JVM no tendrían que preocuparse de ninguna manera.
Marqués de Lorne
49

¿Por qué esa lógica?

NaNsignifica Not a Number. ¿Qué no es un número? Cualquier cosa. Puede tener cualquier cosa en un lado y cualquier cosa en el otro lado, por lo que nada garantiza que ambos sean iguales. NaNse calcula con Double.longBitsToDouble(0x7ff8000000000000L)y como puede ver en la documentación de longBitsToDouble:

Si el argumento es cualquier valor en el rango 0x7ff0000000000001Lmedio 0x7fffffffffffffffLo en el rango 0xfff0000000000001Lmedio 0xffffffffffffffffL, el resultado es una NaN.

Además, NaNse trata lógicamente dentro de la API.


Documentación

/** 
 * A constant holding a Not-a-Number (NaN) value of type
 * {@code double}. It is equivalent to the value returned by
 * {@code Double.longBitsToDouble(0x7ff8000000000000L)}.
 */
public static final double NaN = 0.0d / 0.0;

Por cierto, NaN se prueba como muestra de código:

/**
 * Returns {@code true} if the specified number is a
 * Not-a-Number (NaN) value, {@code false} otherwise.
 *
 * @param   v   the value to be tested.
 * @return  {@code true} if the value of the argument is NaN;
 *          {@code false} otherwise.
 */
static public boolean isNaN(double v) {
    return (v != v);
}

Solución

Lo que puedes hacer es usar compare/ compareTo:

Double.NaNEste método considera que es igual a sí mismo y mayor que todos los demás doublevalores (incluidos Double.POSITIVE_INFINITY).

Double.compare(Double.NaN, Double.NaN);
Double.NaN.compareTo(Double.NaN);

O equals:

Si thisy argumentambos representan Double.NaN, entonces el equalsmétodo regresa true, aunque Double.NaN==Double.NaNtenga el valor false.

Double.NaN.equals(Double.NaN);
falsarella
fuente
¿Conoces algún caso en el que NaN != NaNser falso haría que los programas sean más complicados que NaN != NaNser cierto? Sé que IEEE tomó la decisión hace mucho tiempo, pero desde una perspectiva práctica, nunca he visto casos en los que sea útil. Si se supone que una operación se ejecuta hasta que las iteraciones consecutivas produzcan el mismo resultado, tener dos iteraciones consecutivas produce NaN se detectaría "naturalmente" como una condición de salida si no fuera por ese comportamiento.
supercat
@supercat ¿Cómo puedes decir que dos números no aleatorios son naturalmente iguales? O decir, primitivamente igual? Piense en NaN como una instancia, no como algo primitivo. Cada resultado anormal diferente es una instancia diferente de algo extraño e incluso si ambos deben representar lo mismo, el uso de == para diferentes instancias debe devolver falso. Por otro lado, cuando se usa igual, se puede manejar adecuadamente como lo desee. [ docs.oracle.com/javase/7/docs/api/java/lang/…
falsarella
@falsarella: El problema no es si dos números aleatorios deben considerarse "definitivamente iguales", sino en qué casos es útil hacer que cualquier número se compare como "definitivamente desigual" a sí mismo. Si uno está tratando de calcular el límite de f(f(f...f(x))), y encuentra uno y=f[n](x)para algunos de los ncuales el resultado de f(y)es indistinguible y, entonces yserá indistinguible del resultado de cualquier anidamiento más profundo f(f(f(...f(y))). Incluso si uno quisiera NaN==NaNser falso, tenerlo Nan!=Nan también sería menos "sorprendente" que x!=xser cierto para algunas x.
supercat
1
@falsarella: Creo que el tipo de Double.NaNno lo es Double, pero doublela pregunta está relacionada con el comportamiento de double. Aunque existen funciones que pueden evaluar una relación de equivalencia que involucra doublevalores, la única respuesta convincente que conozco para "por qué" (que es parte de la pregunta original) es "porque algunas personas en el IEEE no pensaron que la prueba de igualdad debería definir una relación de equivalencia ". Por cierto, ¿hay alguna forma idiomática concisa de probar xy ypara la equivalencia usando solo operadores primitivos? Todas las formulaciones que conozco son bastante torpes.
supercat
1
La mejor y simple respuesta. Gracias
Tarun Nagpal
16

Puede que no sea una respuesta directa a la pregunta. Pero si desea verificar si algo es igual a esto Double.NaN, debe usar esto:

double d = Double.NaN
Double.isNaN(d);

Esto volverá true

Jren
fuente
6

El javadoc para Double.NaN lo dice todo:

Una constante que contiene un valor de tipo Not-a-Number (NaN) double. Es equivalente al valor devuelto por Double.longBitsToDouble(0x7ff8000000000000L).

Curiosamente, la fuente de Doubledefine NaNasí:

public static final double NaN = 0.0d / 0.0;

El comportamiento especial que describe está conectado a la JVM.

Bohemio
fuente
55
¿Está cableado en la JVM o lo implementa la CPU como Peter menciona?
Naweed Chougle
4

según, el estándar IEEE para aritmética de coma flotante para números de doble precisión,

La representación estándar de coma flotante de precisión doble IEEE requiere una palabra de 64 bits, que puede representarse como numerada del 0 al 63, de izquierda a derecha

ingrese la descripción de la imagen aquí dónde,

S: Sign  1 bit
E: Exponent  11 bits
F: Fraction  52 bits 

Si E=2047(todos Eson 1) y Fno es cero, entonces V=NaN("No es un número")

Lo que significa,

Si todos los Ebits son 1, y si hay algún bit distinto de cero, Fentonces el número esNaN .

por lo tanto, entre otros, todos los siguientes números son NaN,

0 11111111 0000000000000000010000000000000000000000000000000000 = NaN
1 11111111 0000010000000000010001000000000000001000000000000000 = NaN
1 11111111 0000010000011000010001000000000000001000000000000000 = NaN

En particular, no puedes probar

if (x == Double.NaN) 

para verificar si un resultado particular es igual Double.NaN, porque todos los valores "no un número" se consideran distintos. Sin embargo, puede usar el Double.isNaNmétodo:

if (Double.isNaN(x)) // check whether x is "not a number"
Sufiyan Ghori
fuente
3

NaN es un valor especial que denota "no un número"; es el resultado de ciertas operaciones aritméticas no válidas, como sqrt(-1), y tiene la propiedad (a veces molesta) que NaN != NaN.

Fred Foo
fuente
2

Ningún número representa el resultado de operaciones cuyo resultado no es representable con un número. La operación más famosa es 0/0, cuyo resultado no se conoce.

Por esta razón, NaN no es igual a nada (incluidos otros valores que no sean un número). Para obtener más información, solo consulte la página de Wikipedia: http://en.wikipedia.org/wiki/NaN

Matteo
fuente
-1: no representa el resultado de 0/0. 0/0siempre es NaN, pero NaN puede ser el resultado de otras operaciones, como 2+NaN: an operation that has no mathematically definite result produces NaNsegún la respuesta de @AdrianMitev
ANeves
De hecho, NaN significa "No es un número", y es el resultado de todas las operaciones que tienen como resultado un valor indefinido o no representable. La operación más famosa y común es 0/0, pero obviamente hay muchas otras operaciones que tienen el mismo resultado. Estoy de acuerdo en que mi respuesta podría mejorarse, pero no estoy de acuerdo con el -1 ... acabo de comprobar que también wikipedia utiliza las operaciones 0/0 como primer ejemplo de operación con un resultado NaN ( en.wikipedia.org/wiki/ NaN ).
Matteo
Además, esto está en la fuente de Java para Double: public static final double NaN = 0.0d / 0.0;
Guillaume
1
@Matteo +0, ahora que la declaración falsa se ha ido. Y mi -1 o +1 no son para que usted esté de acuerdo o en desacuerdo; pero es bueno dejar un comentario con un -1, para que el autor pueda entender por qué su respuesta se considera inútil, y cambiarla, si así lo desea.
ANeves
@Guillaume si ese comentario fue para mí, reformúlelo: no lo entiendo.
ANeves
0

Según este enlace , tiene varias situaciones y es difícil de recordar. Así es como los recuerdo y los distingo. NaNsignifica "matemáticamente indefinido", por ejemplo: "el resultado de 0 dividido por 0 es indefinido" y porque es indefinido, entonces "la comparación relacionada con indefinido es, por supuesto, indefinida". Además, funciona más como premisas matemáticas. Por otro lado, tanto el infinito positivo como el negativo están predefinidos y son definitivos, por ejemplo, "infinito positivo o negativo grande está bien definido matemáticamente".

Tiina
fuente