it-swarm.com.de

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

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

Ich vermute, dies vereinfacht numerische Berechnungen in gewisser Weise, aber ich konnte keinen explizit genannten Grund finden, auch nicht in den Vorlesungshinweisen zum Status von IEEE 754 von Kahan, die andere Entwurfsentscheidungen im Detail diskutieren.

Dieses abweichende Verhalten verursacht Probleme bei der einfachen Datenverarbeitung. Beispielsweise beim Sortieren einer Liste von Datensätzen w.r.t. In einem echten Feld in einem C-Programm muss ich zusätzlichen Code schreiben, um NaN als maximales Element zu behandeln. Andernfalls könnte der Sortieralgorithmus verwirrt werden.

Edit: Die bisherigen Antworten sind alle der Meinung, dass ein Vergleich von NaNs sinnlos ist.

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

Daher ist die Wahl zwischen Wahr und Falsch für Vergleiche meiner Meinung nach willkürlich, und es wäre für die allgemeine Datenverarbeitung von Vorteil, wenn sie den üblichen Gesetzen gehorchen. (Reflexivität von ==, Trichotomie von <, ==, >), .__, damit Datenstrukturen, die sich auf diese Gesetze stützen, verwirrt werden.

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

Edit 2: Ich glaube, ich verstehe jetzt, warum es eine schlechte Idee wäre, NaN maximal zu machen, es würde die Berechnung der oberen Grenzen stören.

NaN! = NaN kann wünschenswert sein, um das Erkennen von Konvergenz in einer Schleife wie z

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

dies sollte jedoch besser durch Vergleich der absoluten Differenz mit einem kleinen Grenzwert geschrieben werden. Dies ist meiner Meinung nach ein relativ schwaches Argument für das Brechen der Reflexivität bei NaN.

223
starblue

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

Zunächst sind Fließkommazahlen keine reellen Zahlen, und die Fließkomma-Arithmetik erfüllt die Axiome der reellen Arithmetik nicht. Die Trichotomie ist nicht die einzige Eigenschaft der echten Arithmetik, die weder für Schwimmer noch für die wichtigste gilt. Zum Beispiel:

  • Die Zugabe ist nicht assoziativ.
  • Das Verteilungsgesetz gilt nicht.
  • Es gibt Fließkommazahlen ohne Umkehrung.

Ich könnte weitermachen Es ist nicht möglich, einen arithmetischen Typ mit fester Größe anzugeben, der all der Eigenschaften der reellen Arithmetik erfüllt, die wir kennen und lieben. Das 754-Komitee muss entscheiden, 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 echten 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 y kleiner als x ist. Wenn y NaN ist, ist es nicht weniger als jeder Gleitkommawert x, daher ist die Antwort notwendigerweise falsch.

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

Vier sich gegenseitig ausschließende Beziehungen sind möglich: weniger 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.

Was das Schreiben von extra Code für den Umgang mit NaNs angeht, 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 zusätzlicher Code erforderlich. Dies ist jedoch ein geringer Preis, der für die Bequemlichkeit des algebraischen Abschlusses für die Fließkomma-Arithmetik erforderlich ist.


Nachtrag: Viele Kommentatoren haben argumentiert, dass es sinnvoller wäre, die Reflexivität der Gleichheit und der Trichotomie zu bewahren, da die Annahme von NaN! = NaN kein bekanntes Axiom zu enthalten scheint. Ich gebe zu, etwas Verständnis für diesen Standpunkt zu haben, also dachte ich, ich würde diese Antwort noch einmal besprechen und etwas mehr Kontext geben.

Wenn ich mit Kahan spreche, verstehe ich, dass NaN! = NaN aus zwei pragmatischen Überlegungen entstand:

  • Dass x == y sollte, wann immer möglich, x - y == 0 äquivalent sein (dies ist nicht nur ein Satz von realer Arithmetik, sondern macht die Hardwareimplementierung des Vergleichs platzsparender, was zum Zeitpunkt der Entwicklung der Norm von größter Bedeutung war. Beachten Sie jedoch, dass dies verletzt wird für x = y = unendlich ist es also kein guter Grund, es hätte vernünftigerweise zu (x - y == 0) or (x and y are both NaN)) gebogen werden können.

  • Wichtiger war noch, dass es zu der Zeit kein isnan( )-Prädikat gab, als NaN in der 8087-Arithmetik formalisiert wurde; Es war notwendig, Programmierern ein praktisches und effizientes Mittel zur Erkennung von NaN-Werten bereitzustellen, das nicht von Programmiersprachen abhängt, die etwas wie isnan( ) bieten, was viele Jahre dauern kann. Ich zitiere Kahans eigenes Schreiben zu diesem Thema:

Wenn es keine Möglichkeit gibt, NaNs loszuwerden, wären sie auf CRAYs ebenso unbrauchbar wie Indefinites; Sobald eine gefunden wurde, würde die Berechnung am besten gestoppt und nicht auf unbestimmte Zeit bis zu einer unbestimmten Schlussfolgerung fortgesetzt werden. Aus diesem Grund müssen einige Operationen an 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; Dies ist die einzige einfache Ausnahme zwischen NaNs und Zahlen in Sprachen, denen ein Wort für NaN und ein Prädikat IsNaN (x) fehlen.

Beachten Sie, dass dies auch die Logik ist, die die Rückgabe von „Not-A-Boolean“ ausschließt. Vielleicht war dieser Pragmatismus falsch platziert, und der Standard hätte isnan( ) verlangen müssen, aber das hätte NaNs Verwendung für mehrere Jahre nahezu unmöglich gemacht, während die Welt auf die Einführung von Programmiersprachen wartete. Ich bin nicht überzeugt, dass dies ein vernünftiger Kompromiss gewesen wäre.

Um ehrlich zu sein: Das Ergebnis von NaN == NaN wird sich jetzt nicht ändern. Besser lernen, damit zu leben, als sich im Internet zu beschweren. Wenn Sie behaupten möchten, dass eine für Container geeignete Auftragsbeziehung auch vorhanden sein sollte, würde ich empfehlen, dass Ihre bevorzugte Programmiersprache das in IEEE-754 (2008) standardisierte Prädikat totalOrder implementiert. Die Tatsache, dass Kahans Bedenken, die den gegenwärtigen Stand der Dinge motiviert haben, noch nicht.

453
Stephen Canon

NaN kann als undefinierter Zustand/Nummer betrachtet werden. ähnlich wie das Konzept von 0/0 undefined oder sqrt (-3) (im reellen Zahlensystem, in dem der Gleitkommazahl lebt). 

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

Dieses Verhalten ist auch in den Fällen von Vorteil, in denen Sie sqrt (-3) mit sqrt (-2) vergleichen. Beide würden NaN zurückgeben, sind aber nicht gleichwertig, obwohl sie denselben Wert zurückgeben. Daher ist es das gewünschte Verhalten, wenn Gleichheit im Umgang mit NaN immer falsch zurückgegeben wird.

45
Chris

Noch eine Analogie einzufügen. Wenn ich Ihnen zwei Boxen übergebe und Ihnen sage, dass keiner von ihnen einen Apple enthält, würden Sie mir dann sagen, dass die Boxen dasselbe enthalten?

NaN enthält keine Informationen darüber, was etwas ist, was es nicht ist. Daher können diese Elemente niemals definitiv als gleich bezeichnet werden.

31
Jack Ryan

Im Wikipedia-Artikel zu NaN können die folgenden Praktiken NaNs verursachen:

  • Alle mathematischen Operationen> mit einer 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, des Tangens eines ungeraden Vielfachen von 90 Grad (oder π/2 Radiant) 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, durch welche dieser Operationen das NaN erstellt wurde, gibt es keinen sinnvollen Vergleich.

12
Stefan Rusek

Ich kenne die Entwurfsgrundsätze nicht, aber hier ein Auszug aus dem IEEE 754-1985-Standard:

"Es muss möglich sein, Gleitkommazahlen in allen unterstützten Formaten zu vergleichen, auch wenn die Formate der Operanden unterschiedlich sind. Vergleiche sind genau und niemals Überlauf oder Unterlauf. Vier sich gegenseitig ausschließende Beziehungen sind möglich: weniger als, gleich, größer als und ungeordnet Der letzte Fall tritt auf, wenn mindestens ein Operand NaN ist. Jedes NaN soll ungeordnet mit allem, einschließlich sich selbst, vergleichen. "

4
Rick Regan

Es sieht nur merkwürdig aus, da die meisten Programmierumgebungen, die NaNs erlauben, keine 3-wertige Logik zulassen. Wenn Sie eine 3-wertige Logik in den Mix werfen, wird diese konsistent:

  • (2,7 = 2,7) = wahr
  • (2,7 = 2,6) = falsch
  • (2,7 == NaN) = unbekannt
  • (NaN == NaN) = unbekannt

Sogar .NET bietet keinen bool? operator==(double v1, double v2)-Operator, so dass Sie immer noch mit dem blöden (NaN == NaN) = false-Ergebnis beschäftigt sind.

2

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 eine Arithmetik in SQL mit null-Operanden: Sie alle führen zu null.

Die Vergleiche für Fließkommazahlen vergleichen numerische Werte. Sie können daher nicht für nicht numerische Werte verwendet werden. NaN kann daher nicht numerisch verglichen werden.

1
Daren Thomas

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

Sie könnten erwägen, Ihre NaNs zu testen und durch + INF zu ersetzen, wenn Sie möchten, dass sie sich wie + INF verhalten.

1
David R Tribble

Ich stimme zu, dass der Vergleich von NaN mit einer reellen Zahl ungeordnet sein sollte, aber ich denke, es gibt nur einen Grund, NaN mit sich selbst zu vergleichen. Wie entdeckt man zum Beispiel den Unterschied zwischen der Signalisierung von NaNs und ruhigen NaNs? Wenn wir uns die Signale als einen Satz boolescher Werte vorstellen (d. H. Einen Bitvektor), könnte man durchaus fragen, ob die Bitvektoren gleich oder verschieden sind, und die Sätze entsprechend anordnen. Wenn zum Beispiel beim Dekodieren eines maximal verzerrten Exponenten der Signifikand so verschoben wird, dass das höchstwertige Bit des Signifikanden auf das höchstwertige Bit des Binärformats ausgerichtet wird, wäre ein negativer Wert eine ruhige NaN und jeder positive Wert wäre ein Zeichen NaN sein. Null ist natürlich für das Unendliche reserviert und der Vergleich wäre ungeordnet. Das MSB-Alignment würde den direkten Vergleich von Signalen auch aus verschiedenen Binärformaten ermöglichen. Zwei NaNs mit dem gleichen Satz von Signalen wären daher gleichwertig und bedeuten Gleichheit.

0

NaN ist eine implizite neue Instanz (eines speziellen Laufzeitfehlers). Das bedeutet NaN !== NaN aus demselben Grund wie new Error !== new Error

Und bedenken Sie, dass solche Implizite auch außerhalb von Fehlern gesehen werden, zum Beispiel im Zusammenhang mit regulären Ausdrücken bedeutet /a/ !== /a/, was nur Syntaxzucker für new RegExp('a') !== new RegExp('a')

0