Pourquoi Double.NaN == Double.NaN renvoie false?


155

J'étudiais juste les questions OCPJP et j'ai trouvé ce code étrange:

public static void main(String a[]) {
    System.out.println(Double.NaN==Double.NaN);
    System.out.println(Double.NaN!=Double.NaN);
}

Quand j'ai exécuté le code, j'ai obtenu:

false
true

Comment est le résultat falselorsque nous comparons deux choses qui se ressemblent? Que veut NaNdire?


8
C'est vraiment bizarre. Étant donné que Double.NaN est statique final, la comparaison avec == doit retourner true. +1 pour la question.
Stephan

2
La même chose est vraie en python:In [1]: NaN==NaN Out[1]: False
tdc

58
La même chose est vraie dans toutes les langues qui suivent correctement la norme IEEE 754.
zzzzBov

4
Intuition: "Hello" n'est pas un nombre, true (booléen) n'est pas non plus un nombre. NaN! = NaN pour la même raison "Hello"! = True
Kevin

3
@Stephan: La comparaison avec Double.NaN==Double.NaNdevrait en effet retourner true si elles Double.NaNétaient de type java.lang.Double. Cependant, son type est le primitif double, et les règles d'opérateur pour doubles'appliquent (qui exigent cette inégalité pour se conformer à IEEE 754, comme expliqué dans les réponses).
sleske

Réponses:


139

NaN signifie "Pas un nombre".

La troisième édition de Java Language Specification (JLS) dit :

Une opération qui déborde produit un infini signé, une opération qui déborde produit une valeur dénormalisée ou un zéro signé, et une opération qui n'a pas de résultat mathématiquement défini produit NaN. Toutes les opérations numériques avec NaN comme opérande produisent NaN en conséquence. Comme cela a déjà été décrit, NaN n'est pas ordonné, donc une opération de comparaison numérique impliquant un ou deux retours NaN falseet toute !=comparaison impliquant des retours NaN true, y compris x!=xquand xest NaN.


4
@nibot: Surtout vrai . Toute comparaison avec un flotteur conforme IEEE produira false. Cette norme diffère donc de Java en ce que l'IEEE l'exige (NAN != NAN) == false.
Drew Dormann le

2
Ouverture de cette boîte de Pandore - où voyez-vous que "IEEE exige que (NAN! = NAN) == false"?
Superviseur le

62

NaN n'est par définition égal à aucun nombre, y compris NaN. Cela fait partie de la norme IEEE 754 et est implémenté par le CPU / FPU. Ce n'est pas quelque chose que la JVM doit ajouter de logique à prendre en charge.

http://en.wikipedia.org/wiki/NaN

Une comparaison avec un NaN renvoie toujours un résultat non ordonné même en comparant avec lui-même. ... Les prédicats d'égalité et d'inégalité sont non signalants, donc x = x retournant false peut être utilisé pour tester si x est un NaN silencieux.

Java traite tous les NaN comme des NaN silencieux.


1
Est-il implémenté par le CPU ou est-il câblé dans la JVM comme le mentionne Bohemian?
Naweed Chougle le

3
La JVM doit appeler tout ce qui l'implémentera correctement. Sur un PC, le CPU fait tout le travail en tant que tel. Sur une machine sans ce support, la JVM doit l'implémenter. (Je ne connais pas une telle machine)
Peter Lawrey

À l'époque où le 8087 était une option, la bibliothèque C contenait un émulateur FP. Des programmes comme la JVM n'auraient pas à s'en soucier de toute façon.
Marquis of Lorne

49

Pourquoi cette logique

NaNsignifie Not a Number. Qu'est-ce qui n'est pas un nombre? N'importe quoi. Vous pouvez avoir n'importe quoi d'un côté et n'importe quoi de l'autre côté, donc rien ne garantit que les deux sont égaux. NaNest calculé avec Double.longBitsToDouble(0x7ff8000000000000L)et comme vous pouvez le voir dans la documentation de longBitsToDouble:

Si l'argument est une valeur dans la plage 0x7ff0000000000001Ljusqu'à 0x7fffffffffffffffLou dans la plage 0xfff0000000000001Ljusqu'à 0xffffffffffffffffL, le résultat est a NaN.

En outre, NaNest traité logiquement à l'intérieur de l'API.


Documentation

/** 
 * A constant holding a Not-a-Number (NaN) value of type
 * {@code double}. It is equivalent to the value returned by
 * {@code Double.longBitsToDouble(0x7ff8000000000000L)}.
 */
public static final double NaN = 0.0d / 0.0;

En passant, NaN est testé comme exemple de code:

/**
 * Returns {@code true} if the specified number is a
 * Not-a-Number (NaN) value, {@code false} otherwise.
 *
 * @param   v   the value to be tested.
 * @return  {@code true} if the value of the argument is NaN;
 *          {@code false} otherwise.
 */
static public boolean isNaN(double v) {
    return (v != v);
}

Solution

Ce que vous pouvez faire, c'est utiliser compare/ compareTo:

Double.NaNest considérée par cette méthode comme égale à elle-même et supérieure à toutes les autres doublevaleurs (y compris Double.POSITIVE_INFINITY).

Double.compare(Double.NaN, Double.NaN);
Double.NaN.compareTo(Double.NaN);

Ou, equals:

Si thiset les argumentdeux représentent Double.NaN, alors la equalsméthode retourne true, même si elle Double.NaN==Double.NaNa la valeur false.

Double.NaN.equals(Double.NaN);

Connaissez-vous des cas où le fait d'avoir NaN != NaNété faux rendrait les programmes plus compliqués que d'avoir NaN != NaNété vrai? Je sais que l'IEEE a pris la décision il y a longtemps, mais d'un point de vue pratique, je n'ai jamais vu de cas où cela est utile. Si une opération est supposée s'exécuter jusqu'à ce que des itérations consécutives donnent le même résultat, le fait d'avoir deux itérations consécutives produisant NaN serait "naturellement" détecté comme une condition de sortie sans ce comportement.
supercat du

@supercat Comment pouvez-vous dire que deux non-nombres aléatoires sont naturellement égaux? Ou disons, primitivement égal? Pensez à NaN comme une instance, pas comme quelque chose de primitif. Chaque résultat anormal différent est une instance différente de quelque chose d'étrange et même si les deux doivent représenter la même chose, l'utilisation de == pour différentes instances doit renvoyer false. D'un autre côté, lorsque vous utilisez equals, il peut être géré correctement comme vous le souhaitez. [ docs.oracle.com/javase/7/docs/api/java/lang/…
falsarella

@falsarella: Le problème n'est pas de savoir si deux nombres aléatoires doivent être considérés comme "définitivement égaux", mais plutôt dans quels cas est-il utile que n'importe quel nombre se compare comme "définitivement inégal" à lui-même. Si l'on essaie de calculer la limite de f(f(f...f(x))), et que l'on trouve un y=f[n](x)pour certains ntel que le résultat de f(y)est indiscernable y, alors yil sera impossible de le distinguer du résultat de tout plus profondément imbriqué f(f(f(...f(y))). Même si l'on voulait NaN==NaNêtre faux, avoir Nan!=Nan également été faux serait moins "surprenant" que d'avoir x!=xété vrai pour certains x.
supercat du

1
@falsarella: Je crois que le type de Double.NaNn'est pas Double, mais double, donc la question est liée au comportement de double. Bien qu'il existe des fonctions qui peuvent tester une relation d'équivalence impliquant des doublevaleurs, la seule réponse convaincante que je connaisse pour «pourquoi» (qui fait partie de la question initiale) est «parce que certaines personnes à l'IEEE ne pensaient pas que les tests d'égalité devraient définir une relation d'équivalence ". BTW, existe-t-il un moyen idiomatique concis de tester xet yd'équivalence en utilisant uniquement des opérateurs primitifs? Toutes les formulations que je connais sont plutôt maladroites.
supercat du

1
meilleure et simple réponse. Merci
Tarun Nagpal

16

Ce n'est peut-être pas une réponse directe à la question. Mais si vous voulez vérifier si quelque chose est égal à, Double.NaNvous devez utiliser ceci:

double d = Double.NaN
Double.isNaN(d);

Cela reviendra true


6

Le javadoc pour Double.NaN dit tout:

Une constante contenant une valeur de type Not-a-Number (NaN) double. Il équivaut à la valeur renvoyée par Double.longBitsToDouble(0x7ff8000000000000L).

Fait intéressant, la source de Doubledéfinit NaNainsi:

public static final double NaN = 0.0d / 0.0;

Le comportement spécial que vous décrivez est câblé dans la JVM.


5
Est-il câblé dans la JVM ou est-il implémenté par le CPU comme Peter le mentionne?
Naweed Chougle le

4

selon, La norme IEEE pour l'arithmétique à virgule flottante pour les nombres à double précision,

La représentation standard à virgule flottante double précision IEEE nécessite un mot de 64 bits, qui peut être représenté comme numéroté de 0 à 63, de gauche à droite

entrez la description de l'image ici où,

S: Sign  1 bit
E: Exponent  11 bits
F: Fraction  52 bits 

Si E=2047(tous Esont 1) et Fest différent de zéro, alors V=NaN("Pas un nombre")

Ce qui signifie,

Si tous les Ebits sont 1 et s'il y a un bit différent de zéro, Fle nombre est NaN.

par conséquent, entre autres, tous les nombres suivants sont NaN,

0 11111111 0000000000000000010000000000000000000000000000000000 = NaN
1 11111111 0000010000000000010001000000000000001000000000000000 = NaN
1 11111111 0000010000011000010001000000000000001000000000000000 = NaN

En particulier, vous ne pouvez pas tester

if (x == Double.NaN) 

pour vérifier si un résultat particulier est égal Double.NaN, car toutes les valeurs «pas un nombre» sont considérées comme distinctes. Cependant, vous pouvez utiliser la Double.isNaNméthode:

if (Double.isNaN(x)) // check whether x is "not a number"

3

NaN est une valeur spéciale qui indique "pas un nombre"; c'est le résultat de certaines opérations arithmétiques invalides, telles que sqrt(-1), et possède la propriété (parfois ennuyeuse) qui NaN != NaN.


2

Pas un nombre représente le résultat d'opérations dont le résultat n'est pas représentable par un nombre. L'opération la plus connue est 0/0, dont le résultat n'est pas connu.

Pour cette raison, NaN n'est égal à rien (y compris les autres valeurs not-a-number). Pour plus d'informations, il suffit de consulter la page wikipedia: http://en.wikipedia.org/wiki/NaN


-1: Il ne représente pas le résultat de 0/0. 0/0est toujours NaN, mais NaN peut être le résultat d'autres opérations - telles que 2+NaN: an operation that has no mathematically definite result produces NaN, selon la réponse par @AdrianMitev
ANeves

En effet, NaN signifie "Not a Number", et c'est le résultat de toutes les opérations qui ont comme résultat une valeur indéfinie ou non représentable. L'opération la plus connue et la plus courante est 0/0, mais il y a évidemment des tonnes d'autres opérations qui ont le même résultat. Je suis d'accord que ma réponse pourrait être améliorée, mais je ne suis pas d'accord sur le -1 ... Je viens de vérifier que wikipedia utilise également les opérations 0/0 comme premier exemple de fonctionnement avec un résultat NaN ( en.wikipedia.org/wiki/ NaN ).
Matteo

De plus, cela se trouve dans la source Java pour Double: public static final double NaN = 0.0d / 0.0;
Guillaume

1
@Matteo +0, maintenant que la fausse déclaration a disparu. Et mes -1 ou +1 ne sont pas pour vous d'être d'accord ou en désaccord; mais il est bon de laisser un commentaire avec un -1, afin que l'auteur puisse comprendre pourquoi sa réponse est jugée inutile - et la modifier, s'il le souhaite.
ANeves

@Guillaume si ce commentaire m'était destiné, reformulez-le: je ne le comprends pas.
ANeves

0

Selon ce lien , il y a des situations diverses et difficiles à retenir. C'est ainsi que je me souviens et je les distingue. NaNsignifie "mathématiquement indéfini" par exemple: "le résultat de 0 divisé par 0 est indéfini" et parce qu'il n'est pas défini, donc "la comparaison liée à undefined est bien sûr indéfinie". En outre, cela fonctionne plus comme des prémisses mathématiques. D'autre part, l'infini positif et négatif est prédéfini et définitif, par exemple "l'infini grand positif ou négatif est bien défini mathématiquement".

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.