Erstens sind Gleitkommawerte in ihrem Verhalten nicht "zufällig". Ein genauer Vergleich kann und macht in vielen realen Anwendungen Sinn. Wenn Sie jedoch Gleitkomma verwenden möchten, müssen Sie sich dessen bewusst sein, wie es funktioniert. Wenn Sie davon ausgehen, dass Gleitkomma wie reelle Zahlen funktioniert, erhalten Sie Code, der schnell kaputt geht. Wenn Sie auf der Seite der Annahme von Gleitkommaergebnissen einen großen zufälligen Flaum haben (wie die meisten Antworten hier vermuten lassen), erhalten Sie Code, der auf den ersten Blick zu funktionieren scheint, aber am Ende große Fehler und Fälle mit gebrochenen Ecken aufweist.
Wenn Sie mit Gleitkomma programmieren möchten, sollten Sie zunächst Folgendes lesen:
Was jeder Informatiker über Gleitkomma-Arithmetik wissen sollte
Ja, lies alles. Wenn dies zu aufwändig ist, sollten Sie für Ihre Berechnungen Ganzzahlen / Fixpunkte verwenden, bis Sie Zeit zum Lesen haben. :-)
Nachdem dies gesagt wurde, sind die größten Probleme bei exakten Gleitkomma-Vergleichen folgende:
Die Tatsache , dass viele Werte , die Sie in der Quelle schreiben können, oder lesen Sie in mit scanf
oder strtod
, existiert nicht als Gleitkommazahlen und geräuschlos auf die nächste Annäherung konvertiert werden. Dies ist, worüber die Antwort von Dämon9733 sprach.
Die Tatsache, dass viele Ergebnisse gerundet werden, weil die Genauigkeit nicht ausreicht, um das tatsächliche Ergebnis darzustellen. Ein einfaches Beispiel, wo Sie dies sehen können, ist das Hinzufügen x = 0x1fffffe
und y = 1
als Floats. Hier x
hat die Mantisse 24 Bit Genauigkeit (ok) und y
nur 1 Bit, aber wenn Sie sie hinzufügen, befinden sich ihre Bits nicht an überlappenden Stellen, und das Ergebnis würde 25 Bit Genauigkeit erfordern. Stattdessen wird es gerundet (auf 0x2000000
im Standardrundungsmodus).
Die Tatsache, dass viele Ergebnisse gerundet werden, weil unendlich viele Stellen für den richtigen Wert benötigt werden. Dies beinhaltet sowohl rationale Ergebnisse wie 1/3 (die Sie von der Dezimalstelle kennen, wo es unendlich viele Stellen einnimmt) als auch 1/10 (das auch unendlich viele Stellen in binärer Form einnimmt, da 5 keine Zweierpotenz ist). sowie irrationale Ergebnisse wie die Quadratwurzel von allem, was kein perfektes Quadrat ist.
Doppelte Rundung. Auf einigen Systemen (insbesondere x86) werden Gleitkommaausdrücke mit höherer Genauigkeit als ihre nominalen Typen ausgewertet. Dies bedeutet, dass Sie bei einer der oben genannten Rundungsarten zwei Rundungsschritte erhalten, zuerst eine Rundung des Ergebnisses auf den Typ mit höherer Genauigkeit und dann eine Rundung auf den endgültigen Typ. Betrachten Sie als Beispiel, was in Dezimalzahl passiert, wenn Sie 1,49 auf eine Ganzzahl (1) runden, und was passiert, wenn Sie es zuerst auf eine Dezimalstelle (1,5) runden und dann das Ergebnis auf eine Ganzzahl (2) runden. Dies ist tatsächlich einer der schlimmsten Bereiche, mit denen sich Gleitkommazahlen befassen müssen, da das Verhalten des Compilers (insbesondere bei fehlerhaften, nicht konformen Compilern wie GCC) nicht vorhersehbar ist.
Transzendente Funktionen ( trig
, exp
, log
, etc.) sind nicht korrekt gerundete Ergebnisse haben angegeben; Das Ergebnis wird nur so angegeben, dass es innerhalb einer Einheit an der letzten Stelle der Genauigkeit korrekt ist (normalerweise als 1ulp bezeichnet ).
Wenn Sie Gleitkomma-Code schreiben, müssen Sie berücksichtigen, was Sie mit den Zahlen tun, die dazu führen können, dass die Ergebnisse ungenau sind, und entsprechende Vergleiche anstellen. Oft ist es sinnvoll, mit einem "Epsilon" zu vergleichen, aber dieses Epsilon sollte auf der Größe der Zahlen basieren, die Sie vergleichen , und nicht auf einer absoluten Konstante. (In Fällen, in denen ein absolut konstantes Epsilon funktionieren würde, deutet dies stark darauf hin, dass der Festpunkt und nicht der Gleitkomma das richtige Werkzeug für den Job ist!)
Bearbeiten: Insbesondere sollte ein betragsbezogener Epsilon-Check ungefähr so aussehen:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
Wo FLT_EPSILON
ist die Konstante aus float.h
(ersetzen Sie es durch DBL_EPSILON
für double
s oder LDBL_EPSILON
für long double
s) und K
ist eine Konstante , die Sie so wählen , dass der akkumulierte Fehler Ihrer Berechnungen auf jeden Fall begrenzt ist durch K
Einheiten in den letzten Platz (und wenn Sie nicht sicher sind , haben Sie den Fehler gebundene Berechnung richtig, machen Sie K
ein paar Mal größer als das, was Ihre Berechnungen sagen, dass es sein sollte).
Beachten Sie schließlich, dass bei Verwendung dieser Funktion möglicherweise besondere Sorgfalt nahe Null erforderlich ist, da FLT_EPSILON
dies für Denormale keinen Sinn ergibt. Eine schnelle Lösung wäre, es zu machen:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
und ebenfalls ersetzen, DBL_MIN
wenn Doppel verwendet werden.
fabs(x+y)
ist problematisch, wennx
undy
(kann) ein anderes Vorzeichen haben. Trotzdem eine gute Antwort gegen die Flut von Frachtkult-Vergleichen.