¿Los patrones de bits de los NaN realmente dependen del hardware?

12 minutos de lectura

avatar de usuario
Boann

Estaba leyendo sobre los valores NaN de punto flotante en la Especificación del lenguaje Java (soy aburrido). una de 32 bits float tiene este formato de bits:

seee eeee emmm mmmm mmmm mmmm mmmm mmmm

s es el bit de signo, e son los bits del 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 la mantisa no son todos 0 (lo que sería +/- infinito). Esto significa que hay muchos valores NaN posibles diferentes (que tienen diferentes s y m valores de bits).

En este, JLS §4.2.3 dice:

IEEE 754 permite múltiples valores de NaN distintos para cada uno de sus formatos de punto 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.

Hice 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

La salida 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 NaN 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. El resultado 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, es esa salida garantizada en Java, independientemente de la CPU del compilador o VM, o es todo realmente impredecible? El JLS es misterioso acerca de esto.

Si esa salida está garantizada para 0.0/0.0, ¿existen formas aritméticas de producir NaN que tengan otros patrones de bits (¿posiblemente dependientes del hardware?)? (Lo sé intBitsToFloat/longBitsToDouble puede codificar otros NaN, pero me gustaría saber si pueden ocurrir otros valores a partir de la aritmética normal).


Un punto de seguimiento: he notado que Flotador.NaN y Doble.NaN especificar su patrón de bits exacto, pero en la fuente (Flotar, Doble) son generados por 0.0/0.0. Si el resultado de esa división realmente depende del hardware del compilador, parece que hay una falla en la especificación o en la implementación.

  • Pruébelo en un IBM iSeries. Desafortunadamente, no tengo ninguno por el momento.

    – Elliot Frisch

    31 de julio de 2014 a las 2:58

avatar de usuario
jmiserez

Esto es lo que §2.3.2 de la especificación JVM 7 tiene que decir al respecto:

Los elementos del conjunto de valores dobles son exactamente los valores que se pueden representar usando el formato de punto flotante doble definido en el estándar IEEE 754, excepto que solo hay un valor NaN (IEEE 754 especifica 253-2 valores distintos de NaN).

y §2.8.1:

La máquina virtual de Java no tiene ningún valor NaN de señalización.

Entonces, técnicamente, solo hay un NaN. Pero §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 de NaN de un tipo dado como si estuvieran colapsados ​​en un solo 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 permitían 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 que considero que significa exactamente lo que usted y CandiedOrange proponen: depende del procesador subyacente, pero Java los trata a todos de la misma manera.

Pero se pone mejor: aparentemente, es muy posible que sus valores de NaN se conviertan silenciosamente a diferentes NaN, como se describe en Double.longBitsToDouble():

Tenga en cuenta que es posible que este método no pueda 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 copiar un NaN de señalización también realiza esa conversión. En particular, copiar un NaN de señalización para devolverlo al método de llamada puede realizar esta conversión. Por lo tanto, es posible que longBitsToDouble no pueda devolver un doble con un patrón de bits de señalización NaN. En consecuencia, para algunos valores largos, doubleToRawLongBits(longBitsToDouble(start)) puede no ser igual a start. Además, qué patrones de bits particulares representan NaN 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, hay una tabla de los NaN dependientes del hardware. aquí. 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 probarlo. También cualquier idea sobre cómo interpretar los valores más largos para las arquitecturas Power y Alpha es bienvenida.

  • +1 por mencionar el concepto “NaN canónico”. Hay millones de patrones de bits de NaN válidos, pero solo un NaN canónico. El OP podría haber preguntado mejor si su especificación requiere que la JVM produzca siempre un NaN canónico cuando produce un NaN. Parece que esto no es necesario, por lo que hay espacio para la dependencia del hardware.

    – Mishax

    31 de julio de 2014 a las 9:29

  • “Aparentemente, es muy posible que sus valores de NaN se conviertan silenciosamente a diferentes NaN, como se describe en Double.longBitsToDouble()”. Lo he observado en la práctica en mi CPU AMD x86. Si genero flotantes con patrones de bits en el rango de 7f800001 a 7f8036a9, se convierten silenciosamente a 7fc00001 a 7fc036a9 (es decir, establece el bit NaN silencioso) incluso antes de que tenga la oportunidad de mirarlos.

    -Boann

    31 de julio de 2014 a las 12:32

  • No creo que esos valores de NaN para Alpha puede estar bien: 0x2aa8000000000000 es una representación de un número finito (3.348595872897289986960303364....E-103 para ser precisos) en IEEE 754, y que yo sepa, el formato de coma flotante VAX G compatible con algunas máquinas Alpha no tiene NaN.

    –Mark Dickinson

    31 de julio de 2014 a las 14:07


  • @jmiserez +1, respuesta bien investigada. Proporciona la evidencia perfecta de por qué Java se niega a etiquetar los NaN como señalización o silencio. Tanto el x86 como el PA-RISC usan los mismos valores pero les dan significados opuestos. No es de extrañar que Java vomite sus manos y simplemente diga: es un NaN.

    – naranja confitada

    1 de agosto de 2014 a las 4:26

  • @jmiserez: Sí, no estoy seguro de cómo leer esa tabla; no tiene mucho sentido para mí. Y sí, el punto flotante VAX es diferente; pero es diferente en la medida en que ni siquiera tener NaNs (o infinitos), por lo que si la tabla se refiere al punto flotante VAX, entonces no tiene sentido tener un patrón de bits NaN. Francamente, creo que el autor de la tabla está confundido. 🙂

    –Mark Dickinson

    1 de agosto de 2014 a las 6:43

avatar de usuario
naranja_caramelizada

La forma en que leo el JLS aquí, el valor de bit exacto de un NaN depende de quién/qué lo hizo y, dado que JVM no lo hizo, no les 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. Desafortunadamente, 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 es una especie de NaN.

Es como si Java mirara la cadena “Código de error 4” y dijera: “No sabemos qué significa ‘código 4’ en su hardware, pero había la palabra ‘error’ en esa cadena, por lo que creemos que es un error. “

Sin embargo, el JLS intenta darle la oportunidad de resolverlo por su cuenta:

“Sin embargo, la versión 1.3 de la plataforma Java SE introdujo métodos que permitían al programador distinguir entre valores NaN: el Float.floatToRawIntBits y Double.doubleToRawLongBits métodos. Se remite al lector interesado a las especificaciones del Float y Double clases para más información.”

Que me parece un C++ reinterpret_cast. Es Java brindándole 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 deberían producir qué patrones de bits NaN, puede hacerlo, pero está fuera de la uniformidad que la JVM estaba destinada a brindarnos. Así que espere que pueda cambiar de hardware a hardware.

Al probar si un número es NaN, verificamos si es igual a sí mismo, 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 alguno de esos patrones, 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 de la Corte Suprema de EE. UU., Stewart, dijo la famosa frase: “Lo sé cuando lo veo”. Creo que Java hace lo mismo con NaN. Java no puede decirle nada que un NaN de “señalización” pueda estar señalando 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 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. Una vez más, la forma en que leo el JLS, están diciendo abiertamente que estás solo aquí.

Puedo ver por qué esto se siente escamoso. es escamoso Simplemente no es culpa de Java. Apuesto a que en algunos lugares algunos fabricantes de hardware emprendedores idearon patrones de bits de NaN de señalización maravillosamente expresivos, 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 a NaN en un valor uniforme después de que el hardware los fabrique solo destruiría esa información, dañaría el rendimiento y la única recompensa es no parecer escamoso. Dada la situación, me alegro de que se dieran cuenta de que podían salir del problema con trampas y definir a NaN como algo que no es igual a nada.

  • Con respecto a la última oración: no es Java lo que definió a NaN como no igual a nada, y de todos modos no se trata de eso …

    – usuario253751

    31 de julio de 2014 a las 8:33

  • +1 Esta respuesta tiene mucho sentido para mí. Java no garantiza que NaN tendrá un patrón de bits particular porque está diseñado para hacer que casi cualquier programa sea multiplataforma, y ​​cada plataforma tendrá su propia representación de hardware de NaN.

    –Kevin

    31 de julio de 2014 a las 16:38

  • @immibis IEEE definió que una comparación que involucre un NaN siempre devolvería falso. Java definió igual para darte esa comparación. No tenían que hacerlo. Podrían haber definido dos NaN de señalización como iguales. Si tuvieran una forma confiable de identificar los tipos de NaN que incluso podrían haber funcionado. Pero no encontraron la manera. Otros idiomas intentaron encontrar una manera de hacerlo: johndcook.com/blog/2009/07/21/ieee-arithmetic-python

    – naranja confitada

    5 de agosto de 2014 a las 3:57

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)));
  }
}

producción:

true
7ff1234512345678
true
7ff8000000000000

Independientemente de lo que haga el hardware, el programa puede crear NaN que pueden no ser los mismos que, por ejemplo, 0.0/0.0y puede tener algún significado en el programa.

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad