56 votes

Les motifs binaires des NaNs dépendent-ils vraiment du matériel ?

J'étais en train de lire sur les valeurs NaN en virgule flottante dans la spécification du langage Java (je suis ennuyeux). Un programme 32 bits float a ce format de bit :

seee eeee emmm mmmm mmmm mmmm mmmm mmmm

s est le bit de signe, e sont les bits de l'exposant, et m sont les bits de la mantisse. Une valeur NaN est codée comme un exposant de tous les 1 et les bits de la mantisse ne sont pas tous 0 (ce qui serait +/- l'infini). Cela signifie qu'il existe un grand nombre de valeurs NaN différentes possibles (ayant des valeurs s y m valeurs binaires).

Là-dessus, JLS §4.2.3 dit :

La norme IEEE 754 autorise plusieurs valeurs NaN distinctes pour chacun de ses formats à virgule flottante simple et double. Bien que chaque architecture matérielle renvoie un motif binaire particulier pour NaN lorsqu'un nouveau NaN est généré, un programmeur peut également créer des NaN avec différents motifs binaires pour coder, par exemple, des informations de diagnostic rétrospectives.

Le texte de la JLS semble impliquer que le résultat de, par exemple, 0.0/0.0 Selon que cette expression a été calculée comme une constante de compilation, le matériel dont elle dépend peut être le matériel sur lequel le programme Java a été compilé ou le matériel sur lequel le programme a été exécuté. Tout ceci semble très flasque si c'est vrai.

J'ai effectué le test suivant :

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 sortie sur ma machine est :

7fc00000
7fc00000
7ff8000000000000
7ff8000000000000

La sortie indique que les bits de l'exposant sont à 1 comme prévu. Le bit supérieur de la mantisse est également 1, ce qui, pour les NaN, indique apparemment un "NaN silencieux" par opposition à un "NaN de signalisation" ( https://en.wikipedia.org/wiki/NaN#Floating_point ). Le bit de signe et le reste des bits de mantisse sont à 0. La sortie indique également qu'il n'y a pas de différence entre les NaN générés sur ma machine et les NaN constants des classes Float et Double.

Ma question est la suivante : cette sortie est-elle garantie en Java, indépendamment du CPU du compilateur ou de la VM, ou tout cela est-il vraiment imprévisible ? Le JLS est mystérieux à ce sujet.

Si cette sortie est garantie pour 0.0/0.0 Dans le cas d'un NaN, existe-t-il des moyens arithmétiques de produire des NaN avec d'autres modèles de bits (dépendant éventuellement du matériel) ? (Je sais que je pourrais utiliser intBitsToFloat / longBitsToDouble pour coder d'autres NaNs délibérément, mais j'aimerais savoir si d'autres valeurs peuvent se produire à partir de l'arithmétique normale).


Une question complémentaire : J'ai remarqué que Float.NaN y Double.NaN spécifient leur configuration binaire exacte, mais dans la source ( Flotteur , Double ) ils sont générés par 0.0/0.0. Si ce résultat dépend réellement du matériel du compilateur, la spécification est erronée, n'est-ce pas, et ne peut pas réellement faire cette garantie ?

37voto

jmiserez Points 1507

C'est ce que §2.3.2 de la spécification JVM 7 a à dire à ce sujet :

Les éléments de l'ensemble de valeurs doubles sont exactement les valeurs qui peuvent être représentées en utilisant le format double à virgule flottante défini dans la norme IEEE 754, sauf que qu'il n'y a qu'une seule valeur NaN (IEEE 754 spécifie 2 valeurs NaN). 53 -2 valeurs NaN distinctes).

y §2.8.1 :

La machine virtuelle Java ne signale pas la valeur NaN.

Donc, techniquement, il n'y a qu'un seul NaN. Mais §4.2.3 du JLS dit aussi (juste après votre citation) :

Dans la plupart des cas, la plate-forme Java SE traite les valeurs NaN d'un type donné comme si elles étaient regroupées en une seule valeur canonique. Par conséquent, cette spécification fait normalement référence à un NaN arbitraire comme à une valeur canonique.

Toutefois, la version 1.3 de la plate-forme Java SE a introduit des méthodes permettant au programmeur de distinguer les valeurs NaN : les méthodes Float.floatToRawIntBits et Double.doubleToRawLongBits. Le lecteur intéressé est renvoyé aux spécifications des classes Float et Double pour plus d'informations.

Ce qui, pour moi, signifie exactement ce que vous et Orange confite proposer : cela dépend du processeur sous-jacent, mais Java les traite tous de la même manière.

Mais ça s'améliore : Apparemment, il est tout à fait possible que vos valeurs NaN soient silencieusement converties en NaN différents, comme décrit dans Double.longBitsToDouble() :

Notez que cette méthode peut ne pas être en mesure de retourner un double NaN avec exactement le même motif de bits que l'argument long. L'IEEE 754 distingue deux types de NaN, les NaN silencieux et les NaN de signalisation. Les différences entre ces deux types de NaN ne sont généralement pas visibles en Java. Les opérations arithmétiques sur les NaN de signalisation les transforment en NaN silencieux avec une configuration binaire différente, mais souvent similaire. Cependant, sur certains processeurs, la simple copie d'un NaN de signalisation effectue également cette conversion. En particulier, la copie d'un NaN de signalisation pour le retourner à la méthode appelante peut effectuer cette conversion. Ainsi, longBitsToDouble peut ne pas être en mesure de retourner un double avec un modèle de bit NaN de signalisation. Par conséquent, pour certaines valeurs longues, doubleToRawLongBits(longBitsToDouble(start)) peut ne pas être égal à start. De plus, les motifs binaires particuliers qui représentent des NaN de signalisation dépendent de la plate-forme, bien que tous les motifs binaires NaN, silencieux ou de signalisation, doivent se situer dans la plage NaN identifiée ci-dessus.

Pour référence, il existe un tableau des NaNs dépendant du matériel. aquí . En résumé :

- 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

Donc, pour le vérifier, il vous faudrait vraiment un de ces processeurs et aller l'essayer. Toute idée sur la façon d'interpréter les valeurs plus longues pour les architectures Power et Alpha est également la bienvenue.

15voto

CandiedOrange Points 1287

De la façon dont je lis le JLS ici, la valeur exacte du bit d'un NaN dépend de qui/quoi l'a fait et puisque la JVM ne l'a pas fait, ne leur demandez pas. Vous pourriez tout aussi bien leur demander ce que signifie une chaîne "Error code 4".

Le matériel produit différents modèles de bits destinés à représenter différents types de NaN. Malheureusement, les différents types de matériel produisent différents modèles de bits pour les mêmes types de NaN. Heureusement, il existe un modèle standard que Java peut utiliser pour au moins savoir qu'il s'agit d'un type de NaN.

C'est comme si Java avait regardé la chaîne "Code d'erreur 4" et avait dit : "On ne sait pas ce que signifie "code 4" sur votre matériel, mais il y avait le mot "erreur" dans cette chaîne, donc on pense que c'est une erreur."

Le JLS essaie cependant de vous donner une chance de vous débrouiller tout seul :

" Toutefois, la version 1.3 de la plate-forme Java SE a introduit des méthodes permettant au programmeur de distinguer les valeurs NaN : les méthodes Float.floatToRawIntBits et Double.doubleToRawLongBits. Le lecteur intéressé est renvoyé aux spécifications des classes Float et Double pour plus d'informations."

Ce qui me semble être un cast de réinterprétation c++. C'est Java qui vous donne la possibilité d'analyser vous-même le NaN au cas où vous sauriez comment son signal a été encodé. Si vous voulez retrouver les spécifications du matériel pour pouvoir prédire quels événements différents devraient produire quels modèles de bits NaN, vous êtes libre de le faire mais vous êtes en dehors de l'uniformité que la JVM était censée nous donner. Attendez-vous donc à ce que cela change d'un matériel à l'autre.

Lorsqu'on teste si un nombre est NaN, on vérifie s'il est égal à lui-même puisque c'est le seul nombre qui ne l'est pas. Cela ne veut pas dire que les bits sont différents. Avant de comparer les bits, la JVM teste les nombreux modèles de bits qui indiquent qu'il s'agit d'un NaN. S'il s'agit de l'une de ces configurations, elle signale que le nombre n'est pas égal, même si les bits des deux opérandes sont réellement identiques (et même s'ils sont différents).

En 1964, lorsqu'on lui a demandé de donner une définition exacte de la pornographie, le juge Stewart de la Cour suprême des États-Unis a déclaré : "Je le sais quand je le vois". Je pense que Java fait la même chose avec les NaN. Java ne peut pas vous dire ce qu'un NaN "signalant" pourrait signaler, car il ne sait pas comment ce signal a été codé. Mais il peut regarder les bits et dire qu'il s'agit d'une sorte de NaN puisque ce modèle suit une norme.

Si vous vous trouvez sur un matériel qui code tous les NaN avec des bits uniformes, vous ne pourrez jamais prouver que Java fait quelque chose pour que les NaN aient des bits uniformes. Encore une fois, la façon dont je lis le JLS, ils disent carrément que vous êtes tout seul ici.

Je peux voir pourquoi ça semble floconneux. C'est bancal. Mais ce n'est pas la faute de Java. Je parierais que quelque part, des fabricants de matériel entreprenants sont venus avec des modèles de bits de signalisation NaN merveilleusement expressifs, mais ils n'ont pas réussi à les faire adopter comme standard assez largement pour que Java puisse compter dessus. C'est ça qui est bancal. Nous avons tous ces bits réservés pour signaler quel type de NaN nous avons et nous ne pouvons pas les utiliser parce que nous ne sommes pas d'accord sur leur signification. Faire en sorte que Java convertisse les NaN en une valeur uniforme après que le matériel les ait générés ne ferait que détruire cette information, nuire aux performances, et le seul avantage est de ne pas paraître bizarre. Compte tenu de la situation, je suis heureux qu'ils aient réalisé qu'ils pouvaient tricher pour s'affranchir du problème et définir NaN comme n'étant égal à rien.

14voto

Patricia Shanahan Points 11270

Voici un programme démontrant différents modèles 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)));
  }
}

sortie :

true
7ff1234512345678
true
7ff8000000000000

Indépendamment de ce que fait le matériel, le programme peut lui-même créer des NaN qui peuvent ne pas être les mêmes que par exemple 0.0/0.0 et peuvent avoir une certaine signification dans le programme.

4voto

falsarella Points 2674

Le seul autre NaN que j'ai pu générer avec des opérations arithmétiques normales jusqu'à présent est la même mais avec le signe changé :

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

Sortie :

NaN
NaN
7ff8000000000000
fff8000000000000
false
true

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X