Was ist der Grund für alle Vergleiche, die für IEEE754-NaN-Werte falsch zurückgeben?

267

Warum verhalten sich Vergleiche von NaN-Werten anders als alle anderen Werte? Das heißt, alle Vergleiche mit den Operatoren ==, <=,> =, <,>, bei denen einer oder beide Werte NaN sind, geben entgegen dem Verhalten aller anderen Werte false zurück.

Ich nehme an, dies vereinfacht numerische Berechnungen in gewisser Weise, aber ich konnte keinen explizit angegebenen Grund finden, nicht einmal in den Lecture Notes zum Status von IEEE 754 von Kahan, in denen andere Entwurfsentscheidungen ausführlich erörtert werden.

Dieses abweichende Verhalten verursacht Probleme bei der einfachen Datenverarbeitung. Wenn ich zum Beispiel eine Liste von Datensätzen anhand eines reellen Feldes in einem C-Programm sortiere, muss ich zusätzlichen Code schreiben, um NaN als maximales Element zu behandeln, da sonst der Sortieralgorithmus verwirrt werden kann.

Bearbeiten: Die bisherigen Antworten argumentieren alle, dass es bedeutungslos ist, NaNs zu vergleichen.

Ich stimme zu, aber das bedeutet nicht, dass die richtige Antwort falsch ist, sondern es wäre ein Not-a-Boolean (NaB), der zum Glück nicht existiert.

Die Wahl der Rückgabe von wahr oder falsch für Vergleiche ist meines Erachtens willkürlich, und für die allgemeine Datenverarbeitung wäre es vorteilhaft, wenn die üblichen Gesetze (Reflexivität von ==, Trichotomie von <, ==,>) eingehalten würden, damit keine Datenstrukturen entstehen die sich auf diese Gesetze stützen, werden verwirrt.

Ich bitte um einen konkreten Vorteil, diese Gesetze zu brechen, nicht nur um philosophisches Denken.

Edit 2: Ich denke, ich verstehe jetzt, warum es eine schlechte Idee wäre, NaN maximal zu machen, es würde die Berechnung der Obergrenzen durcheinander bringen.

NaN! = NaN kann wünschenswert sein, um zu vermeiden, dass Konvergenz in einer Schleife wie z

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

Dies sollte jedoch besser geschrieben werden, indem die absolute Differenz mit einer kleinen Grenze verglichen wird. Meiner Meinung nach ist dies ein relativ schwaches Argument, um die Reflexivität bei NaN zu brechen.

Sternenblau
quelle
2
Sobald ein NaN in die Berechnung eingegangen ist, wird es normalerweise nie mehr verlassen, sodass Ihr Konvergenztest zu einer Endlosschleife wird. Es ist normalerweise vorzuziehen, den Fehler bei der Konvergenz zur aufrufenden Routine zu melden, möglicherweise durch Rückgabe von NaN. Somit würde die Schleifenstruktur typischerweise so etwas wie das while (fabs(x - oldX) > threshold)Verlassen der Schleife werden, wenn Konvergenz auftritt oder ein NaN in die Berechnung eintritt. Der Nachweis des NaN und eines geeigneten Mittels würde dann außerhalb der Schleife erfolgen.
Stephen Canon
1
Wenn NaN das minimale Element der Reihenfolge wäre, würde die while-Schleife immer noch funktionieren.
Starblue
2
Denkanstöße: grouper.ieee.org/groups/1788/email/pdfmPSi1DgZZf.pdf Seite 10
starblue

Antworten:

535

Ich war Mitglied des IEEE-754-Komitees. Ich werde versuchen, die Dinge ein wenig zu klären.

Zunächst einmal sind Gleitkommazahlen keine reellen Zahlen, und Gleitkomma-Arithmetik erfüllt nicht die Axiome der reellen Arithmetik. Die Trichotomie ist nicht die einzige Eigenschaft der realen Arithmetik, die weder für Floats noch für die wichtigste gilt. Beispielsweise:

  • Addition ist nicht assoziativ.
  • Das Verteilungsgesetz gilt nicht.
  • Es gibt Gleitkommazahlen ohne Inversen.

Ich könnte weitermachen Es ist nicht möglich, einen arithmetischen Typ fester Größe anzugeben, der alle Eigenschaften der realen Arithmetik erfüllt , die wir kennen und lieben. Das 754-Komitee muss beschließen, einige von ihnen zu biegen oder zu brechen. Dies wird von einigen ziemlich einfachen Prinzipien geleitet:

  1. Wenn wir können, passen wir das Verhalten der realen Arithmetik an.
  2. Wenn wir nicht können, versuchen wir, die Verstöße so vorhersehbar und so einfach wie möglich zu diagnostizieren.

In Bezug auf Ihren Kommentar "Das bedeutet nicht, dass die richtige Antwort falsch ist" ist dies falsch. Das Prädikat (y < x)fragt, ob yes kleiner als ist x. Wenn yNaN ist, ist es nicht kleiner als ein Gleitkommawert x, daher ist die Antwort notwendigerweise falsch.

Ich erwähnte, dass die Trichotomie nicht für Gleitkommawerte gilt. Es gibt jedoch eine ähnliche Eigenschaft, die gilt. Abschnitt 5.11, Absatz 2 der Norm 754-2008:

Es sind vier sich gegenseitig ausschließende Beziehungen möglich: kleiner als, gleich, größer als und ungeordnet. Der letzte Fall tritt auf, wenn mindestens ein Operand NaN ist. Jedes NaN soll ungeordnet mit allem vergleichen, auch mit sich selbst.

Wenn Sie zusätzlichen Code für den Umgang mit NaNs schreiben, ist es normalerweise möglich (wenn auch nicht immer einfach), Ihren Code so zu strukturieren, dass NaNs ordnungsgemäß durchfallen. Dies ist jedoch nicht immer der Fall. Wenn dies nicht der Fall ist, ist möglicherweise ein zusätzlicher Code erforderlich, aber dies ist ein geringer Preis für die Bequemlichkeit, die das algebraische Schließen der Gleitkomma-Arithmetik gebracht hat.


Nachtrag: Viele Kommentatoren haben argumentiert, dass es sinnvoller wäre, die Reflexivität von Gleichheit und Trichotomie zu bewahren, da die Übernahme von NaN! = NaN kein bekanntes Axiom zu bewahren scheint. Ich gebe zu, Sympathie für diesen Standpunkt zu haben, deshalb dachte ich, ich würde diese Antwort noch einmal überdenken und ein bisschen mehr Kontext bieten.

Mein Verständnis aus dem Gespräch mit Kahan ist, dass NaN! = NaN aus zwei pragmatischen Überlegungen hervorgegangen ist:

  • Das x == ysollte nicht höher sein , x - y == 0wenn möglich (über einen Satz von realer Arithmetik zu sein, das macht Hardware - Implementierung des Vergleiches platzsparender, die zu der Zeit von größter Bedeutung war der Standard entwickelt wurde - jedoch zu beachten, dass dies für x verletzt = y = unendlich, es ist also kein guter Grund für sich; es hätte vernünftigerweise dazu neigen können (x - y == 0) or (x and y are both NaN)).

  • Noch wichtiger ist, dass es isnan( )zum Zeitpunkt der Formalisierung von NaN in der 8087-Arithmetik kein Prädikat gab . Es war notwendig, Programmierern ein bequemes und effizientes Mittel zur Erkennung von NaN-Werten zur Verfügung zu stellen, die nicht von Programmiersprachen abhingen, die so etwas wie isnan( )viele Jahre dauern konnten. Ich zitiere Kahans eigenes Schreiben zu diesem Thema:

Wenn es keine Möglichkeit gäbe, NaNs loszuwerden, wären sie genauso nutzlos wie Indefinites on CRAYs. Sobald eine gefunden wurde, sollte die Berechnung am besten gestoppt und nicht auf unbestimmte Zeit bis zu einer unbestimmten Schlussfolgerung fortgesetzt werden. Aus diesem Grund müssen einige Operationen mit NaNs Nicht-NaN-Ergebnisse liefern. Welche Operationen? … Die Ausnahmen sind C-Prädikate „x == x“ und „x! = X“, die für jede unendliche oder endliche Zahl x jeweils 1 und 0 sind, aber umgekehrt, wenn x keine Zahl (NaN) ist; Diese bieten die einzige einfache, nicht außergewöhnliche Unterscheidung zwischen NaNs und Zahlen in Sprachen, denen ein Wort für NaN und ein Prädikat IsNaN (x) fehlt.

Beachten Sie, dass dies auch die Logik ist, die die Rückgabe eines „Not-A-Boolean“ ausschließt. Vielleicht war dieser Pragmatismus fehl am Platz, und der Standard hätte es erfordern müssen isnan( ), aber das hätte es fast unmöglich gemacht, NaN mehrere Jahre lang effizient und bequem zu nutzen, während die Welt auf die Einführung der Programmiersprache wartete. Ich bin nicht davon überzeugt, dass dies ein vernünftiger Kompromiss gewesen wäre.

Um ehrlich zu sein: Das Ergebnis von NaN == NaN wird sich jetzt nicht ändern. Es ist besser zu lernen, damit zu leben, als sich im Internet zu beschweren. Wenn Sie , dass eine Bestellung Beziehung geeignet für Behälter argumentieren will , sollte auch vorhanden ist , würde ich empfehlen , dafür ein, dass Ihre Lieblings - Programmiersprache die Umsetzung totalOrderPrädikat standardisiert in IEEE-754 (2008). Die Tatsache, dass es noch nicht für die Gültigkeit von Kahans Besorgnis spricht, die den aktuellen Stand der Dinge motiviert hat.

Stephen Canon
quelle
16
Ich habe Ihre Punkte 1 und 2 gelesen. Dann habe ich festgestellt, dass NaN in der realen Arithmetik (erweitert, um NaN überhaupt zuzulassen) gleich sich selbst ist - einfach, weil in der Mathematik jede Entität ausnahmslos gleich sich selbst ist. Jetzt bin ich verwirrt: Warum stimmte IEEE nicht mit dem Verhalten der realen Arithmetik überein, was NaN == NaN machen würde? Was vermisse ich?
Max
12
Einverstanden; Die Nichtreflexivität von NaNs hat für Sprachen wie Python mit ihrer auf Gleichheit basierenden Containment-Semantik kein Ende des Schmerzes geschaffen. Sie möchten wirklich nicht, dass Gleichheit keine Äquivalenzbeziehung ist, wenn Sie versuchen, Container darauf aufzubauen. Und zwei getrennte Begriffe von Gleichheit zu haben, ist auch für eine Sprache, die leicht zu lernen sein soll, keine besonders gute Option. Das Ergebnis (im Fall von Python) ist ein unangenehm fragiler Kompromiss zwischen der Einhaltung von IEEE 754 und der nicht zu kaputten Containment-Semantik. Glücklicherweise ist es selten, NaNs in Behälter zu füllen.
Mark Dickinson
5
Einige schöne Beobachtungen hier: bertrandmeyer.com/2010/02/06/…
Mark Dickinson
6
@StephenCanon: Inwiefern wäre (0/0) == (+ INF) + (-INF) unsinniger als zu haben 1f/3f == 10000001f/30000002f? Wenn Gleitkommawerte als Äquivalenzklassen betrachtet werden, a=bbedeutet dies nicht "Die Berechnungen, die sich ergaben aund bbei unendlicher Genauigkeit identische Ergebnisse liefern würden", sondern "Was bekannt ist, astimmt mit dem überein, was bekannt ist." b". Ich bin gespannt, ob Sie Beispiele für Code kennen, bei denen "Nan! = NaN" die Dinge einfacher macht als sonst?
Supercat
5
Wenn Sie NaN == NaN und kein isNaN hätten, könnten Sie theoretisch immer noch mit NaN testen !(x < 0 || x == 0 || x > 0), aber es wäre langsamer und ungeschickter gewesen als x != x.
user2357112 unterstützt Monica
50

NaN kann als undefinierter Zustand / undefinierte Zahl betrachtet werden. ähnlich dem Konzept, dass 0/0 undefiniert oder sqrt (-3) ist (im reellen Zahlensystem, in dem der Gleitkomma lebt).

NaN wird als eine Art Platzhalter für diesen undefinierten Zustand verwendet. Mathematisch gesehen ist undefiniert nicht gleich undefiniert. Sie können auch nicht sagen, dass ein undefinierter Wert größer oder kleiner als ein anderer undefinierter Wert ist. Daher geben alle Vergleiche false zurück.

Dieses Verhalten ist auch in den Fällen vorteilhaft, in denen Sie sqrt (-3) mit sqrt (-2) vergleichen. Sie würden beide NaN zurückgeben, aber sie sind nicht äquivalent, obwohl sie den gleichen Wert zurückgeben. Daher ist es das gewünschte Verhalten, wenn Gleichheit beim Umgang mit NaN immer falsch ist.

Chris
quelle
5
Was sollte das Ergebnis von sqrt (1.00000000000000022) == sqrt (1.0) sein? Wie wäre es mit (1E308 + 1E308-1E308-1E308-1E308) == (1E308 + 1E308)? Außerdem geben nur fünf der sechs Vergleiche false zurück. Der !=Operator gibt true zurück. Wenn NaN==NaNund NaN!=NaNbeide false zurückgeben, kann Code, der x und y vergleicht, auswählen, was passieren soll, wenn beide Operanden NaN sind, indem entweder ==oder ausgewählt wird !=.
Supercat
38

Noch eine Analogie einbringen. Wenn ich Ihnen zwei Schachteln reiche und Ihnen sage, dass keine von ihnen einen Apfel enthält, würden Sie mir dann sagen, dass die Schachteln dasselbe enthalten?

NaN enthält keine Informationen darüber, was etwas ist, nur was es nicht ist. Daher kann man niemals definitiv sagen, dass diese Elemente gleich sind.

Jack Ryan
quelle
6
Alle leeren Mengen sind per Definition gleich.
MSalters
28
Es ist NICHT bekannt, dass die Felder, die Sie erhalten, leer sind.
John Smith
7
Würden Sie mir sagen, dass die Kisten nicht dasselbe enthalten? Ich kann die Gründe dafür verstehen (NaN==Nan)==false. Was ich nicht verstehe, ist die Begründung dafür (Nan!=Nan)==true.
Supercat
3
Ich nehme an, NaN! = NaN ist wahr, weil x! = Y definiert ist als! (X == y). Zugegeben, ich weiß nicht, ob die IEEE-Spezifikation dies so definiert.
Kef Schecter
6
Aber in dieser Analogie, wenn Sie mir eine Schachtel gaben, sagten, dass sie keine Äpfel enthielt, und mich dann fragten, ob sie sich selbst gleich sei, erwarten Sie, dass ich nein sage? Denn das würde ich laut IEEE sagen.
Semikolon
12

Aus dem Wikipedia-Artikel über NaN geht hervor , dass die folgenden Praktiken NaNs verursachen können:

  • Alle mathematischen Operationen> mit einem NaN als mindestens einem Operanden
  • Die Unterteilungen 0/0, ∞ / ∞, ∞ / -∞, -∞ / ∞ und -∞ / -∞
  • Die Multiplikationen 0 × ∞ und 0 × -∞
  • Die Additionen ∞ + (-∞), (-∞) + ∞ und äquivalente Subtraktionen.
  • Anwenden einer Funktion auf Argumente außerhalb ihres Bereichs, einschließlich der Quadratwurzel einer negativen Zahl, des Logarithmus einer negativen Zahl, der Tangente eines ungeraden Vielfachen von 90 Grad (oder π / 2 Bogenmaß) oder des inversen Sinus oder Cosinus einer Zahl, die kleiner als -1 oder größer als +1 ist.

Da es keine Möglichkeit gibt zu wissen, welche dieser Operationen das NaN erzeugt haben, gibt es keine Möglichkeit, sie zu vergleichen, was Sinn macht.

Stefan Rusek
quelle
3
Selbst wenn Sie wüssten, welche Operation durchgeführt wird, würde dies nicht helfen. Ich kann eine beliebige Anzahl von Formeln konstruieren, die irgendwann auf 0/0 gehen und an diesem Punkt (wenn wir Kontinuität annehmen) genau definierte und unterschiedliche Werte haben.
David Thornley
4

Ich kenne die Designgründe nicht, aber hier ist ein Auszug aus dem IEEE 754-1985-Standard:

"Es soll möglich sein, Gleitkommazahlen in allen unterstützten Formaten zu vergleichen, auch wenn sich die Formate der Operanden unterscheiden. Vergleiche sind exakt und laufen weder über noch unter. Vier sich gegenseitig ausschließende Beziehungen sind möglich: kleiner als, gleich, größer als und ungeordnet Der letzte Fall tritt auf, wenn mindestens ein Operand NaN ist. Jedes NaN soll ungeordnet mit allem vergleichen, einschließlich sich selbst. "

Rick Regan
quelle
2

Es sieht nur merkwürdig aus, weil die meisten Programmierumgebungen, die NaNs zulassen, keine 3-wertige Logik zulassen. Wenn Sie eine 3-wertige Logik in die Mischung einbringen, wird sie konsistent:

  • (2.7 == 2.7) = wahr
  • (2.7 == 2.6) = falsch
  • (2.7 == NaN) = unbekannt
  • (NaN == NaN) = unbekannt

Selbst .NET bietet keinen bool? operator==(double v1, double v2)Operator, sodass Sie immer noch am dummen (NaN == NaN) = falseErgebnis festhalten .

Christian Hayter
quelle
1

Ich vermute, dass NaN (Not A Number) genau das bedeutet: Dies ist keine Zahl, und daher macht ein Vergleich keinen Sinn.

Es ist ein bisschen wie Arithmetik in SQL mit nullOperanden: Sie alle führen zu null.

Die Vergleiche für Gleitkommazahlen vergleichen numerische Werte. Daher können sie nicht für nicht numerische Werte verwendet werden. NaN kann daher nicht im numerischen Sinne verglichen werden.

Daren Thomas
quelle
3
"Dies ist keine Zahl, und daher macht ein Vergleich keinen Sinn." Zeichenfolgen sind keine Zahlen, aber ein Vergleich ist sinnvoll.
Jason
2
Ja, der Vergleich eines Strings mit einem String ist sinnvoll. Ein Vergleich einer Schnur mit beispielsweise Äpfeln macht jedoch wenig Sinn. Ist es sinnvoll, Äpfel und Birnen zu vergleichen, da sie keine Zahlen sind? Welches ist größer?
Daren Thomas
@DarenThomas: In SQL ist weder "IF NULL = NULL THEN FOO;" noch "WENN Null <> Null DANN FOO ANRUFEN;" [oder was auch immer die Syntax ist] wird ausgeführt FOO. Damit NaN gleichwertig ist, if (NaN != NaN) foo();sollte es nicht ausgeführt werden foo, aber es tut es.
Supercat
1

Die stark vereinfachte Antwort lautet, dass ein NaN keinen numerischen Wert hat, sodass nichts zu vergleichen ist.

Sie können Ihre NaNs testen und durch + INF ersetzen, wenn Sie möchten, dass sie sich wie + INF verhalten.

David R Tribble
quelle
0

Ich stimme zwar zu, dass Vergleiche von NaN mit einer reellen Zahl ungeordnet sein sollten, aber ich denke, es gibt nur Grund, NaN mit sich selbst zu vergleichen. Wie entdeckt man zum Beispiel den Unterschied zwischen signalisierenden NaNs und leisen NaNs? Wenn wir uns die Signale als eine Menge von Booleschen Werten (dh einen Bitvektor) vorstellen, könnte man sich fragen, ob die Bitvektoren gleich oder verschieden sind, und die Mengen entsprechend anordnen. Wenn beispielsweise beim Decodieren eines maximal vorgespannten Exponenten der Signifikand nach links verschoben würde, um das höchstwertige Bit des Signifikanten auf dem höchstwertigen Bit des Binärformats auszurichten, wäre ein negativer Wert ein leises NaN und jeder positive Wert wäre ein signalisierendes NaN sein. Null ist natürlich für unendlich reserviert und der Vergleich wäre ungeordnet. Die MSB-Ausrichtung würde den direkten Vergleich von Signalen auch aus verschiedenen Binärformaten ermöglichen. Zwei NaNs mit demselben Satz von Signalen wären daher äquivalent und würden der Gleichheit Bedeutung verleihen.

Patrick Campbell
quelle
-1

Für mich ist der einfachste Weg, dies zu erklären:

Ich habe etwas und wenn es kein Apfel ist, ist es dann eine Orange?

Sie können NaN nicht mit etwas anderem vergleichen (auch nicht mit sich selbst), da es keinen Wert hat. Es kann auch ein beliebiger Wert sein (außer einer Zahl).

Ich habe etwas und wenn es nicht gleich einer Zahl ist, ist es dann eine Zeichenfolge?

Halil Tevfik
quelle
Was meinst du mit "es kann ein beliebiger Wert außer einer Zahl sein"?
Puschkin
-2

Weil Mathematik das Feld ist, in dem Zahlen "nur existieren". Bei der Berechnung müssen Sie initialisieren , diese Zahlen und halten ihren Zustand nach Ihren Bedürfnissen. In jenen alten Tagen funktionierte die Speicherinitialisierung so, wie man sich nie verlassen konnte. Sie könnten sich niemals erlauben, darüber nachzudenken "Oh, das würde die ganze Zeit mit 0xCD initialisiert werden, mein Algo wird nicht kaputt gehen" .

Sie benötigen also ein geeignetes nicht mischendes Lösungsmittel, das klebrig genug ist , damit Ihr Algorithmus nicht angesaugt und beschädigt wird. Gute Algorithmen mit Zahlen funktionieren meistens mit Relationen, und solche if () -Relationen werden weggelassen.

Dies ist nur Fett, das Sie bei der Erstellung in eine neue Variable einfügen können, anstatt eine zufällige Hölle aus dem Computerspeicher zu programmieren. Und Ihr Algorithmus, was auch immer er ist, wird nicht kaputt gehen.

Wenn Sie dann immer noch plötzlich feststellen, dass Ihr Algorithmus NaNs produziert, können Sie diese bereinigen und nacheinander in jeden Zweig schauen. Auch hier hilft die "immer falsche" Regel sehr.

Sanaris
quelle
-4

Sehr kurze Antwort:

Denn folgendes: nan / nan = 1 darf NICHT halten. Sonst inf/infwäre 1.

(Daher nankann nicht gleich sein nan. Was >oder <betrifft, wenn nanwir eine Ordnungsrelation in einer Menge respektieren würden, die die archimedische Eigenschaft erfüllt, hätten wir wieder nan / nan = 1an der Grenze).

SeF
quelle
2
Nein, das macht keinen Sinn. Wir haben inf = infund inf / inf = nanwerden es nan = nanauch nicht verhindern nan / nan = nan.
Starblue
@starblue Du meinst nan / nan = 1? Wie auch immer ... Ihre Argumentation macht Sinn, wenn inf und nan genau wie alle anderen Zahlen wären. Das ist nicht der Fall. Der Grund dafür inf/infmuss sein nan(oder eine unbestimmte Form in der Mathematik) und ist nicht 1subtiler als eine einfache algebraische Manipulation (siehe Satz von De L'Hospital).
SeF