Ist es sicher, Gleitkommawerte auf Gleichheit mit 0 zu überprüfen?

100

Ich weiß, dass Sie sich normalerweise nicht auf die Gleichheit zwischen Doppel- oder Dezimalwerten verlassen können, aber ich frage mich, ob 0 ein Sonderfall ist.

Während ich Ungenauigkeiten zwischen 0,00000000000001 und 0,00000000000002 verstehen kann, scheint 0 selbst ziemlich schwer zu vermasseln, da es einfach nichts ist. Wenn Sie auf nichts ungenau sind, ist es nichts mehr.

Aber ich weiß nicht viel über dieses Thema, deshalb kann ich es nicht sagen.

double x = 0.0;
return (x == 0.0) ? true : false;

Wird das immer wahr sein?

Gene Roberts
quelle
69
Der ternäre Operator ist in diesem Code redundant :)
Joel Coehoorn
5
LOL du hast recht. Gehen Sie mich
Gene Roberts
Ich würde es nicht tun, weil Sie nicht wissen, wie x auf Null gesetzt wurde. Wenn Sie es trotzdem tun möchten, möchten Sie wahrscheinlich x abrunden oder auf den Boden legen, um die 1e-12 oder dergleichen zu entfernen, die möglicherweise am Ende markiert sind.
Rex Logan

Antworten:

115

Es ist sicher zu erwarten , dass der Vergleich zurück , truewenn und nur wenn die doppelte Variable einen Wert von genau hat 0.0(was in der ursprünglichen Codeausschnitt ist, natürlich, der Fall ist ). Dies steht im Einklang mit der Semantik des ==Operators. a == bbedeutet " aist gleich b".

Es ist nicht sicher (weil es nicht korrekt ist ) zu erwarten, dass das Ergebnis einer Berechnung in doppelter (oder allgemeiner Gleitkomma) Arithmetik Null ist, wenn das Ergebnis derselben Berechnung in reiner Mathematik Null ist. Dies liegt daran, dass bei Berechnungen ein Gleitkomma-Präzisionsfehler auftritt - ein Konzept, das in der reellen Zahlenarithmetik in der Mathematik nicht existiert.

Daniel Daranas
quelle
51

Wenn Sie viele "Gleichheits" -Vergleiche durchführen müssen, ist es möglicherweise eine gute Idee, eine kleine Hilfsfunktion oder Erweiterungsmethode in .NET 3.5 zu schreiben, um Folgendes zu vergleichen:

public static bool AlmostEquals(this double double1, double double2, double precision)
{
    return (Math.Abs(double1 - double2) <= precision);
}

Dies könnte folgendermaßen verwendet werden:

double d1 = 10.0 * .1;
bool equals = d1.AlmostEquals(0.0, 0.0000001);
Dirk Vollmar
quelle
4
Möglicherweise liegt ein subtraktiver Stornierungsfehler vor, wenn Sie double1 und double2 vergleichen, falls diese Zahlen Werte haben, die sehr nahe beieinander liegen. Ich würde die Math.Abs ​​entfernen und jeden Zweig einzeln überprüfen d1> = d2 - e und d1 <= d2 + e
Theodore Zographos
"Da Epsilon den minimalen Ausdruck eines positiven Werts definiert, dessen Bereich nahe Null liegt, muss der Differenzspielraum zwischen zwei ähnlichen Werten größer als Epsilon sein. In der Regel ist er um ein Vielfaches größer als Epsilon. Aus diesem Grund empfehlen wir dies Verwenden Sie Epsilon nicht, wenn Sie Doppelwerte auf Gleichheit vergleichen. " - msdn.microsoft.com/en-gb/library/ya2zha7s(v=vs.110).aspx
Rafael Costa
15

Für Ihre einfache Probe ist dieser Test in Ordnung. Aber was ist damit:

bool b = ( 10.0 * .1 - 1.0 == 0.0 );

Denken Sie daran, dass .1 eine sich wiederholende Dezimalzahl in Binärform ist und nicht genau dargestellt werden kann. Vergleichen Sie das dann mit diesem Code:

double d1 = 10.0 * .1; // make sure the compiler hasn't optimized the .1 issue away
bool b = ( d1 - 1.0 == 0.0 );

Ich überlasse es Ihnen, einen Test durchzuführen, um die tatsächlichen Ergebnisse zu sehen: Sie werden sich eher daran erinnern.

Joel Coehoorn
quelle
5
Tatsächlich gibt dies aus irgendeinem Grund true zurück (zumindest in LINQPad).
Alexey Romanov
Über was für ein ".1-Problem" sprechen Sie?
Teejay
14

Aus dem MSDN-Eintrag für Double.Equals :

Präzision im Vergleich

Die Equals-Methode sollte mit Vorsicht angewendet werden, da zwei scheinbar äquivalente Werte aufgrund der unterschiedlichen Genauigkeit der beiden Werte ungleich sein können. Das folgende Beispiel zeigt, dass der Double-Wert .3333 und der Double-Wert, die durch Teilen von 1 durch 3 zurückgegeben werden, ungleich sind.

...

Anstatt die Gleichheit zu vergleichen, besteht eine empfohlene Technik darin, eine akzeptable Differenzspanne zwischen zwei Werten zu definieren (z. B. 0,01% eines der Werte). Wenn der absolute Wert der Differenz zwischen den beiden Werten kleiner oder gleich dieser Spanne ist, ist die Differenz wahrscheinlich auf Unterschiede in der Genauigkeit zurückzuführen, und daher sind die Werte wahrscheinlich gleich. Im folgenden Beispiel werden mit dieser Technik .33333 und 1/3 verglichen, die beiden Double-Werte, die im vorherigen Codebeispiel als ungleich befunden wurden.

Siehe auch Double.Epsilon .

Stu Mackellar
quelle
1
Es ist auch möglich, dass nicht ganz äquivalente Werte als gleich verglichen werden. Man würde das erwarten, wenn x.Equals(y)dann (1/x).Equals(1/y), aber das ist nicht der Fall, wenn xist 0und yist 1/Double.NegativeInfinity. Diese Werte werden als gleich deklariert, obwohl dies bei ihren Wechselwirkungen nicht der Fall ist.
Supercat
@ Supercat: Sie sind gleichwertig. Und sie haben keine Gegenseitigkeit. Sie könnten Ihren Test mit x = 0und erneut ausführen y = 0, und das würden Sie immer noch finden 1/x != 1/y.
Ben Voigt
@ BenVoigt: Mit xund yals Typ double? Wie vergleichen Sie die Ergebnisse, um sie als ungleich zu melden? Beachten Sie, dass 1 / 0,0 nicht NaN ist.
Supercat
@supercat: Ok, es ist eines der Dinge, die IEEE-754 falsch macht. (Erstens ist das 1.0/0.0nicht NaN, wie es sein sollte, da die Grenze nicht eindeutig ist. Zweitens, dass Unendlichkeiten gleich sind, ohne auf Unendlichkeitsgrade zu achten)
Ben Voigt
@BenVoigt: Wenn die Null das Ergebnis der Multiplikation zweier sehr kleiner Zahlen war, sollte die Division von 1,0 in diesen Wert einen Wert ergeben, der größer ist als eine beliebige Anzahl der kleinen Zahlen das gleiche Vorzeichen und kleiner als eine beliebige Zahl, wenn eine der kleinen Zahlen Zahlen hatten entgegengesetzte Vorzeichen. Meiner Meinung nach wäre IEEE-754 besser, wenn es eine vorzeichenlose Null hätte, aber positive und negative Infinitesimale.
Supercat
6

Das Problem tritt auf, wenn Sie verschiedene Arten der Implementierung von Gleitkommawerten vergleichen, z. B. den Vergleich von float mit double. Aber mit dem gleichen Typ sollte es kein Problem sein.

float f = 0.1F;
bool b1 = (f == 0.1); //returns false
bool b2 = (f == 0.1F); //returns true

Das Problem ist, dass der Programmierer manchmal vergisst, dass für den Vergleich implizite Typumwandlungen (double to float) auftreten und dies zu einem Fehler führt.

Yogee
quelle
3

Wenn die Nummer direkt dem Float oder Double zugewiesen wurde, ist es sicher, gegen Null oder eine ganze Zahl zu testen, die in 53 Bit für ein Double oder 24 Bit für ein Float dargestellt werden kann.

Oder anders ausgedrückt: Sie können einem Double immer einen ganzzahligen Wert zuweisen und dann das Double wieder mit derselben Integer vergleichen und sicherstellen, dass es gleich ist.

Sie können auch mit der Zuweisung einer ganzen Zahl beginnen und einfache Vergleiche durchführen, indem Sie sich an das Addieren, Subtrahieren oder Multiplizieren mit ganzen Zahlen halten (vorausgesetzt, das Ergebnis beträgt weniger als 24 Bit für einen Float und 53 Bit für ein Double). So können Sie Floats und Doubles unter bestimmten kontrollierten Bedingungen als Ganzzahlen behandeln.

Kevin Gale
quelle
Ich stimme Ihrer Aussage im Allgemeinen zu (und habe sie positiv bewertet), aber ich glaube, es hängt wirklich davon ab, ob die Gleitkomma-Implementierung nach IEEE 754 verwendet wird oder nicht. Und ich glaube, jeder "moderne" Computer verwendet IEEE 754, zumindest zur Speicherung von Floats (es gibt seltsame Rundungsregeln, die sich unterscheiden).
Mark Lakata
2

Nein, das ist nicht in Ordnung. Sogenannte denormalisierte Werte (subnormal) würden bei einem Vergleich von 0,0 als falsch (ungleich Null) verglichen, bei Verwendung in einer Gleichung jedoch normalisiert (würden 0,0). Daher ist es nicht sicher, dies als Mechanismus zur Vermeidung einer Division durch Null zu verwenden. Fügen Sie stattdessen 1.0 hinzu und vergleichen Sie mit 1.0. Dadurch wird sichergestellt, dass alle Subnormen als Null behandelt werden.


quelle
Subnormen sind auch als Denormale bekannt
Manuel
Subnormale werden bei Verwendung nicht gleich Null, obwohl sie abhängig von der genauen Operation möglicherweise das gleiche Ergebnis liefern oder nicht.
wnoise
-2

Versuchen Sie dies, und Sie werden feststellen, dass == für double / float nicht zuverlässig ist.
double d = 0.1 + 0.2; bool b = d == 0.3;

Hier ist der Antwort von Quora.

Rickyuu
quelle
-4

Eigentlich denke ich, dass es besser ist, die folgenden Codes zu verwenden, um einen Doppelwert mit 0.0 zu vergleichen:

double x = 0.0;
return (Math.Abs(x) < double.Epsilon) ? true : false;

Gleiches gilt für float:

float x = 0.0f;
return (Math.Abs(x) < float.Epsilon) ? true : false;
David.Chu.ca
quelle
5
Nein. Aus den Dokumenten von double.Epsilon: "Wenn Sie einen benutzerdefinierten Algorithmus erstellen, der bestimmt, ob zwei Gleitkommazahlen als gleich angesehen werden können, müssen Sie einen Wert verwenden, der größer als die Epsilon-Konstante ist, um den akzeptablen absoluten Differenzspielraum zu ermitteln für die beiden Werte als gleich zu betrachten. (Typischerweise ist dieser Unterschiedsspielraum um ein Vielfaches größer als Epsilon.) "
Alastair Maw
1
@AlastairMaw Dies gilt für die Überprüfung von zwei Doppel jeder Größe auf Gleichheit. Für die Überprüfung der Gleichheit auf Null ist double.Epsilon in Ordnung.
JWG
4
Nein, das ist es nicht . Es ist sehr wahrscheinlich, dass der Wert, zu dem Sie durch eine Berechnung gelangt sind, um ein Vielfaches von Null entfernt ist, aber dennoch als Null betrachtet werden sollte. Sie erreichen nicht auf magische Weise eine ganze Reihe zusätzlicher Präzision in Ihrem Zwischenergebnis von irgendwoher, nur weil es zufällig nahe Null ist.
Alastair Maw
4
Zum Beispiel: (1.0 / 5.0 + 1.0 / 5.0 - 1.0 / 10.0 - 1.0 / 10.0 - 1.0 / 10.0 - 1.0 / 10.0) <double.Epsilon == false (und in Bezug auf die Größe erheblich: 2.78E-17 vs 4.94E -324)
Alastair Maw
Also, was ist die empfohlene Präzision, wenn double.Epsilon nicht in Ordnung ist? Wäre 10 mal Epsilon in Ordnung? 100 mal?
Liang