¿Cuál es la razón de todas las comparaciones que devuelven falso para los valores de NaN IEEE754?

267

¿Por qué las comparaciones de los valores de NaN se comportan de manera diferente a todos los demás valores? Es decir, todas las comparaciones con los operadores ==, <=,> =, <,> donde uno o ambos valores es NaN devuelve falso, contrario al comportamiento de todos los demás valores.

Supongo que esto simplifica los cálculos numéricos de alguna manera, pero no pude encontrar una razón explícitamente expuesta, ni siquiera en las Lecture Notes on the Status of IEEE 754 de Kahan que discute otras decisiones de diseño en detalle.

Este comportamiento desviado está causando problemas al realizar un procesamiento de datos simple. Por ejemplo, al ordenar una lista de registros con algún campo de valor real en un programa en C, necesito escribir un código adicional para manejar NaN como elemento máximo, de lo contrario, el algoritmo de clasificación podría confundirse.

Editar: Las respuestas hasta ahora argumentan que no tiene sentido comparar NaNs.

Estoy de acuerdo, pero eso no significa que la respuesta correcta sea falsa, sino que sería un Not-a-Boolean (NaB), que afortunadamente no existe.

Por lo tanto, la elección de devolver verdadero o falso para las comparaciones es, en mi opinión, arbitraria, y para el procesamiento general de datos sería ventajoso si obedeciera las leyes habituales (reflexividad de ==, tricotomía de <, ==,>), no sea que las estructuras de datos que confían en estas leyes se confunden.

Entonces, pido una ventaja concreta de romper estas leyes, no solo el razonamiento filosófico.

Edición 2: Creo que ahora entiendo por qué hacer que NaN sea máximo sería una mala idea, dañaría el cálculo de los límites superiores.

NaN! = NaN podría ser deseable para evitar detectar la convergencia en un bucle como

while (x != oldX) {
    oldX = x;
    x = better_approximation(x);
}

que, sin embargo, debería escribirse mejor comparando la diferencia absoluta con un límite pequeño. En mi humilde opinión, este es un argumento relativamente débil para romper la reflexividad en NaN.

starblue
fuente
2
Una vez que un NaN ha ingresado al cálculo, generalmente nunca se irá, por lo que su prueba de convergencia se convertirá en un bucle infinito. Por lo general, es preferible informar la falla para converger con la rutina de llamada, posiblemente devolviendo NaN. Por lo tanto, la estructura del bucle se convertiría en algo así como while (fabs(x - oldX) > threshold)salir del bucle si se produce la convergencia o si un NaN ingresa al cálculo. La detección del NaN y el remedio apropiado se realizarían fuera del circuito.
Stephen Canon
1
Si NaN fuera el elemento mínimo del orden, el ciclo while seguiría funcionando.
Starblue
2
Alimento para el pensamiento: grouper.ieee.org/groups/1788/email/pdfmPSi1DgZZf.pdf página 10
starblue

Respuestas:

535

Fui miembro del comité IEEE-754, intentaré ayudar a aclarar un poco las cosas.

En primer lugar, los números de coma flotante no son números reales, y la aritmética de coma flotante no satisface los axiomas de la aritmética real. La tricotomía no es la única propiedad de la aritmética real que no es válida para las carrozas, ni siquiera la más importante. Por ejemplo:

  • La suma no es asociativa.
  • La ley distributiva no se cumple.
  • Hay números de coma flotante sin inversas.

Podría seguir. No es posible especificar un tipo aritmético de tamaño fijo que satisfaga todas las propiedades de la aritmética real que conocemos y amamos. El comité 754 tiene que decidir doblar o romper algunos de ellos. Esto se guía por algunos principios bastante simples:

  1. Cuando podemos, hacemos coincidir el comportamiento de la aritmética real.
  2. Cuando no podemos, tratamos de hacer que las violaciones sean tan predecibles y fáciles de diagnosticar como sea posible.

Con respecto a su comentario "eso no significa que la respuesta correcta sea falsa", esto está mal. El predicado (y < x)pregunta si yes menor que x. Si yes NaN, entonces es no menos que cualquier valor de punto flotante x, por lo que la respuesta es necesariamente falsa.

Mencioné que la tricotomía no es válida para los valores de coma flotante. Sin embargo, hay una propiedad similar que se mantiene. Cláusula 5.11, párrafo 2 de la norma 754-2008:

Son posibles cuatro relaciones mutuamente excluyentes: menor que, igual, mayor que y desordenado. El último caso surge cuando al menos un operando es NaN. Cada NaN se comparará desordenado con todo, incluido él mismo.

En cuanto a escribir código adicional para manejar NaNs, generalmente es posible (aunque no siempre es fácil) estructurar su código de tal manera que los NaNs no funcionen correctamente, pero este no es siempre el caso. Cuando no lo es, puede ser necesario algún código adicional, pero ese es un pequeño precio a pagar por la conveniencia que el cierre algebraico trajo a la aritmética de punto flotante.


Anexo: Muchos comentaristas han argumentado que sería más útil preservar la reflexividad de la igualdad y la tricotomía sobre la base de que adoptar NaN! = NaN no parece preservar ningún axioma familiar. Confieso tener cierta simpatía por este punto de vista, así que pensé en volver a visitar esta respuesta y proporcionar un poco más de contexto.

Mi comprensión al hablar con Kahan es que NaN! = NaN se originó a partir de dos consideraciones pragmáticas:

  • Eso x == ydebería ser equivalente a x - y == 0siempre que sea ​​posible (más allá de ser un teorema de la aritmética real, esto hace que la implementación de hardware de comparación sea más eficiente en el espacio, lo que era de suma importancia en el momento en que se desarrolló el estándar; tenga en cuenta, sin embargo, que esto se viola para x = y = infinito, por lo que no es una gran razón en sí misma; podría haberse doblado razonablemente (x - y == 0) or (x and y are both NaN)).

  • Más importante aún, no había un 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 de NaN que no dependieran de que los lenguajes de programación proporcionaran algo como lo isnan( )que podría llevar muchos años. Citaré los propios escritos de Kahan sobre el tema:

Si no hubiera forma de deshacerse de los NaN, serían tan inútiles como los Indefinidos en los CRAY; Tan pronto como se encuentre uno, será mejor detener el cálculo en lugar de continuarlo por un tiempo indefinido hasta una conclusión indefinida. Es por eso que algunas operaciones en NaN deben entregar resultados que no sean NaN. Que operaciones ... Las excepciones son los predicados C "x == x" y "x! = X", que son respectivamente 1 y 0 para cada número infinito o finito x pero se invierten si x no es un número (NaN); Estos proporcionan la única distinción simple y excepcional entre NaNs y números en idiomas que carecen de una palabra para NaN y un predicado IsNaN (x).

Tenga en cuenta que esta es también la lógica que descarta devolver algo así como un "No es un booleano". Tal vez este pragmatismo estaba fuera de lugar, y el estándar debería haber requerido isnan( ), pero eso habría hecho que NaN fuera casi imposible de usar de manera eficiente y conveniente durante varios años mientras el mundo esperaba la adopción del lenguaje de programación. No estoy convencido de que hubiera sido una compensación razonable.

Para ser franco: el resultado de NaN == NaN no va a cambiar ahora. Es mejor aprender a vivir con eso que quejarse en Internet. Si desea argumentar que también debería existir una relación de orden adecuada para contenedores , recomendaría recomendar que su lenguaje de programación favorito implemente el totalOrderpredicado estandarizado en IEEE-754 (2008). El hecho de que aún no ha demostrado la validez de la preocupación de Kahan que motivó el estado actual de las cosas.

Stephen Canon
fuente
16
Leí sus puntos 1 y 2. Luego observé que en aritmética real (extendida para permitir NaN en primer lugar) NaN es igual a sí mismo, simplemente porque en matemáticas, cualquier entidad es igual a sí misma, sin excepción. Ahora estoy confundido: ¿por qué IEEE no "coincidió con el comportamiento de la aritmética real", lo que haría que NaN == NaN? ¿Qué me estoy perdiendo?
max
12
Convenido; La no reflexividad de los NaN ha creado un sinfín de dificultades para lenguajes como Python, con su semántica de contención basada en la igualdad. Usted realmente no quiere la igualdad a dejar de ser una relación de equivalencia cuando se está tratando de construir contenedores en la parte superior de la misma. Y tener dos nociones separadas de igualdad tampoco es una opción amigable, para un lenguaje que se supone que es fácil de aprender. El resultado (en el caso de Python) es un compromiso desagradablemente frágil entre el respeto por IEEE 754 y la semántica de contención no demasiado rota. Afortunadamente, es raro poner NaN en contenedores.
Mark Dickinson
55
Algunas buenas observaciones aquí: bertrandmeyer.com/2010/02/06/…
Mark Dickinson
66
@StephenCanon: ¿De qué manera (0/0) == (+ INF) + (-INF) sería más absurdo que tener 1f/3f == 10000001f/30000002f? Si los valores de coma flotante se consideran clases de equivalencia, entonces a=bno significa "Los cálculos que produjeron ay b, si se realizan con precisión infinita, arrojarían resultados idénticos", sino más bien "Lo que se sabe acoincide con lo que se sabe sobre b". Tengo curiosidad si conoces algún ejemplo de código donde tener "Nan! = NaN" hace las cosas más simples de lo que serían de otra manera.
supercat
55
Teóricamente, si tuviera NaN == NaN y no isNaN, aún podría probar NaN con !(x < 0 || x == 0 || x > 0), pero habría sido más lento y más torpe que x != x.
user2357112 es compatible con Monica
50

NaN puede considerarse como un estado / número indefinido. similar al concepto de 0/0 sin definir o sqrt (-3) (en el sistema de números reales donde vive el punto flotante).

NaN se utiliza como una especie de marcador de posición para este estado indefinido. Matemáticamente hablando, indefinido no es igual a indefinido. Tampoco puede decir que un valor indefinido es mayor o menor que otro valor indefinido. Por lo tanto, todas las comparaciones devuelven falso.

Este comportamiento también es ventajoso en los casos en que compara sqrt (-3) con sqrt (-2). Ambos devolverían NaN pero no son equivalentes a pesar de que devuelven el mismo valor. Por lo tanto, tener igualdad siempre devolviendo falso cuando se trata de NaN es el comportamiento deseado.

Chris
fuente
55
¿Cuál debería ser el resultado de sqrt (1.00000000000000022) == sqrt (1.0)? ¿Qué tal (1E308 + 1E308-1E308-1E308-1E308) == (1E308 + 1E308)? Además, solo cinco de las seis comparaciones devuelven falso. El !=operador devuelve verdadero. Tener NaN==NaNy NaN!=NaNambos devolver falso permitiría que el código que compara x e y elija lo que debería suceder cuando ambos operandos son NaN eligiendo ==o !=.
supercat
38

Para agregar otra analogía más. Si te entrego dos cajas y te digo que ninguna de ellas contiene una manzana, ¿me dirías que las cajas contienen la misma cosa?

NaN no contiene información sobre qué es algo, solo qué no es. Por lo tanto, nunca se puede decir que estos elementos sean iguales.

Jack Ryan
fuente
66
Todos los conjuntos vacíos son iguales, por definición.
MSalters
28
NO se sabe que las cajas que se le dan estén vacías.
John Smith
77
¿Me dirías que las cajas no contienen lo mismo? Puedo entender la justificación de (NaN==Nan)==false. Lo que no entiendo es la razón de ser (Nan!=Nan)==true.
supercat
3
Supongo que NaN! = NaN es verdadero porque x! = Y se define como! (X == y). De acuerdo, no sé si la especificación IEEE lo define de esa manera.
Kef Schecter
66
Pero en esta analogía, si me diste una caja, dijiste que no contenía manzanas, luego me preguntaste si era igual a sí misma, ¿esperas que te diga que no? Porque eso es lo que tendría que decir según IEEE.
punto
12

Del artículo de Wikipedia sobre NaN , las siguientes prácticas pueden causar NaNs:

  • Todas las operaciones matemáticas> con un NaN como al menos un operando
  • Las divisiones 0/0, ∞ / ∞, ∞ / -∞, -∞ / ∞ y -∞ / -∞
  • Las multiplicaciones 0 × ∞ y 0 × -∞
  • Las adiciones ∞ + (-∞), (-∞) + ∞ y sustracciones equivalentes.
  • Aplicando una función a argumentos fuera de su dominio, incluyendo tomar la raíz cuadrada de un número negativo, tomar el logaritmo de un número negativo, tomar la tangente de un múltiplo impar de 90 grados (o π / 2 radianes), o tomar el seno inverso o coseno de un número que es menor que -1 o mayor que +1.

Como no hay forma de saber cuál de estas operaciones creó el NaN, no hay forma de compararlas que tenga sentido.

Stefan Rusek
fuente
3
Además, incluso si supiera qué operación, no ayudaría. Puedo construir cualquier número de fórmulas que lleguen a 0/0 en algún momento, que tengan (si suponemos continuidad) valores bien definidos y diferentes en ese punto.
David Thornley el
4

No conozco la lógica del diseño, pero aquí hay un extracto del estándar IEEE 754-1985:

"Será posible comparar números de coma flotante en todos los formatos admitidos, incluso si los formatos de los operandos difieren. Las comparaciones son exactas y nunca se desbordan ni se desbordan. Son posibles cuatro relaciones mutuamente excluyentes: menor que, igual, mayor que y desordenado . El último caso surge cuando al menos un operando es NaN. Cada NaN se comparará desordenado con todo, incluido él mismo ".

Rick Regan
fuente
2

Solo parece peculiar porque la mayoría de los entornos de programación que permiten NaN no permiten también la lógica de 3 valores. Si arroja lógica de 3 valores en la mezcla, se vuelve consistente:

  • (2.7 == 2.7) = verdadero
  • (2.7 == 2.6) = falso
  • (2.7 == NaN) = desconocido
  • (NaN == NaN) = desconocido

Incluso .NET no proporciona un bool? operator==(double v1, double v2)operador, por lo que todavía está atascado con el (NaN == NaN) = falseresultado tonto .

Christian Hayter
fuente
1

Supongo que NaN (no es un número) significa exactamente eso: esto no es un número y, por lo tanto, compararlo no tiene sentido.

Es un poco como la aritmética en SQL con nulloperandos: todos resultan en null.

Las comparaciones para números de coma flotante comparan valores numéricos. Por lo tanto, no se pueden usar para valores no numéricos. NaN, por lo tanto, no se puede comparar en sentido numérico.

Daren Thomas
fuente
3
"Este no es un número y, por lo tanto, compararlo realmente no tiene sentido". Las cadenas no son números, pero compararlas tiene sentido.
jason
2
sí, tiene sentido comparar una cadena con una cadena. Pero comparar una cadena con, por ejemplo, manzanas, no tiene mucho sentido. Dado que las manzanas y las peras no son números, ¿tiene sentido compararlas? ¿Cuál es mayor?
Daren Thomas el
@DarenThomas: en SQL, ni "IF NULL = NULL THEN FOO;" ni "IF Null <> Null THEN CALL FOO;" [o cualquiera que sea la sintaxis] se ejecutará FOO. Para que NaN sea equivalente if (NaN != NaN) foo();no debe ejecutarse foo, pero lo hace.
supercat
1

La respuesta simplificada es que un NaN no tiene un valor numérico, por lo que no hay nada en él para compararlo con nada más.

Puede considerar probar y reemplazar sus NaNs con + INF si desea que actúen como + INF.

David R Tribble
fuente
0

Si bien estoy de acuerdo en que las comparaciones de NaN con cualquier número real no deberían estar ordenadas, creo que solo hay motivos para comparar NaN consigo mismo. ¿Cómo, por ejemplo, uno descubre la diferencia entre la señalización de NaNs y NaNs silenciosos? Si pensamos en las señales como un conjunto de valores booleanos (es decir, un vector de bits), uno podría preguntarse si los vectores de bits son iguales o diferentes y ordenar los conjuntos en consecuencia. Por ejemplo, al decodificar un exponente sesgado máximo, si el significado se dejara desplazado para alinear el bit más significativo del significado con el bit más significativo del formato binario, un valor negativo sería un NaN silencioso y cualquier valor positivo sería ser una señalización NaN. Cero, por supuesto, está reservado para el infinito y la comparación no estaría ordenada. La alineación de MSB permitiría la comparación directa de señales incluso desde diferentes formatos binarios. Por lo tanto, dos NaN con el mismo conjunto de señales serían equivalentes y darían sentido a la igualdad.

Patrick Campbell
fuente
-1

Para mí, la forma más fácil de explicarlo es:

Tengo algo y si no es una manzana, ¿es una naranja?

No puede comparar NaN con otra cosa (incluso ella misma) porque no tiene un valor. También puede ser cualquier valor (excepto un número).

Tengo algo y si no es igual a un número, ¿es una cadena?

Halil Tevfik
fuente
¿Qué quiere decir "puede ser cualquier valor excepto un número"?
Pushkin
-2

Porque las matemáticas son el campo donde los números "simplemente existen". En informática, debe inicializar esos números y mantener su estado de acuerdo con sus necesidades. En aquellos viejos tiempos, la inicialización de la memoria funcionaba de formas en las que nunca se podía confiar. Nunca podría permitirse pensar en este "oh, eso se inicializaría con 0xCD todo el tiempo, mi algo no se romperá" .

Por lo tanto, necesita un solvente adecuado que no se mezcle , que sea lo suficientemente pegajoso como para no dejar que su algoritmo sea absorbido y roto. Los buenos algoritmos que involucran números en su mayoría funcionarán con relaciones, y aquellos que () relaciones serán omitidos.

Esto es solo grasa que puedes poner en una nueva variable en la creación, en lugar de programar un infierno aleatorio desde la memoria de la computadora. Y su algoritmo sea lo que sea, no se romperá.

Luego, cuando de repente descubres que tu algoritmo está produciendo NaN, es posible limpiarlo, examinando cada rama de una en una. Nuevamente, la regla "siempre falsa" está ayudando mucho en esto.

sanaris
fuente
-4

Respuesta muy corta:

Porque lo siguiente: nan / nan = 1 NO debe sostenerse. De inf/inflo contrario sería 1.

(Por nanlo tanto , no puede ser igual a nan. En cuanto a , >o <si nanrespetaría cualquier relación de orden en un conjunto que satisface la propiedad de Arquímedes, tendríamos nuevamente nan / nan = 1en el límite).

SeF
fuente
2
No, eso no tiene sentido. Tenemos inf = infy inf / inf = nan, por nan = nanlo tanto , tampoco lo impediremos nan / nan = nan.
Starblue
@starblue ¿Quieres decir nan / nan = 1? De todos modos ... Su razonamiento tiene sentido si inf y nan eran como cualquier otro número. No es el caso. La razón por la que inf/infdebe ser nan(o forma indeterminada en matemáticas) y no 1es más sutil que la simple manipulación algebraica (ver el teorema de De L'Hospital).
SeF