Ich schreibe gerade einen Code, in dem ich Folgendes habe:
double a = SomeCalculation1();
double b = SomeCalculation2();
if (a < b)
DoSomething2();
else if (a > b)
DoSomething3();
Und dann muss ich an anderen Orten möglicherweise Gleichstellung tun:
double a = SomeCalculation3();
double b = SomeCalculation4();
if (a == 0.0)
DoSomethingUseful(1 / a);
if (b == 0.0)
return 0; // or something else here
Kurz gesagt, ich habe viel Gleitkomma-Mathematik im Gange und ich muss verschiedene Vergleiche für Bedingungen durchführen. Ich kann es nicht in ganzzahlige Mathematik umwandeln, weil so etwas in diesem Zusammenhang bedeutungslos ist.
Ich habe zuvor gelesen, dass Gleitkomma-Vergleiche unzuverlässig sein können, da solche Dinge passieren können:
double a = 1.0 / 3.0;
double b = a + a + a;
if ((3 * a) != b)
Console.WriteLine("Oh no!");
Kurz gesagt, ich möchte wissen: Wie kann ich Gleitkommazahlen (kleiner als, größer als Gleichheit) zuverlässig vergleichen?
Der von mir verwendete Zahlenbereich reicht ungefähr von 10E-14 bis 10E6, daher muss ich sowohl mit kleinen als auch mit großen Zahlen arbeiten.
Ich habe dies als sprachunabhängig markiert, weil ich daran interessiert bin, wie ich dies erreichen kann, unabhängig davon, welche Sprache ich verwende.
quelle
Antworten:
Das Vergleichen für größer / kleiner ist kein wirkliches Problem, es sei denn, Sie arbeiten direkt am Rand der Schwimm- / Doppelgenauigkeitsgrenze.
Für einen "Fuzzy Equals" -Vergleich ist dies (Java-Code, sollte leicht anzupassen sein) das, was ich mir nach viel Arbeit und unter Berücksichtigung vieler Kritik für The Floating-Point Guide ausgedacht habe:
public static boolean nearlyEqual(float a, float b, float epsilon) { final float absA = Math.abs(a); final float absB = Math.abs(b); final float diff = Math.abs(a - b); if (a == b) { // shortcut, handles infinities return true; } else 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 return diff < (epsilon * Float.MIN_NORMAL); } else { // use relative error return diff / (absA + absB) < epsilon; } }
Es kommt mit einer Testsuite. Sie sollten jede Lösung, die dies nicht tut, sofort verwerfen, da sie in einigen Randfällen wie einem Wert 0, zwei sehr kleinen Werten gegenüber Null oder Unendlichkeiten praktisch garantiert fehlschlägt.
Eine Alternative (siehe Link oben für weitere Details) besteht darin, die Bitmuster der Floats in eine Ganzzahl umzuwandeln und alles innerhalb eines festen Ganzzahlabstands zu akzeptieren.
In jedem Fall gibt es wahrscheinlich keine Lösung, die für alle Anwendungen perfekt ist. Idealerweise entwickeln / passen Sie Ihre eigene mit einer Testsuite an, die Ihre tatsächlichen Anwendungsfälle abdeckt.
quelle
else if (a * b == 0)
, aber dann ist Ihr Kommentar in der gleichen Zeilea or b or both are zero
. Aber sind das nicht zwei verschiedene Dinge? ZB wenna == 1e-162
undb == 2e-162
dann wird die Bedingunga * b == 0
wahr sein.abs(a-b)<eps
Antworten hier berücksichtigt . Zwei Fragen: (1) Wäre es nicht besser, alle<
s in<=
s zu ändern und so "Null-Eps" -Vergleiche zuzulassen, die exakten Vergleichen entsprechen? (2) Wäre es nicht besser,diff < epsilon * (absA + absB);
anstelle vondiff / (absA + absB) < epsilon;
(letzte Zeile) - zu verwenden?TL; DR
bool nearly_equal( float a, float b, float epsilon = 128 * FLT_EPSILON, float relth = FLT_MIN) // those defaults are arbitrary and could be removed { assert(std::numeric_limits<float>::epsilon() <= epsilon); assert(epsilon < 1.f); if (a == b) return true; auto diff = std::abs(a-b); auto norm = std::min((std::abs(a) + std::abs(b)), std::numeric_limits<float>::max()); // or even faster: std::min(std::abs(a + b), std::numeric_limits<float>::max()); // keeping this commented out until I update figures below return diff < std::max(relth, epsilon * norm); }
Grafik bitte?
Beim Vergleich von Gleitkommazahlen gibt es zwei "Modi".
Der erste ist der relative Modus, bei dem der Unterschied zwischen
x
undy
relativ zu ihrer Amplitude betrachtet wird|x| + |y|
. Beim Zeichnen in 2D wird das folgende Profil angezeigt, wobei Grün Gleichheit vonx
und bedeutety
. (Ich habeepsilon
zu Illustrationszwecken eine von 0,5 genommen).Der relative Modus wird für "normale" oder "ausreichend große" Gleitkommawerte verwendet. (Dazu später mehr).
Der zweite ist ein absoluter Modus, wenn wir einfach ihre Differenz mit einer festen Zahl vergleichen. Es gibt das folgende Profil (wieder mit einem
epsilon
von 0,5 und einemrelth
von 1 zur Veranschaulichung).Dieser absolute Vergleichsmodus wird für "winzige" Gleitkommawerte verwendet.
Die Frage ist nun, wie wir diese beiden Antwortmuster zusammenfügen.
In Michael Borgwardts Antwort basiert der Schalter auf dem Wert von
diff
, der unten liegen sollterelth
(Float.MIN_NORMAL
in seiner Antwort). Diese Schaltzone ist in der folgenden Grafik schraffiert dargestellt.Weil
relth * epsilon
kleiner ist, dassrelth
die grünen Flecken nicht zusammenkleben, was wiederum der Lösung eine schlechte Eigenschaft gibt: Wir können Drillinge von Zahlen finden, diex < y_1 < y_2
und dochx == y2
aberx != y1
.Nehmen Sie dieses bemerkenswerte Beispiel:
x = 4.9303807e-32 y1 = 4.930381e-32 y2 = 4.9309825e-32
Wir haben
x < y1 < y2
und ist in der Taty2 - x
mehr als 2000-mal größer alsy1 - x
. Und doch mit der aktuellen Lösung,nearlyEqual(x, y1, 1e-4) == False nearlyEqual(x, y2, 1e-4) == True
Im Gegensatz dazu basiert in der oben vorgeschlagenen Lösung die Schaltzone auf dem Wert von
|x| + |y|
, der durch das schraffierte Quadrat unten dargestellt wird. Es stellt sicher, dass beide Zonen ordnungsgemäß verbunden sind.Der obige Code hat auch keine Verzweigung, was effizienter sein könnte. Bedenken Sie, dass Vorgänge wie
max
undabs
, die a priori verzweigt werden müssen, häufig dedizierte Montageanweisungen haben. Aus diesem Grund denke ich, dass dieser Ansatz einer anderen Lösung überlegen ist, die darin besteht, MichaelsnearlyEqual
durch Ändern des Schalters vondiff < relth
auf zu reparierendiff < eps * relth
, was dann im Wesentlichen das gleiche Antwortmuster erzeugen würde.Wo kann man zwischen relativem und absolutem Vergleich wechseln?
Der Wechsel zwischen diesen Modi
relth
erfolgt wieFLT_MIN
in der akzeptierten Antwort. Diese Wahl bedeutet, dass die Darstellung vonfloat32
die Genauigkeit unserer Gleitkommazahlen einschränkt.Das macht nicht immer Sinn. Wenn die Zahlen, die Sie vergleichen, beispielsweise das Ergebnis einer Subtraktion sind, ist möglicherweise etwas im Bereich von
FLT_EPSILON
sinnvoller. Wenn es sich um Quadratwurzeln subtrahierter Zahlen handelt, kann die numerische Ungenauigkeit sogar noch höher sein.Es ist ziemlich offensichtlich, wenn Sie einen Gleitkomma mit vergleichen
0
. Hier wird jeder relative Vergleich scheitern, weil|x - 0| / (|x| + 0) = 1
. Der Vergleich muss also in den absoluten Modus wechseln, wenn erx
in der Größenordnung der Ungenauigkeit Ihrer Berechnung liegt - und selten ist er so niedrig wieFLT_MIN
.Dies ist der Grund für die Einführung des
relth
obigen Parameters.Auch durch die nicht multipliziert
relth
mitepsilon
der Interpretation dieses Parameters ist einfach und entsprechen dem Niveau der numerischen Präzision , dass wir auf diese Zahlen erwarten.Mathematisches Grollen
(hier meistens zu meinem eigenen Vergnügen gehalten)
Generell gehe ich davon aus, dass ein gut erzogener Gleitkomma-Vergleichsoperator
=~
einige grundlegende Eigenschaften haben sollte.Folgendes ist ziemlich offensichtlich:
a =~ a
a =~ b
impliziertb =~ a
a =~ b
impliziert-a =~ -b
(Wir haben
a =~ b
undb =~ c
implizierena =~ c
,=~
ist keine Äquivalenzbeziehung).Ich würde die folgenden Eigenschaften hinzufügen, die spezifischer für Gleitkomma-Vergleiche sind
a < b < c
, danna =~ c
implizierta =~ b
(engere Werte sollten auch gleich sein)a, b, m >= 0
danna =~ b
implizierta + m =~ b + m
(größere Werte mit der gleichen Differenz sollten auch gleich sein)0 <= λ < 1
danna =~ b
impliziertλa =~ λb
(vielleicht weniger offensichtlich zu argumentieren).Diese Eigenschaften schränken mögliche Gleichheitsfunktionen bereits stark ein. Die oben vorgeschlagene Funktion überprüft sie. Möglicherweise fehlen eine oder mehrere ansonsten offensichtliche Eigenschaften.
Wenn man sich
=~
eine Familie von Gleichheitsbeziehungen=~[Ɛ,t]
vorstellt, die durchƐ
und parametrisiert sindrelth
, könnte man auch hinzufügenƐ1 < Ɛ2
danna =~[Ɛ1,t] b
implizierta =~[Ɛ2,t] b
(Gleichheit für eine gegebene Toleranz impliziert Gleichheit bei einer höheren Toleranz)t1 < t2
danna =~[Ɛ,t1] b
implizierta =~[Ɛ,t2] b
(Gleichheit für eine gegebene Ungenauigkeit impliziert Gleichheit bei einer höheren Ungenauigkeit)Die vorgeschlagene Lösung überprüft auch diese.
quelle
(std::abs(a) + std::abs(b))
jemals größer sein alsstd::numeric_limits<float>::max()
?Ich hatte das Problem, Gleitkommazahlen zu vergleichen
A < B
undA > B
Folgendes scheint zu funktionieren:if(A - B < Epsilon) && (fabs(A-B) > Epsilon) { printf("A is less than B"); } if (A - B > Epsilon) && (fabs(A-B) > Epsilon) { printf("A is greater than B"); }
Die Fabs - absoluter Wert - kümmern sich darum, ob sie im Wesentlichen gleich sind.
quelle
fabs
überhaupt zu verwenden, wenn Sie den ersten Test machenif (A - B < -Epsilon)
Wir müssen eine Toleranzstufe wählen, um Float-Zahlen zu vergleichen. Zum Beispiel,
final float TOLERANCE = 0.00001; if (Math.abs(f1 - f2) < TOLERANCE) Console.WriteLine("Oh yes!");
Eine Note. Dein Beispiel ist ziemlich lustig.
double a = 1.0 / 3.0; double b = a + a + a; if (a != b) Console.WriteLine("Oh no!");
Einige Mathe hier
a = 1/3 b = 1/3 + 1/3 + 1/3 = 1. 1/3 != 1
Oh ja..
Meinst du
if (b != 1) Console.WriteLine("Oh no!")
quelle
Idee, die ich für den Gleitkomma-Vergleich schnell hatte
infix operator ~= {} func ~= (a: Float, b: Float) -> Bool { return fabsf(a - b) < Float(FLT_EPSILON) } func ~= (a: CGFloat, b: CGFloat) -> Bool { return fabs(a - b) < CGFloat(FLT_EPSILON) } func ~= (a: Double, b: Double) -> Bool { return fabs(a - b) < Double(FLT_EPSILON) }
quelle
Anpassung an PHP aus der Antwort von Michael Borgwardt & bosonix:
class Comparison { const MIN_NORMAL = 1.17549435E-38; //from Java Specs // from http://floating-point-gui.de/errors/comparison/ public function nearlyEqual($a, $b, $epsilon = 0.000001) { $absA = abs($a); $absB = abs($b); $diff = abs($a - $b); if ($a == $b) { return true; } else { if ($a == 0 || $b == 0 || $diff < self::MIN_NORMAL) { return $diff < ($epsilon * self::MIN_NORMAL); } else { return $diff / ($absA + $absB) < $epsilon; } } } }
quelle
Sie sollten sich fragen, warum Sie die Zahlen vergleichen. Wenn Sie den Zweck des Vergleichs kennen, sollten Sie auch die erforderliche Genauigkeit Ihrer Zahlen kennen. Das ist in jeder Situation und in jedem Anwendungskontext unterschiedlich. In so ziemlich allen praktischen Fällen ist jedoch eine absolute Genauigkeit erforderlich . Es ist nur sehr selten, dass eine relative Genauigkeit anwendbar ist.
Ein Beispiel: Wenn Sie ein Diagramm auf dem Bildschirm zeichnen möchten, möchten Sie wahrscheinlich, dass Gleitkommawerte gleich verglichen werden, wenn sie demselben Pixel auf dem Bildschirm zugeordnet sind. Wenn die Größe Ihres Bildschirms 1000 Pixel beträgt und Ihre Zahlen im Bereich von 1e6 liegen, möchten Sie wahrscheinlich, dass 100 gleich 200 sind.
Bei der erforderlichen absoluten Genauigkeit wird der Algorithmus zu:
quelle
Die Standardempfehlung besteht darin, einen kleinen "Epsilon" -Wert zu verwenden (wahrscheinlich abhängig von Ihrer Anwendung ausgewählt) und Floats, die innerhalb von Epsilon voneinander liegen, als gleich zu betrachten. zB so etwas wie
#define EPSILON 0.00000001 if ((a - b) < EPSILON && (b - a) < EPSILON) { printf("a and b are about equal\n"); }
Eine vollständigere Antwort ist kompliziert, da Gleitkommafehler äußerst subtil und verwirrend sind. Wenn Sie sich wirklich für Gleichheit im wahrsten Sinne des Wortes interessieren, suchen Sie wahrscheinlich nach einer Lösung, die kein Gleitkomma beinhaltet.
quelle
if ((a - b) < EPSILON/a && (b - a) < EPSILON/a)
c
, denn sobald Ihre Zahl groß genug ist, ist der EPSILON kleiner als die Maschinengenauigkeit vonc
. Nehmen wir zum Beispiel anc = 1E+22; d=c/3; e=d+d+d;
. Danne-c
kann durchaus erheblich größer sein als 1.double a = pow(8,20); double b = a/7; double c = b+b+b+b+b+b+b; std::cout<<std::scientific<<a-c;
(a und c nicht gleich nach pnt und nelhage) oderdouble a = pow(10,-14); double b = a/2; std::cout<<std::scientific<<a-b;
(a und b gleich nach pnt und nelhage)Ich habe versucht, eine Gleichstellungsfunktion unter Berücksichtigung der obigen Kommentare zu schreiben. Folgendes habe ich mir ausgedacht:
Bearbeiten: Wechsel von Math.Max (a, b) zu Math.Max (Math.Abs (a), Math.Abs (b))
static bool fpEqual(double a, double b) { double diff = Math.Abs(a - b); double epsilon = Math.Max(Math.Abs(a), Math.Abs(b)) * Double.Epsilon; return (diff < epsilon); }
Gedanken? Ich muss immer noch ein größeres als und ein kleineres als auch herausfinden.
quelle
epsilon
sollte seinMath.abs(Math.Max(a, b)) * Double.Epsilon;
, oder es wird immer kleiner sein alsdiff
für negativea
undb
. Und ich denke, Ihreepsilon
ist zu klein, die Funktion gibt möglicherweise nichts anderes als den==
Operator zurück. Größer als es ista < b && !fpEqual(a,b)
.Sie müssen berücksichtigen, dass der Kürzungsfehler relativ ist. Zwei Zahlen sind ungefähr gleich, wenn ihre Differenz ungefähr so groß ist wie ihre ulp (Einheit an letzter Stelle).
Wenn Sie jedoch Gleitkommaberechnungen durchführen, steigt Ihr Fehlerpotential mit jeder Operation (insbesondere vorsichtig mit Subtraktionen!), Daher muss sich Ihre Fehlertoleranz entsprechend erhöhen.
quelle
Der beste Weg, um Doppelwerte auf Gleichheit / Ungleichheit zu vergleichen, besteht darin, den absoluten Wert ihrer Differenz mit einem ausreichend kleinen Wert (abhängig von Ihrem Kontext) zu vergleichen.
double eps = 0.000000001; //for instance double a = someCalc1(); double b = someCalc2(); double diff = Math.abs(a - b); if (diff < eps) { //equal }
quelle