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.x
undy
haben ein anderes Vorzeichen, ist das kein Problem. Die rechte Seite wird "zu klein" sein, aber dax
undy
unterschiedliche Vorzeichen haben, sollten sie sowieso nicht gleich vergleichen. (Es sei denn, sie sind so klein, dass sie denormal sind, aber dann fängt der zweite Fall es auf)Da 0 genau als IEEE754-Gleitkommazahl darstellbar ist (oder eine andere Implementierung von fp-Zahlen verwendet, mit denen ich jemals gearbeitet habe), ist ein Vergleich mit 0 wahrscheinlich sicher. Sie könnten jedoch gebissen werden, wenn Ihr Programm einen Wert berechnet (z. B.
theView.frame.origin.x
), von dem Sie Grund zu der Annahme haben, dass er 0 sein sollte, dessen Berechnung jedoch nicht garantieren kann, dass er 0 ist.Um ein wenig zu verdeutlichen, eine Berechnung wie:
wird (es sei denn, Ihre Sprache oder Ihr System ist fehlerhaft) einen Wert erstellen, so dass (areal == 0.0) true zurückgibt, aber eine andere Berechnung wie
nicht dürfen.
Wenn Sie sich sicher sein können, dass Ihre Berechnungen Werte ergeben, die 0 sind (und nicht nur Werte, die 0 sein sollten), können Sie fp-Werte mit 0 vergleichen. Wenn Sie sich nicht im erforderlichen Maße versichern können Halten Sie sich am besten an den üblichen Ansatz der „tolerierten Gleichstellung“.
Im schlimmsten Fall kann der unachtsame Vergleich von fp-Werten äußerst gefährlich sein: Denken Sie an Avionik, Waffenführung, Kraftwerksbetrieb, Fahrzeugnavigation und fast jede Anwendung, bei der die Berechnung auf die reale Welt trifft.
Für Angry Birds nicht so gefährlich.
quelle
1.30 - 2*(0.65)
ist ein perfektes Beispiel für einen Ausdruck, der offensichtlich auf 0,0 bewertet , wenn Ihr Compiler implementiert IEEE 754, weil die doppelt als dargestellt0.65
und1.30
haben die gleichen Mantissen und Multiplikation mit zwei ist offensichtlich genau.Ich möchte eine etwas andere Antwort geben als die anderen. Sie eignen sich hervorragend zur Beantwortung Ihrer Frage, aber wahrscheinlich nicht für das, was Sie wissen müssen oder was Ihr eigentliches Problem ist.
Gleitkomma in Grafiken ist in Ordnung! Es ist jedoch fast nicht erforderlich, Floats direkt zu vergleichen. Warum sollten Sie das tun müssen? Grafik verwendet Floats, um Intervalle zu definieren. Und zu vergleichen, ob ein Float innerhalb eines Intervalls liegt, das auch durch Floats definiert ist, ist immer gut definiert und muss lediglich konsistent, nicht genau oder präzise sein! Solange ein Pixel (das auch ein Intervall ist!) Zugewiesen werden kann, sind alle Grafikanforderungen erfüllt.
Wenn Sie also testen möchten, ob Ihr Punkt außerhalb eines Bereichs von [0..width [liegt, ist dies in Ordnung. Stellen Sie einfach sicher, dass Sie die Inklusion konsistent definieren. Zum Beispiel immer innerhalb definieren ist (x> = 0 && x <Breite). Gleiches gilt für Kreuzungs- oder Treffertests.
Wenn Sie jedoch eine Grafikkoordinate als eine Art Flag missbrauchen, z. B. um festzustellen, ob ein Fenster angedockt ist oder nicht, sollten Sie dies nicht tun. Verwenden Sie stattdessen ein boolesches Flag, das von der Grafikpräsentationsebene getrennt ist.
quelle
Der Vergleich mit Null kann eine sichere Operation sein, solange die Null kein berechneter Wert war (wie in einer obigen Antwort angegeben). Der Grund dafür ist, dass Null eine perfekt darstellbare Zahl im Gleitkomma ist.
Wenn Sie perfekt darstellbare Werte sprechen, erhalten Sie 24 Bit Reichweite in einem Zweierpotenzbegriff (einfache Genauigkeit). 1, 2, 4 sind also perfekt darstellbar, ebenso wie .5, .25 und .125. Solange alle wichtigen Bits in 24 Bits vorliegen, sind Sie golden. So kann 10.625 präzise dargestellt werden.
Das ist toll, fällt aber unter Druck schnell auseinander. Zwei Szenarien fallen mir ein: 1) Wenn es sich um eine Berechnung handelt. Vertraue nicht, dass sqrt (3) * sqrt (3) == 3. Es wird einfach nicht so sein. Und es wird wahrscheinlich nicht innerhalb eines Epsilons sein, wie einige der anderen Antworten vermuten lassen. 2) Wenn ein Non-Power-of-2 (NPOT) beteiligt ist. Es mag seltsam klingen, aber 0.1 ist eine unendliche Reihe in Binärform, und daher ist jede Berechnung mit einer solchen Zahl von Anfang an ungenau.
(Oh, und die ursprüngliche Frage erwähnte Vergleiche mit Null. Vergessen Sie nicht, dass -0,0 auch ein perfekt gültiger Gleitkommawert ist.)
quelle
[Die 'richtige Antwort' beschönigt die Auswahl
K
. Die Auswahl erfolgtK
genauso ad-hoc wie die Auswahl,VISIBLE_SHIFT
aber die AuswahlK
ist weniger offensichtlich, da sie im GegensatzVISIBLE_SHIFT
zu keiner Anzeigeeigenschaft basiert. Wählen Sie also Ihr Gift - wählen SieK
oder wählen SieVISIBLE_SHIFT
. Diese Antwort befürwortet die AuswahlVISIBLE_SHIFT
und zeigt dann die Schwierigkeit bei der AuswahlK
]Gerade wegen Rundungsfehlern sollten Sie für logische Operationen keinen Vergleich von 'exakten' Werten verwenden. In Ihrem speziellen Fall einer Position auf einem visuellen Display kann es unmöglich sein, ob die Position 0,0 oder 0,0000000003 beträgt - der Unterschied ist für das Auge unsichtbar. Ihre Logik sollte also ungefähr so aussehen:
Letztendlich hängt "für das Auge unsichtbar" jedoch von Ihren Anzeigeeigenschaften ab. Wenn Sie die Anzeige nach oben verschieben können (sollten Sie in der Lage sein); Wählen Sie dann, ob Sie
VISIBLE_SHIFT
ein Bruchteil dieser Obergrenze sein möchten.Nun beruht die „richtige Antwort“ darauf.
K
Lassen Sie uns also die Auswahl untersuchenK
. Die 'richtige Antwort' oben lautet:Also brauchen wir
K
. Wenn esK
schwieriger und weniger intuitiv ist, meine zu findenVISIBLE_SHIFT
, entscheiden Sie, was für Sie funktioniert. Um dies herauszufinden, werdenK
wir ein Testprogramm schreiben, das eine Reihe vonK
Werten betrachtet, damit wir sehen können, wie es sich verhält.K
Sollte offensichtlich sein, wie man wählt , wenn die "richtige Antwort" verwendbar ist. Nein?Wir werden als "richtige Antwort" Details verwenden:
Probieren wir einfach alle Werte von K aus:
Ah, also sollte K 1e16 oder größer sein, wenn 1e-13 'Null' sein soll.
Ich würde also sagen, Sie haben zwei Möglichkeiten:
K
.quelle
K
die schwierig und nicht intuitiv auszuwählen sind.Die richtige Frage: Wie vergleicht man Punkte in Cocoa Touch?
Die richtige Antwort: CGPointEqualToPoint ().
Eine andere Frage: Sind zwei berechnete Werte gleich?
Die Antwort hier gepostet: Sie sind nicht.
Wie kann man überprüfen, ob sie in der Nähe sind? Wenn Sie überprüfen möchten, ob sie nahe beieinander liegen, verwenden Sie CGPointEqualToPoint () nicht. Überprüfen Sie jedoch nicht, ob sie in der Nähe sind. Tun Sie etwas, das in der realen Welt Sinn macht, z. B. zu überprüfen, ob sich ein Punkt jenseits einer Linie befindet oder ob sich ein Punkt innerhalb einer Kugel befindet.
quelle
Als ich das letzte Mal den C-Standard überprüfte, war es nicht erforderlich, dass Gleitkommaoperationen auf Doppelwerten (64 Bit insgesamt, 53 Bit Mantisse) mit mehr als dieser Genauigkeit genau waren. Einige Hardwarekomponenten können jedoch die Operationen in Registern mit größerer Genauigkeit ausführen, und die Anforderung wurde dahingehend interpretiert, dass keine Anforderung zum Löschen von Bits niedrigerer Ordnung (über die Genauigkeit der in die Register geladenen Zahlen hinaus) besteht. Sie könnten also unerwartete Ergebnisse solcher Vergleiche erhalten, je nachdem, was in den Registern von dem übrig geblieben ist, der zuletzt dort geschlafen hat.
Das heißt, und trotz meiner Bemühungen, es zu löschen, wann immer ich es sehe, hat das Outfit, in dem ich arbeite, viel C-Code, der mit gcc kompiliert und unter Linux ausgeführt wird, und wir haben seit langer Zeit keine dieser unerwarteten Ergebnisse mehr bemerkt . Ich habe keine Ahnung, ob dies daran liegt, dass gcc die niederwertigen Bits für uns löscht, die 80-Bit-Register für diese Operationen auf modernen Computern nicht verwendet werden, der Standard geändert wurde oder was. Ich würde gerne wissen, ob jemand Kapitel und Verse zitieren kann.
quelle
Sie können einen solchen Code verwenden, um float mit null zu vergleichen:
Dies wird mit einer Genauigkeit von 0,1 verglichen, die in diesem Fall für CGFloat ausreicht.
quelle
int
ohne VersicherungtheView.frame.origin.x
liegt in / nahe diesem Bereich vonint
führt zu undefiniertem Verhalten (UB) - oder in diesem Fall 1/100 des Bereichs vonint
.}}
quelle
Ich verwende die folgende Vergleichsfunktion, um eine Anzahl von Dezimalstellen zu vergleichen:
quelle
Ich würde sagen, das Richtige ist, jede Zahl als Objekt zu deklarieren und dann drei Dinge in diesem Objekt zu definieren: 1) einen Gleichheitsoperator. 2) eine setAcceptableDifference-Methode. 3) der Wert selbst. Der Gleichheitsoperator gibt true zurück, wenn die absolute Differenz zweier Werte kleiner als der als akzeptabel festgelegte Wert ist.
Sie können das Objekt entsprechend dem Problem in Unterklassen unterteilen. Beispielsweise können runde Metallstangen zwischen 1 und 2 Zoll als gleich groß angesehen werden, wenn sich ihre Durchmesser um weniger als 0,0001 Zoll unterscheiden. Sie würden also setAcceptableDifference mit dem Parameter 0,0001 aufrufen und dann den Gleichheitsoperator mit Sicherheit verwenden.
quelle