Quelle est la justification de toutes les comparaisons renvoyant false pour les valeurs NaEE IEEE754?

Pourquoi les comparaisons de valeurs NaN se comportent-elles différemment de toutes les autres valeurs? C’est-à-dire que 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 en quelque sorte les calculs numériques, mais je n’ai pas trouvé de raison explicite, pas même dans les notes de cours sur l’état de l’IEEE 754 de Kahan qui traitent d’autres décisions de conception en détail.

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

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

Je suis d’accord, mais cela ne signifie pas que la bonne réponse est fausse, mais plutôt une non-booléenne (NaB), qui n’existe heureusement pas.

Donc, le choix de retourner vrai ou faux pour les comparaisons est à mon avis arbitraire, et pour le traitement général des données, il serait avantageux d’obéir aux lois habituelles (réflexivité ==, sortingchotomie de ), de peur que les structures de données qui s’appuient sur ces lois deviennent confuses.

Donc, je demande un avantage concret de la violation de ces lois, pas seulement du raisonnement philosophique.

Edit 2: Je pense comprendre maintenant pourquoi faire 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 é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é chez NaN.

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

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 sortingchotomie n’est pas la seule propriété de l’arithmétique réelle qui ne tient pas pour les flottants, ni même les plus importants. Par exemple:

  • L’ajout n’est pas associatif.
  • La loi de dissortingbution ne tient pas.
  • Il y a 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 certaines d’entre elles. 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 le pouvons pas, nous essayons de rendre les violations aussi prévisibles et 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, alors ce n'est pas moins que toute valeur en virgule flottante x , donc la réponse est nécessairement fausse.

J'ai mentionné que la sortingchotomie ne tient pas pour les valeurs à virgule flottante. Cependant, il existe une propriété similaire qui tient. Clause 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 doit comparer sans ordonnance 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. Si ce n'est pas le cas, un code supplémentaire peut être nécessaire, mais c'est un petit prix à payer pour la commodité que la fermeture algébrique a apscope à l'arithmétique en 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 sortingchotomie au motif que l’adoption de NaN! = NaN ne semble préserver aucun axiome connu. J'avoue avoir de la sympathie pour ce sharepoint vue, alors j'ai pensé revenir sur cette réponse et fournir 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:

  • Que x == y devrait être équivalent à x - y == 0 chaque fois que possible (au-delà d'être un théorème d'arithmétique réelle, cela rend la mise en œuvre matérielle de la comparaison plus efficace, ce qui était primordial au moment du développement de la norme) notez toutefois que ceci est violé pour x = y = infini, donc ce n'est pas une bonne raison en soi, il aurait pu raisonnablement être plié pour (x - y == 0) or (x and y are both NaN) ) .

  • Plus important encore, il n'y avait pas de prédicat isnan( ) 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 de langages de programmation fournissant quelque chose comme isnan( ) qui pourrait prendre de nombreuses années. Je citerai les é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 qu'il y en a eu une, il vaut mieux arrêter le calcul plutôt que de le poursuivre pour une durée indéterminée jusqu'à une conclusion indéfinie. C'est pourquoi certaines opérations sur 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 inverser si x est un nombre non (NaN); ceux-ci fournissent la seule distinction non exceptionnelle simple entre NaN et les nombres dans les langues qui ne contiennent pas de mot pour NaN et un prédicat IsNaN (x).

Notez que c'est aussi la logique qui exclut le retour de quelque chose comme un «non-booléen». Peut-être que ce pragmatisme a été déplacé et que la norme aurait dû exiger isnan( ) , mais cela aurait rendu NaN impossible à utiliser de manière efficace et pratique pendant plusieurs années, alors que le monde attendait l'adoption des langages 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 se plaindre sur internet. Si vous voulez faire valoir qu'une relation de commande adaptée aux conteneurs devrait également exister, je vous recommande 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’elle n’ait pas déjà été à l’origine de la préoccupation de Kahan qui a motivé l’état actuel des choses.

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

NaN est utilisé comme une sorte d’espace réservé pour cet état indéfini. Mathématiquement parlant, undefined n’est pas égal à non défini. Vous ne pouvez pas non plus dire qu’une valeur indéfinie est supérieure ou inférieure à une autre valeur indé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 deux NaN mais ils ne sont pas équivalents même s’ils renvoient la même valeur. Par conséquent, le fait d’avoir une égalité renvoyant toujours faux lorsque l’on traite avec NaN est le comportement souhaité.

Jeter encore une autre analogie. Si je vous remets deux boîtes et vous dit qu’aucune d’entre elles ne contient de pomme, me direz-vous que les boîtes contiennent la même chose?

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

Dans 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 ajouts ∞ + (-∞), (-∞) + ∞ et les soustractions équivalentes.
  • Appliquer une fonction à des arguments en dehors de son domaine, y compris 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.

Comme 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 soit logique.

Je ne connais pas le raisonnement de 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ébordent. Quatre relations mutuellement exclusives sont possibles: inférieur, égal, supérieur ou non 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. ”

Cela semble étrange car la plupart des environnements de programmation qui autorisent les NaN n’autorisent pas non plus la logique à 3 valeurs. Si vous lancez une logique à 3 valeurs dans le mixage, cela devient cohérent:

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

Même .NET ne fournit pas un bool? operator==(double v1, double v2) bool? operator==(double v1, double v2) , vous êtes toujours bloqué avec le résultat idiot (NaN == NaN) = false .

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

C’est un peu comme l’arithmétique en SQL avec null opérandes null : ils donnent tous la valeur 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é dans un sens numérique.

La réponse trop simplifiée est qu’un NaN n’a pas de valeur numérique, donc il n’y a rien à comparer à autre chose.

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

Bien que je sois d’accord sur le fait que les comparaisons de NaN avec un nombre réel ne devraient pas être ordonnées, je pense qu’il est juste de comparer NaN avec lui-même. Comment, par exemple, découvrir 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 binary, une valeur négative serait un NaN silencieux et toute valeur positive serait être un signal NaN. Zéro bien sûr est réservé à l’infini et la comparaison serait désordonnée. L’alignement MSB permettrait la comparaison directe des signaux, même à partir de différents formats binarys. Deux NaN avec le même ensemble de signaux seraient donc équivalents et donneraient un sens à l’égalité.

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

Et gardez à l’esprit /a/ !== /a/ telle implicite est également visible en dehors des erreurs, par exemple dans le contexte des expressions régulières, cela signifie /a/ !== /a/ qui est juste du sucre de syntaxe pour le new RegExp('a') !== new RegExp('a')

Parce que les mathématiques sont le domaine où les nombres “existent”. En informatique, vous devez initialiser ces chiffres et conserver leur état en fonction de vos besoins. A ces temps anciens, l’initialisation de la mémoire fonctionnait de la façon dont vous ne pouviez jamais compter. Vous ne pourriez jamais vous permettre de penser à cela “oh, cela serait initialisé avec 0xCD tout le temps, mon algo ne sera pas cassé” .

Vous avez donc besoin d’un solvant sans mélange approprié, suffisamment collant pour que votre algorithme ne soit pas aspiré et cassé. Les bons algorithmes impliquant des nombres vont pour la plupart travailler avec des relations, et ces relations if () seront omises.

Ceci est juste de la graisse que vous pouvez mettre dans une nouvelle variable à la création, au lieu de programmer un enfer aléatoire dans 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 twig une par une. Encore une fois, la règle “toujours faux” aide beaucoup à cela.

Pour moi, le moyen le plus simple de l’expliquer est:

J’ai quelque chose et si ce n’est pas une pomme, 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 d’une valeur quelconque (sauf un nombre).

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

Très courte réponse:

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

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