¿Por qué NaN no es igual a NaN? [duplicar]
El estándar IEEE relevante define una constante numérica NaN (no un número) y prescribe que NaN debe compararse como no igual a sí mismo. ¿Porqué es eso?
Todos los idiomas con los que estoy familiarizado implementan esta regla. Pero a menudo causa problemas importantes, por ejemplo, un comportamiento inesperado cuando NaN se almacena en un contenedor, cuando NaN está en los datos que se están clasificando, etc. Sin mencionar que la gran mayoría de los programadores esperan que cualquier objeto sea igual a sí mismo ( antes de que aprendan sobre NaN), por lo que sorprenderlos aumenta los errores y la confusión.
Los estándares IEEE están bien pensados, por lo que estoy seguro de que hay una buena razón por la cual comparar NaN como igual a sí mismo sería malo. Simplemente no puedo entender qué es.
Editar: consulte ¿Cuál es el motivo por el que todas las comparaciones arrojan resultados falsos para los valores NaN IEEE754? como la respuesta autorizada.
La respuesta aceptada es 100% sin lugar a dudas MAL . Ni medio equivocado ni siquiera un poco equivocado. Me temo que este problema confundirá y engañará a los programadores durante mucho tiempo cuando esta pregunta aparezca en las búsquedas.
NaN está diseñado para propagarse a través de todos los cálculos, infectándolos como un virus, por lo que si en algún lugar de sus cálculos profundos y complejos encuentra un NaN, no genera una respuesta aparentemente sensata. De lo contrario, por identidad, NaN/NaN debería ser igual a 1, junto con todas las demás consecuencias como (NaN/NaN)==1, (NaN*1)==NaN, etc. Si imagina que sus cálculos salieron mal en alguna parte (el redondeo produjo un denominador cero, dando NaN), etc., entonces podría obtener resultados tremendamente incorrectos (o peor: sutilmente incorrectos) de sus cálculos sin ningún indicador obvio de por qué.
También hay muy buenas razones para utilizar NaN en los cálculos al probar el valor de una función matemática; Uno de los ejemplos dados en el documento vinculado es encontrar los ceros() de una función f(). Es muy posible que en el proceso de probar la función con valores estimados, pruebe uno en el que la función f() no produzca ningún resultado sensible. Esto permite que zeros() vea el NaN y continúe su trabajo.
La alternativa a NaN es activar una excepción tan pronto como se encuentre una operación ilegal (también llamada señal o trampa). Además de las enormes penalizaciones de rendimiento que podría encontrar, en ese momento no había garantía de que las CPU lo soportarían en hardware o que el sistema operativo/lenguaje lo soportaría en software; cada uno era su propio copo de nieve único en el manejo del punto flotante. IEEE decidió manejarlo explícitamente en software como valores NaN para que fuera portátil en cualquier sistema operativo o lenguaje de programación. Los algoritmos de punto flotante correctos generalmente son correctos en todas las implementaciones de punto flotante , ya sea node.js o COBOL (ja).
En teoría, no es necesario establecer directivas #pragma específicas, establecer indicadores de compilación locos, detectar las excepciones correctas o instalar controladores de señales especiales para que lo que parece ser el algoritmo idéntico realmente funcione correctamente. Desafortunadamente, algunos diseñadores de lenguajes y escritores de compiladores han estado muy ocupados deshaciendo esta característica lo mejor que pudieron.
Lea parte de la información sobre la historia del punto flotante IEEE 754. También esta respuesta a una pregunta similar en la que un miembro del comité respondió: ¿Cuál es el motivo por el que todas las comparaciones arrojan resultados falsos para los valores NaN IEEE754?
"Una entrevista con el viejo del punto flotante"
"Historia del formato de punto flotante IEEE"
Lo que todo informático debería saber sobre la aritmética de coma flotante
Bueno, log(-1)
da NaN
y acos(2)
también da NaN
. ¿Eso significa eso log(-1) == acos(2)
? Claramente no. De ahí que tenga perfecto sentido que NaN
no sea igual a sí mismo.
Revisando esto casi dos años después, aquí hay una función de comparación "segura para NaN":
function compare(a,b) {
return a == b || (isNaN(a) && isNaN(b));
}
Mi respuesta original (de hace 4 años) critica la decisión desde la perspectiva moderna sin comprender el contexto en el que se tomó la decisión. Como tal, no responde a la pregunta.
La respuesta correcta se da aquí :
NaN
!=NaN
se originó a partir de dos consideraciones pragmáticas:[...] No había ningún
isnan( )
predicado en el momento en que NaN se formalizó en la aritmética 8087; era necesario proporcionar a los programadores un medio conveniente y eficiente para detectar valores NaN que no dependieran de lenguajes de programación que proporcionaran algo asíisnan( )
que podría llevar muchos años.
Ese enfoque tenía una desventaja: hacía que NaN fuera menos útil en muchas situaciones no relacionadas con el cálculo numérico. Por ejemplo, mucho más tarde, cuando la gente quiso usarlo NaN
para representar los valores faltantes y ponerlos en contenedores basados en hash, no pudieron hacerlo.
Si el comité previó casos de uso futuros y los consideró lo suficientemente importantes, podrían haber optado por algo más detallado !(x<x & x>x)
en lugar de x!=x
una prueba NaN
. Sin embargo, su enfoque era más pragmático y limitado: proporcionar la mejor solución para un cálculo numérico y, como tal, no vieron ningún problema en su enfoque.
===
Respuesta original:
Lo siento, por mucho que aprecio la idea que figura en la respuesta más votada, no estoy de acuerdo con ella. NaN no significa "indefinido"; consulte http://www.cs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF , página 7 (busque la palabra "indefinido"). Como confirma ese documento, NaN es un concepto bien definido.
Además, el enfoque de IEEE era seguir las reglas matemáticas habituales tanto como fuera posible y, cuando no podían, seguir la regla de "menor sorpresa" (consulte https://stackoverflow.com/a/1573715/336527 ). Cualquier objeto matemático es igual a sí mismo, por lo que las reglas de las matemáticas implicarían que NaN == NaN debería ser Verdadero. No veo ninguna razón válida y poderosa para desviarme de un principio matemático tan importante (sin mencionar las reglas menos importantes de tricotomía de comparación, etc.).
Como resultado, mi conclusión es la siguiente.
Los miembros del comité del IEEE no pensaron esto con mucha claridad y cometieron un error. Dado que muy pocas personas entendieron el enfoque del comité IEEE, o se preocuparon por lo que dice exactamente el estándar sobre NaN (a saber: el tratamiento que la mayoría de los compiladores dan a NaN viola el estándar IEEE de todos modos), nadie dio la alarma. Por lo tanto, este error ahora está incluido en la norma. Es poco probable que se solucione, ya que dicha solución rompería una gran cantidad de código existente.
Editar: aquí hay una publicación de una discusión muy informativa. Nota: para obtener una visión imparcial, debes leer el hilo completo, ya que Guido tiene una visión diferente a la de otros desarrolladores principales. Sin embargo, Guido no está personalmente interesado en este tema y sigue en gran medida la recomendación de Tim Peters. Si alguien tiene los argumentos de Tim Peters a favor de NaN != NaN
, agréguelos en los comentarios; Tienen muchas posibilidades de cambiar mi opinión.