java - error - ¿Por qué los infinitos de coma flotante, a diferencia de los NaN, son iguales?
nan java error solucion (8)
¿Por qué son iguales los infinitos? Porque funciona
La aritmética de punto flotante está diseñada para producir cálculos (relativamente) rápidos que preserven los errores. La idea es que no verifique si hay desbordamientos u otras tonterías durante un cálculo largo; esperas hasta que termine. Es por eso que los NaN se propagan de la manera en que lo hacen: una vez que hayas obtenido un NaN, hay muy pocas cosas que puedas hacer para que desaparezca. Una vez que finaliza el cálculo, puede buscar NaN para verificar si algo salió mal.
Lo mismo para los infinitos: si existe la posibilidad de desbordamiento, no haga cosas que desechen los infinitos.
Si desea ir lento y seguro, IEEE-754 tiene mecanismos para instalar controladores de trampa para proporcionar devoluciones de llamada en su código cuando el resultado de un cálculo sería un NaN o un infinito. Principalmente eso no se usa; Por lo general, es demasiado lento e inútil una vez que el código se ha depurado correctamente (no es eso fácil: las personas obtienen doctorados sobre cómo hacer esto bien).
¿Por qué la comparación infinita no sigue la lógica aplicada a los NaN?
Este código imprime
false
tres veces:
double a = Double.NaN;
double b = Double.NaN;
System.out.println(a == b); // false
System.out.println(a < b); // false
System.out.println(a > b); // false
Sin embargo, si cambio
Double.NaN
a
Double.POSITIVE_INFINITY
, obtengo
true
para la igualdad, pero
false
para las comparaciones mayor que y menor que:
double a = Double.POSITIVE_INFINITY;
double b = Double.POSITIVE_INFINITY;
System.out.println(a == b); // true
System.out.println(a < b); // false
System.out.println(a > b); // false
Esto parece peligroso. Suponiendo que los valores infinitos resultan de desbordamientos, imagino que es más probable que dos variables que terminaron como infinitos no sean iguales en aritmética perfecta.
Dado que se mencionó Double.Nan.equals (Double.NaN): una cosa es lo que debería suceder cuando realiza operaciones aritméticas y compara números, es algo totalmente diferente cuando considera cómo deben comportarse los objetos.
Dos casos típicos de problemas son: ordenar una matriz de números y usar valores hash para implementar diccionarios, conjuntos, etc. Hay dos casos excepcionales en los que no se aplica el orden normal con <, = y>: un caso es que +0 = -0 y el otro es que NaN ≠ NaN, y x <NaN, x> NaN, x = NaN siempre será falso lo que sea x.
Los algoritmos de clasificación pueden meterse en problemas con esto. Un algoritmo de clasificación puede suponer que x = x siempre es cierto. Entonces, si sé que x está almacenado en una matriz y lo busco, es posible que no haga ninguna verificación de límites porque la búsqueda debe encontrar algo. No si x es NaN. Un algoritmo de clasificación puede suponer que exactamente uno de a <by a> = b debe ser verdadero. No si uno es NaN. Entonces, un ingenuo algoritmo de clasificación puede fallar cuando hay NaNs presentes. Tendría que decidir dónde desea que terminen los NaN al ordenar la matriz, y luego cambiar su código de comparación para que funcione.
Ahora diccionarios y conjuntos y en general hashing: ¿Qué pasa si uso un NaN como clave? Un conjunto contiene objetos únicos. Si el conjunto contiene un NaN e intento agregar otro, ¿es único porque no es igual al que ya está allí? ¿Qué pasa con +0 y -0, deben considerarse iguales o diferentes? Existe la regla de que dos elementos considerados iguales deben tener el mismo valor hash. Entonces, lo más sensato es (probablemente) que una función hash devuelve un valor único para todos los NaN y un valor único para +0 y -0. Y después de la búsqueda de hash cuando necesita encontrar un elemento con el mismo valor de hash que es realmente igual, dos NaN deben considerarse iguales (pero diferentes de cualquier otra cosa).
Probablemente por eso Double.Nan.equal () se comporta de manera diferente a ==.
Esto se debe a que NaN no es un número y, por lo tanto, no es igual a ningún número, incluido NaN.
La respuesta correcta es simple "porque el estándar (y los documentos ) lo dicen". Pero no voy a ser cínico porque es obvio que eso no es lo que buscas.
Además de las otras respuestas aquí, trataré de relacionar los infinitos con la aritmética saturada.
Otras respuestas ya han declarado que la razón por la cual las comparaciones en NaN resultan
true
, así que no voy a vencer a un caballo muerto.
Digamos que tengo un número entero saturado que representa los colores en escala de grises.
¿Por qué estoy usando aritmética saturada?
Porque cualquier cosa
más brillante
que el blanco sigue siendo blanca, y cualquier cosa
más oscura
que el negro sigue siendo negra (excepto
orange
).
Eso significa
BLACK - x == BLACK
y
WHITE + x == WHITE
.
¿Tiene sentido?
Ahora, supongamos que queremos representar esos colores en escala de grises con un número entero de 8 bits (con signo)
complementario
donde
BLACK == -127
y
WHITE == 127
.
¿Por qué se complementan?
Porque nos da un
cero con signo
como el
punto flotante IEEE 754
.
Y, como estamos usando aritmética saturante,
-127 - x == -127
y
127 + x == 127
.
¿Cómo se relaciona esto con los infinitos de coma flotante?
Reemplace el entero con coma flotante,
BLACK
con
NEGATIVE_INFINITY
y
WHITE
con
POSITIVE_INFINITY
y ¿qué obtiene?
NEGATIVE_INFINITY - x == NEGATIVE_INFINITY
y
POSITIVE_INFINITY + x == POSITIVE_INFINITY
.
Como
POSITIVE_INFINITY
, también lo usaré.
Primero necesitamos una clase para representar nuestro color saturado basado en enteros;
vamos a llamarlo
SaturatedColor
y supongamos que funciona como cualquier otro entero en Java.
Ahora, tomemos su código y reemplacemos
double
con nuestro propio
SaturatedColor
y
Double.POSITIVE_INFINITY
con
SaturatedColor.WHITE
:
SaturatedColor a = SaturatedColor.WHITE;
SaturatedColor b = SaturatedColor.WHITE;
Como establecimos anteriormente,
SaturatedColor.WHITE
(solo
WHITE
arriba) es
127
, así que hagamos eso aquí:
SaturatedColor a = 127;
SaturatedColor b = 127;
Ahora tomamos las declaraciones
System.out.println
que usó y reemplazamos
a
y
b
con su valor (¿valores?):
System.out.println(127 == 127);
System.out.println(127 < 127);
System.out.println(127 > 127);
Debería ser obvio lo que esto imprimirá.
Otra perspectiva que justifica que los valores "infinitos" sean iguales es evitar por completo el concepto de
cardinality
.
Esencialmente, si no puede especular sobre "cuán infinito es un valor en comparación con otro, dado que ambos son infinitos", es más simple suponer que
Inf = Inf
.
Editar: como aclaración de mi comentario sobre la cardinalidad, daré dos ejemplos sobre la comparación (o igualdad) de cantidades infinitas.
Considere el conjunto de enteros positivos
S1 = {1,2,3, ...}
, que es infinito.
Considere también el conjunto de enteros pares
S2 = {2,4,6, ...}
, que también son infinitos.
Si bien hay claramente el doble de elementos en S1 que en S2, tienen "igual número de elementos", ya que puede tener fácilmente una función uno a uno entre los conjuntos, es decir,
1 -> 2
,
2-> 4
, .. Tienen, pues, la misma cardinalidad.
Considere en cambio el conjunto de números reales
R
y el conjunto de enteros
I
De nuevo, ambos son conjuntos infinitos.
Sin embargo, para cada número entero
i
hay infinitos números reales entre
(i, i+1)
.
Por lo tanto, ninguna función uno a uno puede mapear los elementos de estos dos conjuntos y, por lo tanto, su cardinalidad es diferente.
En pocas palabras: la igualdad de cantidades infinitas es complicada, más fácil de evitar en idiomas imperativos :)
Para mí, parece que "porque debería comportarse igual que cero" sería una buena respuesta. El desbordamiento y el subflujo aritmético deben ser manejables de manera similar.
Si se desborda del valor más pequeño casi infinitamente pequeño que se puede almacenar en un flotante, obtendrá cero y los ceros se compararán como idénticos.
Si se desborda desde el valor más grande casi infinitamente grande que se puede almacenar en un flotante, obtiene INF, y los INF se comparan como idénticos.
Esto significa que el código que maneja números que están fuera del alcance en ambas direcciones no requerirá una carcasa especial separada para uno u otro. En cambio, ambos o ninguno necesitarán ser tratados de manera diferente.
Y el requisito más simple está cubierto por el caso "ninguno": si desea verificar si algo se desbordó o se desbordó, puede compararlo con cero / INF utilizando solo los operadores de comparación aritmética normales, sin necesidad de conocer la sintaxis especial del idioma actual para el comando de comprobación: ¿es Math.isInfinite (), Float.checkForPositiveInfinity (), hasOverflowed () ...?
Porque ese es el estándar. Infinito representa un número mayor o menor que Double.MAX_VALUE / -Double.MAX_VALUE.
NaN representa el resultado de una operación que no tiene sentido. Es decir, la operación posiblemente no salió con un número.
Supongo que la lógica es que una vez que un número se hace lo suficientemente grande (infinito) y debido a la limitación de los números de coma flotante, agregarle números no cambiará el resultado, por lo que su infinito ''como''.
Entonces, si desea comparar con números realmente grandes, en algún momento podría decir que esos dos números grandes son lo suficientemente cercanos para todos los efectos. Pero si desea comparar dos cosas que no son números, no puede compararlas, por lo que es falso. Al menos no podías compararlos como primitivos.
Su razonamiento es que
Double.POSITIVE_INFINITY
no debería ser igual a sí mismo porque es "probable" que se haya obtenido como resultado de una pérdida de precisión.
Esta línea de razonamiento se aplica a todos los puntos flotantes.
Se puede obtener cualquier valor finito como resultado de una operación inexacta.
Eso no empujó al comité de estandarización IEEE 754 a definir
==
como siempre evaluando a falso para valores finitos, entonces ¿por qué los infinitos deberían ser diferentes?
Según lo definido,
==
es útil para las personas que entienden lo que hace (es decir,
pruebe los valores de punto flotante que se han obtenido
, y ciertamente no los valores que deberían haberse obtenido con cálculos reales).
Para cualquiera que entienda eso, y usted necesita entenderlo, usar punto flotante incluso para cálculos que no involucren infinito, hacer que
Double.POSITIVE_INFINITY == Double.POSITIVE_INFINITY
evalúe como verdadero es conveniente, aunque solo sea para probar si el punto flotante El resultado de un cálculo de punto flotante es
Double.POSITIVE_INFINITY
.
Eso deja la pregunta de por qué NaN puede permitirse tener un comportamiento especial, y los infinitos deben seguir los mismos principios generales que los valores finitos. NaN es diferente de los infinitos: el principio subyacente del estándar IEEE 754 es que los valores son exactamente lo que son, pero el resultado de una operación se puede aproximar con respecto al resultado real y, en este caso, el valor de punto flotante resultante se obtiene de acuerdo con el modo de redondeo.
Olvide por un instante que
1.0 / 0.0
se define como + inf, lo cual es una molestia en esta discusión.
Piense por el momento en
Double.POSITIVE_INFINITY
solo como resultado de operaciones como
1.0e100 / 1.0e-300
o
Double.MAX_VALUE + Double.MAX_VALUE
.
Para estas operaciones, + inf es la aproximación más cercana del resultado real, al igual que para las operaciones que producen un resultado finito.
Por el contrario, NaN es el resultado que obtienes cuando la operación no tiene sentido.
Es defendible que NaN se comporte especialmente, pero inf es solo una aproximación de todos los valores demasiado grandes para representar.
En realidad,
1.0 / 0.0
también produce + inf, pero
eso
debería considerarse una excepción.
Hubiera sido tan coherente definir el resultado de esa operación como NaN, pero definirlo como + inf fue más conveniente en la implementación de algunos algoritmos.
Se proporciona un ejemplo en la página 10 de
las notas de Kahan
.
Más detalles de los que la mayoría deseará están en el
artículo "Cortes de ramas para funciones elementales complejas, o mucho ruido y pocas nueces"
.
También interpretaría la existencia en IEEE 754 de una bandera de “división por cero” separada de la bandera de NaN como un reconocimiento de que el usuario puede tratar la división por cero especialmente aunque no se define como que produce NaN.