Quelle est la justification de toutes les comparaisons renvoyant des valeurs fausses pour les valeurs NaN IEEE754?

267

Pourquoi les comparaisons des valeurs de NaN se comportent-elles différemment de toutes les autres valeurs? Autrement dit, toutes les comparaisons avec les opérateurs ==, <=,> =, <,> où 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, même pas dans les notes de cours sur le statut de l'IEEE 754 de Kahan qui discutent en détail d'autres décisions de conception.

Ce comportement déviant cause des problèmes lors d'un traitement de données simple. Par exemple, lors du tri d'une liste d'enregistrements par rapport à un champ à valeur réelle dans un programme C, j'ai besoin d'écrire du code supplémentaire pour gérer NaN comme élément maximal, sinon l'algorithme de tri pourrait devenir confus.

Edit: Jusqu'à présent, les réponses affirment toutes qu'il est inutile de comparer les NaN.

Je suis d'accord, mais cela ne signifie pas que la bonne réponse est fausse, ce serait plutôt un Not-a-Boolean (NaB), qui n'existe heureusement pas.

Donc, le choix de renvoyer vrai ou faux pour les comparaisons est à mon avis arbitraire, et pour le traitement général des données, il serait avantageux qu'il obéisse aux lois habituelles (réflexivité de ==, trichotomie de <, ==,>), de peur que les structures de données qui s'appuient sur ces lois 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, c'est un argument relativement faible pour briser la réflexivité à NaN.

starblue
la source
2
Une fois qu'un NaN est entré dans le calcul, il ne sortira jamais, donc votre test de convergence deviendrait une boucle infinie. Il est généralement préférable de signaler l'échec de la convergence vers la routine d'appel, éventuellement en renvoyant NaN. Ainsi, la structure de la boucle deviendrait généralement quelque chose comme while (fabs(x - oldX) > threshold), sortant de la boucle si la convergence se produit ou si un NaN entre dans le calcul. La détection du NaN et du remède approprié se ferait alors en dehors de la boucle.
Stephen Canon
1
Si NaN était l'élément minimal de l'ordre, la boucle while fonctionnerait toujours.
starblue

Réponses:

535

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 tient pas pour les 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 satisfait toutes les 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 faisons correspondre 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 yest inférieur à x. Si yest NaN, il n'est pas inférieur à toute valeur à virgule flottante x, donc la réponse est nécessairement fausse.

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

Quatre relations mutuellement exclusives sont possibles: inférieure à, égale, supérieure à et non ordonnée. Le dernier cas se présente quand au moins un opérande est NaN. Chaque NaN comparera 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 (mais pas toujours facile) de structurer votre code de manière à ce que les NaN passent correctement, mais ce n'est pas toujours le cas. Dans le cas contraire, un code supplémentaire peut être nécessaire, mais c'est un petit prix à payer pour la commodité que la fermeture algébrique a apportée à l'arithmétique à virgule flottante.


Addendum: 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 conserver aucun axiome familier. J'avoue avoir une certaine 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:

  • Cela x == ydevrait être équivalent à x - y == 0chaque fois que possible (au-delà d'être un théorème de l'arithmétique réelle, cela rend l'implémentation matérielle de la comparaison plus efficace en termes d'espace, ce qui était de la plus haute importance au moment où la norme a été élaborée - notez cependant que cela est violé pour x = y = l'infini, ce n'est donc pas une bonne raison en soi; il aurait pu être raisonnablement plié (x - y == 0) or (x and y are both NaN)).

  • Plus important encore, il n'y avait pas de isnan( )prédicat au moment 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 qui ne dépendaient pas des langages de programmation fournissant quelque chose comme cela isnan( )qui pourrait prendre de nombreuses 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éfinis sur les CRAY; dès que l'on en rencontrait un, il serait préférable d'arrêter le calcul plutôt que de le poursuivre pendant une durée indéterminée jusqu'à une conclusion indéfinie. C'est pourquoi certaines opérations sur les NaN doivent fournir 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és si x n'est pas un nombre (NaN); ceux-ci fournissent la seule distinction simple et non exceptionnelle entre les NaN et les nombres dans les langues qui n'ont pas de mot pour NaN et un prédicat IsNaN (x).

Notez que c'est également la logique qui exclut le retour de quelque chose comme un «Not-A-Boolean». Peut-être que ce pragmatisme était déplacé et que la norme aurait dû être exigée isnan( ), mais cela aurait rendu NaN presque impossible à utiliser efficacement et commodément 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 que de se plaindre sur Internet. Si vous voulez faire valoir qu'une relation d'ordre appropriée pour les conteneurs devrait également exister, je recommanderais de recommander que votre langage de programmation préféré implémente le totalOrderprédicat normalisé dans IEEE-754 (2008). Le fait qu'il n'ait pas déjà témoigné de la validité de l'inquiétude de Kahan qui a motivé l'état actuel des choses.

Stephen Canon
la source
16
J'ai lu vos points 1 et 2. Ensuite, j'ai observé qu'en arithmétique réelle (étendue pour autoriser NaN en premier lieu), NaN est égal à lui-même - simplement parce qu'en mathématiques, toute entité est égale à elle-même, sans exception. Maintenant, je suis confus: pourquoi IEEE n'a-t-il pas "correspondu au comportement de la vraie arithmétique", ce qui ferait NaN == NaN? Qu'est-ce que je rate?
max
12
D'accord; la non-réflexivité des NaN n'a pas nui à des langages comme Python, avec sa sémantique de confinement basée sur l'égalité. Vous ne voulez vraiment pas que l'égalité ne soit pas une relation d'équivalence lorsque vous essayez de créer des conteneurs par-dessus. Et avoir deux notions distinctes d'égalité n'est pas non plus une option conviviale, pour une langue qui est censée être facile à apprendre. Le résultat (dans le cas de Python) est un compromis désagréablement fragile entre le respect de l'IEEE 754 et une sémantique de confinement pas trop brisée. Heureusement, il est rare de mettre des NaN dans des conteneurs.
Mark Dickinson
5
Quelques belles observations ici: bertrandmeyer.com/2010/02/06/…
Mark Dickinson
6
@StephenCanon: De quelle manière (0/0) == (+ INF) + (-INF) serait-il plus absurde que d'avoir 1f/3f == 10000001f/30000002f? Si les valeurs à virgule flottante sont considérées comme des classes d'équivalence, a=bcela ne signifie pas "Les calculs qui ont donné aet b, s'ils étaient effectués avec une précision infinie, donneraient des résultats identiques", mais plutôt "Ce que l'on sait acorrespond à ce que l'on sait b". Je suis curieux si vous connaissez des exemples de code où avoir "Nan! = NaN" rend les choses plus simples qu'elles ne le seraient autrement?
supercat
5
Théoriquement, si vous aviez NaN == NaN et pas isNaN, vous pourriez toujours tester NaN avec !(x < 0 || x == 0 || x > 0), mais cela aurait été plus lent et plus maladroit que x != x.
user2357112 prend en charge Monica
50

NaN peut être considéré comme un état / nombre indéfini. similaire au concept de 0/0 non défini ou sqrt (-3) (dans le système de nombres réels où vit la virgule flottante).

NaN est utilisé comme une sorte d'espace réservé pour cet état indéfini. Mathématiquement parlant, undefined n'est pas égal à undefined. 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 renvoient faux.

Ce comportement est également avantageux dans les cas où vous comparez sqrt (-3) à sqrt (-2). Ils renverraient tous les deux NaN mais ils ne sont pas équivalents même s'ils renvoient la même valeur. Par conséquent, avoir toujours un retour égal à faux dans le cas de NaN est le comportement souhaité.

Chris
la source
5
Quel devrait être le résultat de sqrt (1.00000000000000022) == sqrt (1.0)? Que diriez-vous (1E308 + 1E308-1E308-1E308-1E308) == (1E308 + 1E308)? De plus, seulement cinq des six comparaisons renvoient la valeur false. L' !=opérateur renvoie true. Avoir NaN==NaNet les NaN!=NaNdeux renvoyer false permettrait au code qui compare x et y de choisir ce qui devrait se produire lorsque les deux opérandes sont NaN en choisissant soit ==ou !=.
supercat
38

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

NaN ne contient aucune information sur ce qu'est quelque chose, juste ce qu'elle n'est pas. Par conséquent, ces éléments ne peuvent jamais être considérés comme absolument égaux.

Jack Ryan
la source
6
Tous les ensembles vides sont égaux, par définition.
MSalters
28
Les boîtes qui vous sont données ne sont PAS connues pour être vides.
John Smith,
7
Pourriez-vous me dire que les boîtes ne contiennent pas la même chose? Je peux comprendre la justification de (NaN==Nan)==false. Ce que je ne comprends pas, c'est la justification (Nan!=Nan)==true.
supercat
3
Je suppose que NaN! = NaN est vrai parce que x! = Y est défini comme! (X == y). Certes, je ne sais pas si la spécification IEEE le définit de cette façon.
Kef Schecter
6
Mais dans cette analogie, si vous me donnez une boîte, dites qu'elle ne contient pas de pommes, puis me demandez si elle est égale à elle-même, vous vous attendez à ce que je dise non? Parce que c'est ce que j'aurais à dire selon IEEE.
point
12

D'après l'article de wikipedia sur NaN , les pratiques suivantes peuvent provoquer 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.
  • Appliquer une fonction à des arguments hors de son domaine, notamment prendre la racine carrée d'un nombre négatif, prendre le logarithme d'un nombre négatif, prendre la tangente d'un multiple impair de 90 degrés (ou π / 2 radians), ou prendre 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.

Stefan Rusek
la source
3
De plus, même si vous saviez quelle opération, cela n'aiderait pas. Je peux construire n'importe quel nombre de formules qui vont à 0/0 à un moment donné, qui ont (si nous supposons une continuité) des valeurs bien définies et différentes à ce point.
David Thornley
4

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

"Il doit être possible de comparer des 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ébordent ni ne dépassent jamais. Quatre relations mutuellement exclusives sont possibles: inférieure à, égale, supérieure à et non ordonnée Le dernier cas se présente quand au moins un opérande est NaN. Chaque NaN doit être comparé sans ordre avec tout, y compris lui-même. "

Rick Regan
la source
2

Cela ne semble particulier que parce que la plupart des environnements de programmation qui autorisent les NaN ne permettent pas également la logique à 3 valeurs. Si vous lancez une logique à 3 valeurs dans le mélange, cela devient cohérent:

  • (2,7 == 2,7) = vrai
  • (2,7 == 2,6) = faux
  • (2,7 == NaN) = inconnu
  • (NaN == NaN) = inconnu

Même .NET ne fournit pas d' bool? operator==(double v1, double v2)opérateur, vous êtes donc toujours coincé avec le (NaN == NaN) = falserésultat stupide .

Christian Hayter
la source
1

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

C'est un peu comme l'arithmétique en SQL avec des nullopérandes: ils résultent tous en null.

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

Daren Thomas
la source
3
"Ce n'est pas un nombre et donc le comparer n'a pas vraiment de sens." Les chaînes ne sont pas des nombres, mais les comparer est logique.
Jason
2
oui, comparer une chaîne à une chaîne est logique. Mais comparer une chaîne à, disons, des pommes, n'a pas beaucoup de sens. Puisque les pommes et les poires ne sont pas des nombres, est-il judicieux de les comparer? Quel est le plus grand?
Daren Thomas
@DarenThomas: En SQL, ni "IF NULL = NULL THEN FOO;" ni "IF Null <> Null THEN CALL FOO;" [ou quelle que soit la syntaxe] s'exécutera FOO. Pour que NaN soit équivalent, if (NaN != NaN) foo();il ne devrait pas s'exécuter foo, mais c'est le cas.
supercat
1

La réponse trop simplifiée est qu'un NaN n'a pas de valeur numérique, il n'y a donc rien à comparer avec quoi que ce soit d'autre.

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

David R Tribble
la source
0

Bien que je convienne que les comparaisons de NaN avec n'importe quel nombre réel ne devraient pas être ordonnées, je pense qu'il y a une raison valable de comparer NaN avec lui-même. Comment, par exemple, découvre-t-on 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 binaire), on pourrait bien se demander si les vecteurs binaires 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 significand était déplacé vers la gauche de manière à aligner le bit le plus significatif du significand sur le bit le plus significatif du format binaire, une valeur négative serait un NaN silencieux et toute valeur positive serait être un NaN de signalisation. Le zéro est bien sûr réservé à l'infini et la comparaison ne serait pas 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 sens à l'égalité.

Patrick Campbell
la source
-1

Pour moi, la façon la plus simple de l'expliquer est:

J'ai quelque chose et si ce n'est pas une pomme alors est-ce une orange?

Vous ne pouvez pas comparer NaN avec autre chose (même lui-même) car il n'a pas de valeur. Il peut également s'agir de n'importe quelle valeur (sauf un nombre).

J'ai quelque chose et s'il n'est pas égal à un nombre, est-ce une chaîne?

Halil Tevfik
la source
Que voulez-vous dire "cela peut être n'importe quelle valeur sauf un nombre"?
Pouchkine
-2

Parce que les mathématiques sont le domaine où les nombres "n'existent que". En informatique, vous devez initialiser ces nombres et conserver leur état en fonction de vos besoins. À cette époque, l'initialisation de la mémoire fonctionnait d'une manière sur laquelle vous ne pouviez jamais compter. Vous ne pourriez jamais vous permettre de penser à ce "oh, ce serait initialisé avec 0xCD tout le temps, mon algo ne se cassera pas" .

Vous avez donc besoin d'un solvant non mélangeur approprié qui est suffisamment collant pour ne pas laisser votre algorithme être aspiré et cassé. Les bons algorithmes impliquant des nombres vont surtout fonctionner avec des relations, et ceux if () seront omis.

C'est juste de la graisse que vous pouvez mettre dans une nouvelle variable lors de la création, au lieu de programmer l'enfer aléatoire de la mémoire de l'ordinateur. Et votre algorithme, quel qu'il soit, ne se cassera pas.

Ensuite, lorsque vous découvrez soudainement que votre algorithme produit des NaN, il est possible de le nettoyer, en examinant chaque branche une par une. Encore une fois, la règle "toujours faux" aide beaucoup à cela.

sanaris
la source
-4

Réponse très courte:

Parce que ce qui suit: ne nan / nan = 1 doit PAS tenir. Sinon, ce inf/infserait 1.

(Par conséquent, nanne peut pas être égal à nan. Quant à >ou <, si nanrespecterait toute relation d'ordre dans un ensemble satisfaisant la propriété d'Archimède, nous aurions à nouveau nan / nan = 1à la limite).

SeF
la source
2
Non, ça n'a pas de sens. Nous avons inf = infet inf / inf = nan, donc nan = nann'empêchera pas non nan / nan = nanplus.
starblue
@starblue Vous voulez dire nan / nan = 1? Quoi qu'il en soit ... Votre raisonnement a du sens si inf et nan étaient comme tous les autres nombres. Ce n'est pas le cas. La raison pour laquelle inf/infdoit être nan(ou forme indéterminée en mathématiques) et non 1est plus subtile que la simple manipulation algébrique (voir le théorème de L'Hospital).
SeF