162 votes

Pourquoi NaN n'est-il pas égal à NaN ?

La norme IEEE pertinente définit une constante numérique NaN (not a number) et prescrit que NaN doit être comparé comme non égal à lui-même. Pourquoi ?

Tous les langages que je connais appliquent cette règle. Mais elle cause souvent des problèmes importants, par exemple un comportement inattendu lorsque NaN est stocké dans un conteneur, lorsque NaN est dans les données qui sont triées, etc. Sans compter que la grande majorité des programmeurs s'attendent à ce que tout objet soit égal à lui-même (avant qu'ils n'apprennent l'existence de NaN), donc les surprendre ajoute aux bugs et à la confusion.

Les normes IEEE sont bien pensées, donc je suis sûr qu'il y a une bonne raison pour laquelle la comparaison de NaN comme égal à lui-même serait mauvaise. Je n'arrive tout simplement pas à savoir ce que c'est.

Edit : veuillez vous référer à Quelle est la raison pour laquelle toutes les comparaisons retournent faux pour les valeurs IEEE754 NaN ? comme la réponse qui fait autorité.

12 votes

Les normes IEEE ont été conçues par des ingénieurs, et non par des programmeurs, des vendeurs d'ordinateurs ou des auteurs de bibliothèques mathématiques, pour lesquels la règle NaN est un désastre.

201voto

xenadu Points 1669

La réponse acceptée est 100% sans question FAUX. . Pas à moitié faux ou même légèrement faux. Je crains que ce problème n'embrouille et n'induise en erreur les programmeurs pendant longtemps encore lorsque cette question apparaîtra dans les recherches.

NaN est conçu pour se propager à travers tous les calculs, les infectant comme un virus, donc si quelque part dans vos calculs profonds et complexes vous tombez sur un NaN, vous ne sortez pas une réponse apparemment sensée. Sinon, par identité, NaN/NaN devrait être égal à 1, avec toutes les autres conséquences comme (NaN/NaN)==1, (NaN*1)==NaN, etc. Si vous imaginez que vos calculs se sont trompés quelque part (l'arrondi a produit un dénominateur nul, donnant NaN), etc., vous pourriez obtenir des résultats très incorrects (ou pire : subtilement incorrects) de vos calculs sans indication évidente de la raison.

Il y a aussi de très bonnes raisons d'utiliser des NaN dans les calculs lorsqu'on cherche la valeur d'une fonction mathématique ; l'un des exemples donnés dans le document lié est la recherche des zéros() d'une fonction f(). Il est tout à fait possible qu'en sondant la fonction avec des valeurs supposées, vous en sondiez une où la fonction f() ne donne aucun résultat sensé. Cela permet à zeros() de voir le NaN et de continuer son travail.

L'alternative à NaN est de déclencher une exception dès qu'une opération illégale est rencontrée (également appelé un signal ou un piège). Outre les pénalités de performance massives que vous pourriez rencontrer, il n'y avait à l'époque aucune garantie que les processeurs le supporteraient au niveau matériel ou que le système d'exploitation/langage le supporterait au niveau logiciel ; chacun était son propre flocon de neige dans la gestion de la virgule flottante. L'IEEE a décidé de le traiter explicitement dans le logiciel comme les valeurs NaN afin qu'il soit portable sur n'importe quel OS ou langage de programmation. Les algorithmes en virgule flottante corrects sont généralement corrects dans toutes les implémentations en virgule flottante. que ce soit node.js ou COBOL (hah).

En théorie, il n'est pas nécessaire de définir des directives #pragma spécifiques, d'activer des drapeaux de compilation farfelus, d'attraper les bonnes exceptions ou d'installer des gestionnaires de signaux spéciaux pour que ce qui semble être un algorithme identique fonctionne correctement. Malheureusement, certains concepteurs de langage et auteurs de compilateurs ont été très occupés à défaire cette fonctionnalité au mieux de leurs capacités.

Veuillez lire certaines des informations sur l'histoire de la virgule flottante IEEE 754. Aussi cette réponse sur une question similaire où un membre du comité a répondu : Quelle est la raison pour laquelle toutes les comparaisons retournent faux pour les valeurs IEEE754 NaN ?

"Un entretien avec le vieil homme du point flottant

"Histoire du format à virgule flottante de l'IEEE"

Ce que tout informaticien devrait savoir sur l'arithmétique à virgule flottante

32 votes

J'aime aussi que NaN se propage "comme un virus". Malheureusement, ce n'est pas le cas. Dès que vous comparez, par exemple, NaN + 1 != 0 o NaN * 1 > 0 il renvoie True o False comme si tout allait bien. Par conséquent, vous ne pouvez pas compter sur NaN vous protégeant des problèmes si vous prévoyez d'utiliser des opérateurs de comparaison. Étant donné que les comparaisons ne vous aideront pas à "propager" les NaN, pourquoi ne pas au moins les rendre sensées ? En l'état actuel des choses, elles perturbent les cas d'utilisation des NaN dans les dictionnaires, elles rendent le tri instable, etc. Aussi, une petite erreur dans votre réponse. NaN/NaN == 1 n'évaluerait pas True si ça ne tenait qu'à moi.

36 votes

De plus, vous affirmez que ma réponse est 100% positive et absolument FAUX. Cependant, la personne du comité de l'IEEE que vous avez citée a déclaré dans le post que vous avez cité : ` De nombreux commentateurs ont fait valoir qu'il serait plus utile de préserver la réflexivité de l'égalité et de la trichotomie au motif que l'adoption de NaN != NaN ne semble préserver aucun axiome familier. J'avoue avoir une certaine sympathie pour ce point de vue, alors j'ai pensé revenir sur cette réponse et fournir un peu plus de contexte Alors peut-être, cher Monsieur, pourriez-vous envisager d'être un peu moins énergique dans vos déclarations.

2 votes

Je comprends, mais je voulais qu'il soit clair à 100% pour les nouveaux développeurs ou ceux qui ne sont pas intéressés par les détails de la virgule flottante que votre déclaration selon laquelle le comité a fait une "erreur" était incorrecte - la conception était délibérée.

122voto

Niet the Dark Absol Points 154811

Bien, log(-1) donne NaN y acos(2) donne également NaN . Est-ce que cela signifie que log(-1) == acos(2) ? De toute évidence, non. Il est donc parfaitement logique que NaN n'est pas égal à lui-même.

En réexaminant cette question presque deux ans plus tard, voici une fonction de comparaison "sûre pour NaN" :

function compare(a,b) {
    return a == b || (isNaN(a) && isNaN(b));
}

1 votes

Dans quelle situation le fait de les supposer égaux (dans une application réelle, pas en mathématiques) poserait-il un problème ?

1 votes

En d'autres termes, NaN signifie simplement indéfini . Par définition, vous ne pouvez pas raisonnablement affirmer qu'une valeur indéfinie est égale à une autre.

22 votes

Eh bien, si vous cherchez une intersection entre le log et la fonction acos alors toutes les valeurs négatives passées -1 serait considéré comme une intersection. Intéressant, Infinity == Infinity est vrai, malgré le fait que l'on ne puisse pas en dire autant dans les mathématiques réelles.

39voto

max Points 6673

Ma réponse originale (d'il y a 4 ans) critique la décision d'un point de vue moderne sans comprendre le contexte dans lequel la décision a été prise. En tant que telle, elle ne répond pas à la question.

La réponse correcte est donnée aquí :

NaN != NaN est née de deux considérations pragmatiques :

[...] Il n'y avait pas isnan( ) à l'époque où NaN a été formalisé dans l'arithmétique 8087 ; il était nécessaire de fournir aux programmeurs un moyen pratique et efficace de détecter les valeurs NaN sans dépendre de langages de programmation fournissant quelque chose comme isnan( ) ce qui pourrait prendre plusieurs années

Cette approche présentait un inconvénient : elle rendait NaN moins utile dans de nombreuses situations sans rapport avec le calcul numérique. Par exemple, bien plus tard, lorsque les gens ont voulu utiliser NaN pour représenter les valeurs manquantes et les mettre dans des conteneurs basés sur le hachage, ils n'y sont pas parvenus.

Si la commission avait prévu des cas d'utilisation futurs et les avait jugés suffisamment importants, elle aurait pu opter pour la formule plus verbeuse suivante !(x<x & x>x) au lieu de x!=x comme un test pour NaN . Cependant, leur objectif était plus pragmatique et étroit : fournir la meilleure solution pour un calcul numérique, et en tant que tel, ils ne voyaient aucun problème dans leur approche.

\===

Réponse originale :

Je suis désolé, bien que j'apprécie la réflexion qui a été faite dans la réponse la plus votée, je ne suis pas d'accord avec elle. NaN ne signifie pas "indéfini" - cf. http://www.cs.berkeley.edu/~wkahan/ieee754status/IEEE754.PDF page 7 (recherchez le mot "undefined"). Comme le confirme ce document, NaN est un concept bien défini.

De plus, l'approche de l'IEEE consistait à suivre les règles mathématiques régulières autant que possible, et lorsqu'elles ne le pouvaient pas, à suivre la règle de la "moindre surprise" - cf. https://stackoverflow.com/a/1573715/336527 . Tout objet mathématique est égal à lui-même, donc les règles mathématiques impliqueraient que NaN == NaN devrait être vrai. Je ne vois aucune raison valable et puissante de s'écarter d'un principe mathématique aussi important (sans parler des règles moins importantes de la trichotomie de comparaison, etc.)

En conséquence, ma conclusion est la suivante.

Les membres du comité IEEE n'ont pas réfléchi très clairement et ont fait une erreur. Comme très peu de gens ont compris l'approche du comité IEEE, ou se sont souciés de ce que la norme dit exactement à propos de NaN (à savoir : le traitement de NaN par la plupart des compilateurs viole de toute façon la norme IEEE), personne n'a tiré la sonnette d'alarme. Par conséquent, cette erreur est maintenant intégrée dans la norme. Il est peu probable qu'elle soit corrigée, car une telle correction casserait beaucoup de code existant.

Edit : Voici un poste d'une discussion très instructive. Remarque : pour avoir une vision impartiale, vous devez lire l'intégralité du fil de discussion, car Guido a un point de vue différent de celui de certains autres développeurs principaux. Cependant, Guido n'est pas personnellement intéressé par ce sujet, et suit largement les recommandations de Tim Peters. Si quelqu'un a les arguments de Tim Peters en faveur de NaN != NaN veuillez les ajouter dans les commentaires ; ils ont de bonnes chances de me faire changer d'avis.

3 votes

IMHO, avoir NaN violer la trichotomie a du sens, mais comme vous, je ne vois aucune justification sémantique raisonnable pour ne pas avoir de == définir une relation d'équivalence lorsque ses opérandes sont tous deux du même type (en allant un peu plus loin, je pense que les langages devraient explicitement interdire les comparaisons entre des choses de types différents - même lorsqu'il existe des conversions implicites - si ces comparaisons ne peuvent pas mettre en oeuvre une relation d'équivalence). Le concept de relation d'équivalence est tellement fondamental en programmation et en mathématiques qu'il semble fou de le violer.

1 votes

Vous pouvez lire la suite ; Kahan dit ailleurs dans ce document "Les NaNs doivent se conformer à des règles mathématiquement cohérentes qui ont été déduites, et non inventées arbitrairement [ ]". Je conviens qu'il ne mentionne pas comment NaN != NaN est déduit au-delà de dire qu'il est nécessaire pour distinguer NaN de non NaN sans support de bibliothèque comme isnan() .

0 votes

Notez que même si nous considérons que NaN représente une valeur inconnue (et donc que chaque NaN mai être inégale à une autre), nous ne pouvons pas conclure que deux valeurs NaN sont nécessairement son inégaux les uns par rapport aux autres. En bref : il pourrait être logique que NaN == NaN renvoie NaN (ou une autre représentation de l'expression "non calculable" dans votre langage - undefined, une exception levée, etc.), mais il est définitivement bizarre de simplement renvoyer false.

13voto

asf107 Points 623

Une propriété intéressante est : si x == x retourne false, alors x es NaN.

(on peut utiliser cette propriété pour vérifier si x es NaN ou pas.)

4 votes

On pourrait avoir cette propriété et toujours avoir (Nan != Nan) également renvoyer un faux. Si l'IEEE avait fait cela, le code qui voulait tester une relation d'équivalence entre a y b aurait pu utiliser !(a != b) .

0 votes

C'est un excellent substitut à np.isnan() et pd.isnull() ! !

9voto

Mike C Points 2197

Essayez ça :

var a = 'asdf';
var b = null;

var intA = parseInt(a);
var intB = parseInt(b);

console.log(intA); //logs NaN
console.log(intB); //logs NaN
console.log(intA==intB);// logs false

Si intA == intB était vrai, cela pourrait vous amener à conclure que a==b, ce qui n'est clairement pas le cas.

Une autre façon de voir les choses est que NaN vous donne simplement des informations sur ce que quelque chose N'EST PAS, pas ce qu'il est. Par exemple, si je dis "une pomme n'est pas un gorille" et "une orange n'est pas un gorille", pouvez-vous en conclure que "une pomme" = "une orange" ?

8 votes

"qui pourrait vous amener à conclure que a==b" -- Mais ce serait simplement une conclusion invalide -- strtol("010") == strtol("8"), par exemple.

2 votes

Je ne suis pas votre logique. Étant donné que a=16777216f , b=0.25 y c=0.125 si le fait que a+b == a+c être considéré comme impliquant que b==c ? Ou simplement que les deux calculs donnent indiscernable des résultats ? Pourquoi ne pas considérer que sqrt(-1) et (0,0/0,0) sont indiscernables, en l'absence d'un moyen de les distinguer ?

0 votes

Si vous sous-entendez que des choses indiscernables doivent être considérées comme égales, je ne suis pas d'accord. L'égalité implique que vous disposiez d'un moyen de distinguer les deux sujets de comparaison, et pas seulement d'un manque de connaissances identiques à leur sujet. Si vous n'avez aucun moyen de les distinguer, alors ils peuvent être égaux ou non. Je pourrais voir NaN==NaN renvoyer "indéfini", mais pas vrai.

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