Testen der Gleichheit zweier Floats: Realistisches Beispiel

8

Wann ist es in der Programmierung normalerweise sinnvoll, die Gleichheit zweier Gleitkommazahlen zu testen?

dh

a == b 

wo beide a & b schwimmt.

Mein naiver Eindruck ist, dass man den Unterschied immer gegen ein Toleranz-Epsilon testen würde.

Liege ich falsch? Kann es in bestimmten Kontexten sinnvoll sein, die Gleichheit von Floats zu testen?

Irgendwelche Beispiele aus der Wildnis? dh von echten Codebasen oder Anwendungen da draußen auf Git usw.

PS. Ich gehe implizit davon aus, dass die Verwendung des Gleichheitsoperators für Floats in einigen Kontexten tatsächlich sinnvoll ist . Warum sollten die meisten Programmiersprachen dies sonst zulassen?

neugierig_katze
quelle
1
Da es sich um praktische Probleme mit numerischen Algorithmen handelt, migriere ich zu Computational Science .
Es könnte hilfreich sein zu wissen, welches Problem Sie lösen möchten, indem Sie diese spezielle Frage stellen. meta.stackexchange.com/q/66377
Kirill
1
@Krill Das Problem ist die Neugier. Ich kenne den Rat, wann ich es NICHT verwenden soll. Wenn der Bediener jedoch weiterhin zugelassen ist, muss es Fälle geben, in denen die Verwendung tatsächlich korrekt ist. Aber diese Fälle waren nicht offensichtlich. Also wollte ich es wissen. Und die Antworten bringen einige gute Beispiele hervor.
neugierige Katze

Antworten:

5

Mein naiver Eindruck ist, dass man den Unterschied immer gegen ein Toleranz-Epsilon testen würde.

Eine nicht naive Implementierung dieser Idee sollte wahrscheinlich den Gleichheitsvergleichsoperator nutzen, um die wichtigen Sonderfälle zu behandeln, die der IEEE 754- Standard in Betracht zieht (Unendlichkeiten, denormalisierte Zahlen ...).

Werfen Sie einen Blick auf Wie soll ich einen Gleitkomma-Vergleich durchführen? ::

...

if (a == b)  // shortcut, handles infinities
  return true;

if (a == 0 || b == 0 || diff < Float.MIN_NORMAL) {
  // a or b is zero or both are extremely close to it
  // relative error is less meaningful here

...


Manchmal gibt es wirklich eine Antwort, die richtig ist und Sie wollen exakte Gleichheit. Das Testen der Richtigkeit einer Implementierung ist ein gutes Beispiel (dh es gibt nur vier Milliarden Floats - also testen Sie sie alle! ).

Manlio
quelle
Wird der Test für eine == 0 in der OR-Klausel nicht durch diff <Float.MIN_NORMAL überflüssig gemacht?
neugierige Katze
3

Ein naheliegendes Beispiel, bei dem ==es in Ordnung ist, ist, wann aund bmit derselben Nummer a=c; b=c, z. B. um zu überprüfen, ob aund bauf dieselbe Weise initialisiert wurden. |a-b| < epsilonWürde natürlich auch hier funktionieren. Das einzige Problem ist, wie klein ist epsilon?

Außerdem a == bwürde in eine Anweisung kompiliert, während |a-b| < epsiloneinige dauern würde.

Karolis Juodelė
quelle
Vielen Dank! Wann möchte man in einer typischen Codierungssituation wissen, ob a und b auf die gleiche Weise initialisiert wurden? Können Sie damit Code-Schnipsel posten? Ich meine, konnte man nicht einfach die Quelle untersuchen und erzählen?
neugierige Katze
1
@curious_cat Angenommen, Sie erstellen ein Spiel wie Minecraft mit einem Raster aus Kacheln. Angenommen, diese Kacheln haben aus irgendeinem Grund Gleitkoordinaten und wurden in einer offensichtlichen Schleife wie initialisiert for x in [0..n] step w: for y in [0..n] step w: add_tile(x, y). In diesem Fall können Sie auf t1.x == t2.xsichere Weise testen, ob sich die beiden Kacheln in einer Ebene befinden. Sie brauchen nur, |a-b|<epsilonwenn aund bErgebnisse verschiedener Berechnungen sind, die den gleichen Wert erhalten sollten. Sehr oft wird jedoch eine Berechnung in zwei Variablen gespeichert oder Variablen mit Konstanten überschrieben.
Karolis Juodelė
Sehr schönes Beispiel! Vielen Dank. Macht sehr viel Sinn. Der erste wirklich klingende Anwendungsfall, den ich wirklich bekommen habe! Sie sollten das zu Ihrer Antwort hinzufügen!
neugierige Katze
2

Ein extremes Beispiel: IBM war das erste Unternehmen, das Prozessoren mit einer fusionierten Multiplikationsaddition aufbaute. Mit dieser Anweisung erstellten sie eine sehr schnelle Methode zur Berechnung von Quadratwurzeln gemäß dem IEEE-754-Standard. Diese Methode schlägt für einen einzelnen Eingabewert 1 ≤ x <4 fehl: Wenn x die größte Zahl ist, die als Gleitkommazahl kleiner als 4 dargestellt werden kann, wird das Ergebnis falsch gerundet.

Irgendwo in ihrer Implementierung prüfen sie also, ob x diesem einen bestimmten Wert entspricht. Sie wollen diesen Wert erkennen und keine anderen.

gnasher729
quelle
Vielen Dank! Welchen Wert hat das, irgendeine Idee? Ich konnte keine Referenzen zum Lesen finden. Kannst du irgendwelche Links posten?
neugierige Katze
2

Mein naiver Eindruck ist, dass man den Unterschied immer gegen ein Toleranz-Epsilon testen würde. Liege ich falsch? Kann es in bestimmten Kontexten sinnvoll sein, die Gleichheit von Floats zu testen?

Es gibt keine einzigartigen Rezepte. In diesem Artikel finden Sie eine ausführliche Beschreibung, in der Sie eine vollständige Antwort mit technischen Informationen und Code finden.

Zusammenfassend gibt es hauptsächlich 3 Fälle:

  • Vergleich gegen Null
  • Vergleich mit einer Nicht-Null
  • Vergleichen von zwei beliebigen Zahlen

Ihre Idee, einen Vergleich mit einer Toleranz durchzuführen, ist in einigen Fällen gut, aber es gibt auch eine Technik, die auf der Einheit an letzter Stelle ( ULP ) basiert und im Artikel beschrieben wird

Ich gehe implizit davon aus, dass die Verwendung des Gleichheitsoperators für Floats in einigen Kontexten tatsächlich sinnvoll ist. Warum sollten die meisten Programmiersprachen dies sonst zulassen?

Wie oben gibt es Situationen, in denen Sie es verwenden können, aber seien Sie gewarnt. Zum Beispiel hat der gcc-Compiler eine Warnung:

warning: comparing floating point with == or != is unsafe

Aktualisieren

Ich füge einige Überlegungen über diesem Argument hinzu, auch sie sind nicht eng mit dem Fall verbunden a == b.

Gleichheit mit Ausdruck

Betrachtet man den Fall:

a + b == c 

a b c

einb==cfl(fl(ein)+fl(b))==fl(c)
  • fl(x)x

|einein+b|errein+|bein+b|errb
errx=|x- -fl(x)||x|

In diesem Fall ist die Verwendung ==also heikler.

Portierung in verschiedenen Umgebungen

Wenn wir einen Code in verschiedenen Umgebungen (verschiedenen Maschinen) portieren, können wir unterschiedliche Ergebnisse erzielen (versuchen Sie beispielsweise, an einen Komponententest zu denken). Auch in dem Fall ist die Verwendung von ==empfindlich.

Mauro Vanzetto
quelle
Ah! Es wird also eine Warnung generiert. Vielen Dank. Mein Eindruck ist nun, dass in 99% der Codierungsfälle ein Float-Gleichheitsvergleich ein Fehler ist, der jedoch für die wenigen Eckfälle zulässig ist, in denen dies möglicherweise sinnvoll ist.
neugierige Katze
1
@curious_cat Ja, ich stimme dir zu. Ich füge eine Notiz hinzu.
Mauro Vanzetto
0

Ihr "naiver Eindruck" ist kein "naiver Eindruck" - er ist das Ergebnis des Lesens über Gleitkomma-Arithmetik und nur des halben Verstehens. Der "naive Eindruck" wäre der offensichtliche Eindruck, dass man fragt, ob a und b gleich sind, um herauszufinden, ob sie gleich sind.

Es gibt viele Situationen, in denen Sie nur wissen müssen, ob zwei Gleitkommazahlen gleich sind oder nicht. Es gibt viele Situationen, in denen Sie wissen, dass entweder keine Rundungsfehler oder keine Abweichungen aufgrund von Rundungsfehlern vorliegen. Wie das Konvertieren von Dezimalzahlen in Gleitkommazahlen, was bei jeder vernünftigen Implementierung deterministisch ist.

Hier ist eine gute: Jemand hat behauptet, dass für zwei beliebige Gleitkommazahlen a, b das Ergebnis von (b + a + b) und (b + b + a) dasselbe ist und Sie diese Behauptung testen möchten. Versuchen Sie dies, ohne zwei Gleitkommazahlen auf Gleichheit zu vergleichen.

Hier ist eine bessere: Versuchen Sie, eine Reihe von Gleitkommazahlen zu erstellen.

gnasher729
quelle