java - significa - ¿Los patrones de bits de NaNs realmente dependen del hardware?
que significa nan en roblox (4)
Aquí hay un programa que demuestra diferentes patrones de bits NaN:
public class Test {
public static void main(String[] arg) {
double myNaN = Double.longBitsToDouble(0x7ff1234512345678L);
System.out.println(Double.isNaN(myNaN));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(myNaN)));
final double zeroDivNaN = 0.0 / 0.0;
System.out.println(Double.isNaN(zeroDivNaN));
System.out
.println(Long.toHexString(Double.doubleToRawLongBits(zeroDivNaN)));
}
}
salida:
true
7ff1234512345678
true
7ff8000000000000
Independientemente de lo que haga el hardware, el programa puede crear NaN que no sea el mismo que, por ejemplo, 0.0/0.0
, y puede tener algún significado en el programa.
Estaba leyendo acerca de los valores NaN de coma flotante en la Especificación del lenguaje Java (estoy aburrido). Un float
32 bits tiene este formato de bit:
seee eeee emmm mmmm mmmm mmmm mmmm mmmm
s
es el bit de signo, e
son los bits de exponente y m
son los bits de mantisa. Un valor de NaN se codifica como un exponente de todos los 1, y los bits de mantisa no son todos 0 (que serían +/- infinito). Esto significa que hay muchos valores de NaN posibles diferentes (con diferentes valores de s
y m
bits).
Sobre esto, JLS §4.2.3 dice:
IEEE 754 permite múltiples valores distintos de NaN para cada uno de sus formatos de coma flotante simple y doble. Si bien cada arquitectura de hardware devuelve un patrón de bits particular para NaN cuando se genera un nuevo NaN, un programador también puede crear NaN con diferentes patrones de bits para codificar, por ejemplo, información de diagnóstico retrospectiva.
El texto en el JLS parece implicar que el resultado de, por ejemplo, 0.0/0.0
, tiene un patrón de bits dependiente del hardware, y dependiendo de si esa expresión se calculó como una constante de tiempo de compilación, el hardware del que depende podría ser el hardware en el que se compiló el programa Java o el hardware en el que se ejecutó el programa. Todo esto parece muy escamoso si es cierto.
Ejecuté la siguiente prueba:
System.out.println(Integer.toHexString(Float.floatToRawIntBits(0.0f/0.0f)));
System.out.println(Integer.toHexString(Float.floatToRawIntBits(Float.NaN)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(0.0d/0.0d)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(Double.NaN)));
La salida en mi máquina es:
7fc00000
7fc00000
7ff8000000000000
7ff8000000000000
El resultado no muestra nada fuera de lo esperado. Los bits de exponente son todos 1. El bit superior de la mantisa también es 1, que para NaNs aparentemente indica un "NaN silencioso" en oposición a un "NaN de señalización" ( https://en.wikipedia.org/wiki/NaN#Floating_point ). El bit de signo y el resto de los bits de mantisa son 0. La salida también muestra que no hubo diferencia entre los NaN generados en mi máquina y los NaN constantes de las clases Float y Double.
Mi pregunta es, ¿esa salida está garantizada en Java, independientemente de la CPU del compilador o VM , o es realmente impredecible? El JLS es misterioso sobre esto.
Si esa salida está garantizada para 0.0/0.0
, ¿hay alguna forma aritmética de producir NaN que tenga otros patrones de bits (posiblemente dependientes del hardware?). (Sé que intBitsToFloat
/ longBitsToDouble
puede codificar otros NaN, pero me gustaría saber si pueden ocurrir otros valores de la aritmética normal).
Un punto de seguimiento: me he dado cuenta de que Float.NaN y Double.NaN especifican su patrón exacto de bits, pero en la fuente ( Float , Double ) se generan en 0.0/0.0
. Si el resultado de esa división es realmente dependiente del hardware del compilador, parece que hay un defecto en la especificación o la implementación.
El único otro valor de NaN
que pude generar con operaciones aritméticas normales hasta ahora es el mismo pero con el signo cambiado:
public static void main(String []args) {
Double tentative1 = 0d/0d;
Double tentative2 = Math.sqrt(-1d);
System.out.println(tentative1);
System.out.println(tentative2);
System.out.println(Long.toHexString(Double.doubleToRawLongBits(tentative1)));
System.out.println(Long.toHexString(Double.doubleToRawLongBits(tentative2)));
System.out.println(tentative1 == tentative2);
System.out.println(tentative1.equals(tentative2));
}
Salida:
Yaya
Yaya
7ff8000000000000
fff8000000000000
falso
cierto
Esto es lo que el §2.3.2 de la especificación de JVM 7 tiene que decir al respecto:
Los elementos del conjunto de valores dobles son exactamente los valores que se pueden representar utilizando el formato de coma flotante doble definido en el estándar IEEE 754, excepto que solo hay un valor NaN (IEEE 754 especifica 2 53-2 valores distintos de NaN).
y §2.8.1 :
La máquina virtual de Java no tiene ningún valor de NaN de señalización.
Entonces, técnicamente, solo hay un NaN. Pero el §4.2.3 del JLS también dice (justo después de su cita):
En su mayor parte, la plataforma Java SE trata los valores NaN de un tipo dado como si estuvieran colapsados en un único valor canónico, y por lo tanto, esta especificación normalmente se refiere a un NaN arbitrario como si fuera un valor canónico.
Sin embargo, la versión 1.3 de la plataforma Java SE introdujo métodos que permiten al programador distinguir entre valores de NaN: los métodos Float.floatToRawIntBits y Double.doubleToRawLongBits. El lector interesado se refiere a las especificaciones de las clases Float y Double para obtener más información.
Lo cual considero que significa exactamente lo que tú y CandiedOrange proponen: depende del procesador subyacente, pero Java los trata de todos CandiedOrange .
Pero se pone mejor: aparentemente, es muy posible que sus valores NaN se conviertan silenciosamente a diferentes NaN, como se describe en Double.longBitsToDouble()
:
Tenga en cuenta que este método puede no ser capaz de devolver un NaN doble con exactamente el mismo patrón de bits que el argumento largo. IEEE 754 distingue entre dos tipos de NaN, NaN silenciosos y NaN de señalización. Las diferencias entre los dos tipos de NaN generalmente no son visibles en Java. Las operaciones aritméticas en los NaN de señalización los convierten en NaN silenciosos con un patrón de bits diferente, pero a menudo similar. Sin embargo, en algunos procesadores simplemente copiando un NaN de señalización también realiza esa conversión. En particular, la copia de un NaN de señalización para devolverlo al método de llamada puede realizar esta conversión. Así que longBitsToDouble puede no ser capaz de devolver un doble con un patrón de bit NaN de señalización. En consecuencia, para algunos valores largos, doubleToRawLongBits (longBitsToDouble (start)) puede no ser igual al inicio. Además, qué patrones de bits particulares representan NaNs de señalización depende de la plataforma; aunque todos los patrones de bits de NaN, silenciosos o de señalización, deben estar en el rango de NaN identificado anteriormente.
Como referencia, aquí hay una tabla de los NaN dependientes del hardware. En resumen:
- x86:
quiet: Sign=0 Exp=0x7ff Frac=0x80000
signalling: Sign=0 Exp=0x7ff Frac=0x40000
- PA-RISC:
quiet: Sign=0 Exp=0x7ff Frac=0x40000
signalling: Sign=0 Exp=0x7ff Frac=0x80000
- Power:
quiet: Sign=0 Exp=0x7ff Frac=0x80000
signalling: Sign=0 Exp=0x7ff Frac=0x5555555500055555
- Alpha:
quiet: Sign=0 Exp=0 Frac=0xfff8000000000000
signalling: Sign=1 Exp=0x2aa Frac=0x7ff5555500055555
Entonces, para verificar esto, realmente necesitarías uno de estos procesadores y ve a probarlo. También se agradece cualquier idea sobre cómo interpretar los valores más largos para las arquitecturas Power y Alpha.
La forma en que leo el JLS aquí, el valor exacto de un NaN depende de quién / qué lo hizo y dado que la JVM no lo logró, no se lo pregunte. También podría preguntarles qué significa una cadena de "Código de error 4".
El hardware produce diferentes patrones de bits destinados a representar diferentes tipos de NaN. Lamentablemente, los diferentes tipos de hardware producen diferentes patrones de bits para los mismos tipos de NaN. Afortunadamente, hay un patrón estándar que Java puede usar para al menos decir que se trata de algún tipo de NaN.
Es como si Java mirara la cadena "Código de error 4" y dijera: "No sabemos qué significa el ''código 4'' en su hardware, pero sí la palabra ''error'' en esa cadena, por lo que creemos que es un error. "
El JLS intenta darte la oportunidad de resolverlo por tu cuenta:
"Sin embargo, la versión 1.3 de la plataforma Java SE introdujo métodos que permiten al programador distinguir entre valores NaN: los métodos Float.floatToRawIntBits
y Double.doubleToRawLongBits
. El lector interesado puede consultar las especificaciones de las clases Float
y Double
para obtener más información".
Lo cual me parece un reinterpret_cast
C ++. Es Java que le da la oportunidad de analizar el NaN usted mismo en caso de que sepa cómo se codificó su señal. Si desea rastrear las especificaciones de hardware para poder predecir qué eventos diferentes deben producir qué patrones de bits de NaN, puede hacerlo, pero está fuera de la uniformidad que la JVM debía proporcionarnos. Así que espera que cambie de hardware a hardware.
Al probar si un número es NaN, verificamos si es igual a él, ya que es el único número que no lo es. Esto no quiere decir que los bits sean diferentes. Antes de comparar los bits, la JVM prueba los muchos patrones de bits que dicen que es un NaN. Si es uno de esos patrones, entonces informa que no es igual, incluso si los bits de los dos operandos son realmente iguales (e incluso si son diferentes).
En 1964, cuando se le presionó para que diera una definición exacta de pornografía, el juez Stewart de la Corte Suprema de los Estados Unidos dijo: "Sé cuando lo veo". Pienso que Java hace lo mismo con NaN. Java no puede decirle nada de lo que un NaN de "señalización" podría estar señalizando porque no sabe cómo se codificó esa señal. Pero puede mirar los bits y decir que es un NaN de algún tipo ya que ese patrón sigue un estándar.
Si usted está en un hardware que codifica todos los NaN con bits uniformes, nunca probará que Java está haciendo algo para que los NaN tengan bits uniformes. De nuevo, por la forma en que leo el JLS, dicen abiertamente que estás solo aquí.
Puedo ver por qué esto se siente escamoso. Es escamoso Simplemente no es culpa de Java. Me atrevería a decir que algunos fabricantes de hardware emprendedores obtuvieron señales de NaN de señalización maravillosamente expresivas, pero no lograron que se adoptara como un estándar lo suficientemente amplio como para que Java pueda contar con él. Eso es lo escamoso Tenemos todos estos bits reservados para indicar qué tipo de NaN tenemos y no podemos usarlos porque no estamos de acuerdo con lo que significan. Hacer que Java supere el valor de NaN en un valor uniforme después de que el hardware lo haga, solo destruiría esa información, dañaría el rendimiento y la única recompensa es no parecer escamosa. Dada la situación, me alegra que se dieran cuenta de que podían engañar para salir del problema y definir que NaN no era igual a nada.