web-dev-qa-db-fra.com

Quelle est la raison pour laquelle toutes les comparaisons renvoient false pour les valeurs IEEE754 NaN?

Pourquoi les comparaisons de valeurs NaN se comportent-elles différemment de toutes les autres valeurs? C’est-à-dire toutes les comparaisons avec les opérateurs ==, <=,> =, <,> où l’une ou les deux valeurs sont NaN renvoie false, contrairement au comportement de toutes les autres valeurs.

Je suppose que cela simplifie les calculs numériques d’une certaine manière, mais je n’ai pas trouvé de raison explicite, pas même dans le Notes de conférence sur le statut de IEEE 754 de Kahan, qui traite en détail d’autres décisions de conception.

Ce comportement déviant pose des problèmes lors du traitement de données simple. Par exemple, lors du tri d'une liste d'enregistrements w.r.t. un champ réel dans un programme C, je dois écrire un code supplémentaire pour gérer NaN en tant qu'élément maximal, sinon l'algorithme de tri pourrait devenir confus.

Edit: Les réponses à ce jour montrent toutes que comparer des NaN n'a pas de sens.

Je suis d’accord, mais cela ne veut pas dire que la bonne réponse est fausse, mais plutôt que ce serait un non-booléen (NaB), qui, heureusement, n’existe pas.

Ainsi, le choix de rendre vrai ou faux pour les comparaisons est à mon avis arbitraire, .__ et pour le traitement de données général, il serait avantageux de respecter les lois habituelles .__ (réflexivité de ==, trichotomie de <, ==, >), de peur que les structures de données reposant sur ces lois ne se confondent.

Je demande donc un avantage concret à enfreindre ces lois, pas seulement un raisonnement philosophique.

Edit 2: Je pense que je comprends maintenant pourquoi rendre NaN maximal serait une mauvaise idée, cela gâcherait le calcul des limites supérieures.

NaN! = NaN peut être souhaitable pour éviter de détecter la convergence dans une boucle telle que

while (x != oldX) {
    oldX = x;
    x = better_approximation(x);
}

qui devrait cependant être mieux écrit en comparant la différence absolue avec une petite limite . Donc, à mon humble avis, cet argument est relativement faible pour casser la réflexivité à NaN.

223
starblue

J'étais membre du comité IEEE-754, je vais essayer de clarifier un peu les choses.

Tout d'abord, les nombres à virgule flottante ne sont pas des nombres réels et l'arithmétique à virgule flottante ne satisfait pas les axiomes de l'arithmétique réelle. La trichotomie n'est pas la seule propriété de l'arithmétique réelle qui ne s'applique pas aux flotteurs, ni même la plus importante. Par exemple:

  • L'addition n'est pas associative.
  • La loi distributive ne tient pas.
  • Il existe des nombres à virgule flottante sans inverses.

Je pourrais continuer. Il n'est pas possible de spécifier un type arithmétique de taille fixe qui satisfasse tout des propriétés de l'arithmétique réelle que nous connaissons et aimons. Le comité 754 doit décider de plier ou de casser certains d'entre eux. Ceci est guidé par des principes assez simples:

  1. Lorsque nous le pouvons, nous adaptons le comportement de l'arithmétique réelle.
  2. Lorsque nous ne pouvons pas, nous essayons de rendre les violations aussi prévisibles et aussi faciles à diagnostiquer que possible.

En ce qui concerne votre commentaire "cela ne signifie pas que la bonne réponse est fausse", c'est faux. Le prédicat (y < x) demande si y est inférieur à x. Si y est NaN, il est alors non inférieur à toute valeur à virgule flottante x; la réponse est donc forcément fausse.

J'ai mentionné que la trichotomie ne s'applique pas aux valeurs en virgule flottante. Cependant, il existe une propriété similaire qui est valable. Clause 5.11, paragraphe 2 de la norme 754-2008:

Quatre relations mutuellement exclusives sont possibles: inférieur à, égal, supérieur à et non ordonné. Le dernier cas survient quand au moins un opérande est NaN. Chaque NaN doit comparer sans ordre avec tout, y compris lui-même.

En ce qui concerne l'écriture de code supplémentaire pour gérer les NaN, il est généralement possible (bien que pas toujours facile) de structurer votre code de telle sorte que les NaN tombent correctement, mais ce n'est pas toujours le cas. Si ce n'est pas le cas, du code supplémentaire peut être nécessaire, mais c'est un petit prix à payer pour la commodité que la fermeture algébrique amène à l'arithmétique en virgule flottante.


Additif: 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 pas conserver d’axiome familier. J'avoue avoir de la sympathie pour ce point de vue, alors j'ai pensé que je reviendrais sur cette réponse et fournirais un peu plus de contexte.

D'après ce que j'ai compris en parlant à Kahan, NaN! = NaN est né de deux considérations pragmatiques:

  • Ce x == y devrait être équivalent à x - y == 0 chaque fois que cela est possible (au-delà d’un théorème d’arithmétique réelle, cela rend la mise en oeuvre matérielle de la comparaison plus efficace, ce qui était de la plus haute importance au moment de l’élaboration de la norme. Notez toutefois que cela est violé. pour x = y = infini, ce n’est donc pas une bonne raison en soi, elle aurait pu raisonnablement être pliée en (x - y == 0) or (x and y are both NaN)).

  • Plus important encore, il n'existait aucun prédicat isnan( ) au moment où NaN était formalisé dans l'arithmétique 8087; il était nécessaire de fournir aux programmeurs un moyen pratique et efficace de détecter des valeurs de NaN qui ne dépendaient pas de langages de programmation fournissant quelque chose comme isnan( ) qui pouvait prendre plusieurs années. Je citerai les propres écrits de Kahan sur le sujet:

S'il n'y avait aucun moyen de se débarrasser des NaN, ils seraient aussi inutiles que les Indéterminés sur CRAY; dès que l'on en rencontrait un, il était préférable d'arrêter le calcul plutôt que de le poursuivre indéfiniment jusqu'à une conclusion indéterminée. C’est pourquoi certaines opérations sur NaN doivent donner des résultats non NaN. Quelles opérations? … Les exceptions sont les prédicats C «x == x» et «x! = X», qui sont respectivement 1 et 0 pour chaque nombre infini ou fini x mais inversé si x n'est pas un nombre (NaN); ceux-ci fournissent la seule distinction simple et sans exception entre les NaN et les nombres dans les langues dépourvues de mot pour NaN et de prédicat IsNaN (x).

Notez que c’est aussi la logique qui interdit de renvoyer quelque chose comme un «non-booléen». Peut-être que ce pragmatisme était mal placé et que la norme aurait dû exiger isnan( ), mais cela aurait rendu NaN presque impossible à utiliser de manière efficace et pratique pendant plusieurs années, alors que le monde attendait l'adoption du langage de programmation. Je ne suis pas convaincu que cela aurait été un compromis raisonnable.

Pour être franc: le résultat de NaN == NaN ne va pas changer maintenant. Mieux vaut apprendre à vivre avec cela que de se plaindre sur Internet. Si vous voulez faire valoir qu'une relation d'ordre appropriée pour les conteneurs devrait aussi exister, je vous recommanderais de préconiser que votre langage de programmation préféré implémente le prédicat totalOrder normalisé dans IEEE-754 (2008). Le fait qu’il n’ait pas encore parlé de la validité de la préoccupation de Kahan qui a motivé la situation actuelle.

453
Stephen Canon

NaN peut être considéré comme un état/nombre indéfini. semblable au concept de 0/0 étant indéfini ou sqrt (-3) (dans le système de nombres réels où vit le point flottant). 

NaN est utilisé comme une sorte d'espace réservé pour cet état non défini. Mathématiquement, indéfini n'est pas égal à indéfini. Vous ne pouvez pas non plus dire qu'une valeur non définie est supérieure ou inférieure à une autre valeur non définie. Par conséquent, toutes les comparaisons sont fausses.

Ce comportement est également avantageux dans les cas où vous comparez sqrt (-3) à sqrt (-2). Ils retourneraient tous les deux NaN mais ils ne sont pas équivalents même s'ils retournent la même valeur. Par conséquent, le fait d’avoir une égalité qui revient toujours à faux s’agissant de NaN est le comportement souhaité.

45
Chris

Pour introduire encore une autre analogie. Si je vous remets deux boîtes et que je vous dis qu'aucune d'entre elles ne contient de pomme, pouvez-vous me dire que les boîtes contiennent la même chose?

NaN ne contient aucune information sur ce que quelque chose est, mais sur ce que ce n'est pas. Par conséquent, on ne peut jamais dire que ces éléments sont égaux.

31
Jack Ryan

D'après l'article de wikipedia sur NaN , les pratiques suivantes peuvent causer des NaN:

  • Toutes les opérations mathématiques> avec un NaN comme au moins un opérande
  • Les divisions 0/0, ∞/∞, /-, -∞/∞ et -∞/-∞
  • Les multiplications 0 × et 0 × -∞
  • Les additions ∞ + (-∞), (-∞) + ∞ et les soustractions équivalentes.
  • Application d'une fonction à des arguments extérieurs à son domaine, notamment prendre la racine carrée d'un nombre négatif, le logarithme d'un nombre négatif, la tangente d'un multiple impair de 90 degrés (ou π/2 radians) ou le sinus inverse ou cosinus d'un nombre inférieur à -1 ou supérieur à +1.

Puisqu'il n'y a aucun moyen de savoir laquelle de ces opérations a créé le NaN, il n'y a aucun moyen de les comparer qui ait du sens.

12
Stefan Rusek

Je ne connais pas la logique de conception, mais voici un extrait de la norme IEEE 754-1985:

"Il sera possible de comparer les nombres à virgule flottante dans tous les formats pris en charge, même si les formats des opérandes diffèrent. Les comparaisons sont exactes et ne dépassent jamais jamais le dépassement. Quatre relations mutuellement exclusives sont possibles: inférieur, égal, supérieur à et non ordonné Le dernier cas survient quand au moins un opérande est NaN. Chaque NaN doit être comparé sans ordre avec tout, y compris lui-même. "

4
Rick Regan

Cela ne semble étrange que parce que la plupart des environnements de programmation autorisant les NaN n'autorisent pas la logique à 3 valeurs. Si vous intégrez une logique à 3 valeurs dans le mix, cela devient cohérent:

  • (2.7 == 2.7) = true
  • (2.7 == 2.6) = false
  • (2,7 == NaN) = inconnu
  • (NaN == NaN) = inconnu

Même .NET ne fournit pas d'opérateur bool? operator==(double v1, double v2), vous restez donc coincé avec le résultat idiot (NaN == NaN) = false.

2
Christian Hayter

J'imagine que NaN (Not A Number) signifie exactement cela: ce n'est pas un nombre et donc comparer cela n'a pas vraiment de sens.

C'est un peu comme l'arithmétique en SQL avec les opérandes null: ils ont tous pour résultat null.

Les comparaisons des nombres en virgule flottante comparent des valeurs numériques. Ainsi, ils ne peuvent pas être utilisés pour des valeurs non numériques. NaN ne peut donc pas être comparé dans un sens numérique.

1
Daren Thomas

La réponse trop simpliste est qu'un NaN n'a pas de valeur numérique, il n'y a donc rien à comparer.

Vous pouvez envisager de tester et de remplacer vos NaN par + INF si vous souhaitez qu’ils agissent comme + INF.

1
David R Tribble

Bien que je convienne que les comparaisons de NaN avec un nombre réel ne doivent pas être ordonnées, je pense qu’il est juste de comparer NaN avec lui-même. Comment, par exemple, découvre-t-il la différence entre les NaN de signalisation et les NaN silencieux? Si nous considérons les signaux comme un ensemble de valeurs booléennes (c’est-à-dire un vecteur de bits), on peut se demander si les vecteurs de bits sont identiques ou différents et ordonner les ensembles en conséquence. Par exemple, lors du décodage d'un exposant biaisé maximal, si le significande était laissé décalé de manière à aligner le bit le plus significatif du significande sur le bit le plus significatif du format binaire, une valeur négative serait un NaN silencieux et toute valeur positive être un NaN de signalisation. Zéro bien sûr est réservé à l'infini et la comparaison serait non ordonnée. L'alignement MSB permettrait la comparaison directe des signaux même à partir de différents formats binaires. Deux NaN avec le même ensemble de signaux seraient donc équivalents et donneraient un sens à l'égalité.

0
Patrick Campbell

NaN est une nouvelle instance implicite (d'un type spécial d'erreur d'exécution). Cela signifie NaN !== NaN pour la même raison que new Error !== new Error

Et gardez à l'esprit qu'une telle implicite se voit aussi en dehors des erreurs, par exemple, dans le contexte des expressions rationnelles, cela signifie /a/ !== /a/ qui n'est qu'un sucre syntaxique pour new RegExp('a') !== new RegExp('a').

0
Ivan Castellanos