Obecnie piszę kod, w którym mam coś w stylu:
double a = SomeCalculation1();
double b = SomeCalculation2();
if (a < b)
DoSomething2();
else if (a > b)
DoSomething3();
A potem w innych miejscach być może będę musiał zrobić równość:
double a = SomeCalculation3();
double b = SomeCalculation4();
if (a == 0.0)
DoSomethingUseful(1 / a);
if (b == 0.0)
return 0; // or something else here
Krótko mówiąc, mam dużo matematyki zmiennoprzecinkowej i muszę robić różne porównania warunków. Nie mogę przekształcić tego w matematykę liczb całkowitych, ponieważ w tym kontekście taka rzecz jest bez znaczenia.
Czytałem wcześniej, że porównania zmiennoprzecinkowe mogą być niewiarygodne, ponieważ możesz mieć takie rzeczy:
double a = 1.0 / 3.0;
double b = a + a + a;
if ((3 * a) != b)
Console.WriteLine("Oh no!");
Krótko mówiąc, chciałbym wiedzieć: jak mogę wiarygodnie porównywać liczby zmiennoprzecinkowe (mniejsze niż, większe niż, równość)?
Zakres liczb, których używam, wynosi z grubsza od 10E-14 do 10E6, więc muszę pracować zarówno z małymi, jak i dużymi liczbami.
Oznaczyłem to jako język agnostyk, ponieważ interesuje mnie, jak mogę to osiągnąć bez względu na język, którego używam.
źródło
Odpowiedzi:
Porównywanie dla większego / mniejszego nie jest tak naprawdę problemem, chyba że pracujesz tuż przy granicy pływaka / podwójnej precyzji.
Aby porównać „rozmyte równa się”, to (kod Java powinien być łatwy do dostosowania) jest tym, co wymyśliłem dla Przewodnika zmiennoprzecinkowego po wielu pracach i biorąc pod uwagę wiele krytyki:
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; } }
Jest dostarczany z zestawem testowym. Powinieneś natychmiast odrzucić każde rozwiązanie, które tego nie robi, ponieważ praktycznie gwarantuje się niepowodzenie w niektórych skrajnych przypadkach, takich jak jedna wartość 0, dwie bardzo małe wartości przeciwne do zera lub nieskończoność.
Alternatywą (patrz link powyżej, aby uzyskać więcej informacji) jest konwersja wzorców bitowych elementów zmiennoprzecinkowych na liczbę całkowitą i akceptowanie wszystkiego w ustalonej odległości całkowitej.
W każdym razie prawdopodobnie nie ma rozwiązania, które byłoby idealne do wszystkich zastosowań. Idealnie byłoby, gdybyś opracował / dostosował własny zestaw testów obejmujący rzeczywiste przypadki użycia.
źródło
else if (a * b == 0)
, ale twój komentarz w tej samej linii jesta or b or both are zero
. Ale czy to nie są dwie różne rzeczy? Np. Jeślia == 1e-162
ib == 2e-162
wtedy waruneka * b == 0
będzie prawdziwy.abs(a-b)<eps
odpowiedzi tutaj. Dwa pytania: (1) Czy nie byłoby lepiej zmienić wszystkie<
s na<=
s, umożliwiając w ten sposób porównania „zero-eps”, równoważne porównaniom dokładnym? (2) Czy nie byłoby lepiej użyćdiff < epsilon * (absA + absB);
zamiastdiff / (absA + absB) < epsilon;
(ostatnia linia) -?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); }
Grafika, proszę?
Przy porównywaniu liczb zmiennoprzecinkowych istnieją dwa „tryby”.
Pierwszym z nich jest tryb względny , w którym różnica między
x
iy
jest rozpatrywana w stosunku do ich amplitudy|x| + |y|
. Przy rysowaniu w 2D daje następujący profil, gdzie kolor zielony oznacza równośćx
iy
. (Wziąłemepsilon
0,5 dla celów ilustracyjnych).Tryb względny jest używany dla „normalnych” lub „dostatecznie dużych” wartości zmiennoprzecinkowych. (Więcej o tym później).
Drugi to tryb absolutny , kiedy po prostu porównujemy ich różnicę do ustalonej liczby. Daje następujący profil (ponownie z
epsilon
0,5 irelth
1 dla ilustracji).Ten absolutny tryb porównania jest używany dla „małych” wartości zmiennoprzecinkowych.
Teraz pytanie brzmi, jak połączyć te dwa wzory odpowiedzi.
W odpowiedzi Michaela Borgwardta przełącznik opiera się na wartości
diff
, która powinna być poniżejrelth
(Float.MIN_NORMAL
w jego odpowiedzi). Ta strefa przełączania jest pokazana jako zakreskowana na poniższym wykresie.Ponieważ
relth * epsilon
jest mniejszyrelth
, zielone plamy nie sklejają się, co z kolei nadaje rozwiązaniu złą właściwość: możemy znaleźć trojaczki liczb takie,x < y_1 < y_2
a jednakx == y2
alex != y1
.Weźmy ten uderzający przykład:
x = 4.9303807e-32 y1 = 4.930381e-32 y2 = 4.9309825e-32
Mamy
x < y1 < y2
i faktyczniey2 - x
jest ponad 2000 razy większa niży1 - x
. A jednak przy obecnym rozwiązaniunearlyEqual(x, y1, 1e-4) == False nearlyEqual(x, y2, 1e-4) == True
Z kolei w rozwiązaniu zaproponowanym powyżej strefa przełączania bazuje na wartości
|x| + |y|
, która jest reprezentowana przez zakreskowany kwadrat poniżej. Zapewnia, że obie strefy łączą się wdzięcznie.Ponadto powyższy kod nie ma rozgałęzień, co mogłoby być bardziej wydajne. Weź pod uwagę, że operacje takie jak
max
iabs
, które a priori wymagają rozgałęzienia, często mają dedykowane instrukcje asemblacji. Z tego powodu uważam, że to podejście jest lepsze od innego rozwiązania, które polegałoby na naprawie MichaelanearlyEqual
poprzez zmianę przełącznika zdiff < relth
nadiff < eps * relth
, co spowodowałoby zasadniczo ten sam wzorzec odpowiedzi.Gdzie przełączać się między porównaniem względnym i bezwzględnym?
Przełączanie między tymi trybami odbywa się wokół
relth
, co jest przyjmowane tak, jakFLT_MIN
w zaakceptowanej odpowiedzi. Ten wybór oznacza, że reprezentacjafloat32
ogranicza precyzję naszych liczb zmiennoprzecinkowych.To nie zawsze ma sens. Na przykład, jeśli porównywane liczby są wynikiem odejmowania, być może coś z zakresu
FLT_EPSILON
ma większy sens. Jeśli są to pierwiastki kwadratowe z odejmowanych liczb, niedokładność liczbowa może być jeszcze większa.Jest to raczej oczywiste, gdy rozważasz porównanie liczb zmiennoprzecinkowych z
0
. Tutaj każde względne porównanie zakończy się niepowodzeniem, ponieważ|x - 0| / (|x| + 0) = 1
. Zatem porównanie musi zostać przełączone do trybu bezwzględnego, gdyx
jest na poziomie dokładności obliczeń - i rzadko jest tak niskie, jakFLT_MIN
.To jest powód wprowadzenia
relth
powyższego parametru.Ponadto, nie mnożąc
relth
przezepsilon
, interpretacja tego parametru jest prosta i odpowiada poziomowi dokładności numerycznej, którego oczekujemy od tych liczb.Matematyczne dudnienie
(trzymane tutaj głównie dla własnej przyjemności)
Bardziej ogólnie zakładam, że dobrze zachowujący się operator porównania zmiennoprzecinkowego
=~
powinien mieć kilka podstawowych właściwości.Oto raczej oczywiste:
a =~ a
a =~ b
implikujeb =~ a
a =~ b
implikuje-a =~ -b
(Nie mamy
a =~ b
ib =~ c
sugerujea =~ c
, że=~
nie jest to relacja równoważności).Dodałbym następujące właściwości, które są bardziej specyficzne dla porównań zmiennoprzecinkowych
a < b < c
, toa =~ c
implikujea =~ b
(bliższe wartości również powinny być równe)a, b, m >= 0
toa =~ b
oznaczaa + m =~ b + m
(większe wartości z tą samą różnicą również powinny być równe)0 <= λ < 1
toa =~ b
oznaczaλa =~ λb
(być może mniej oczywiste do argumentowania za).Te właściwości już dają silne ograniczenia dla możliwych funkcji bliskich równości. Weryfikuje je funkcja zaproponowana powyżej. Być może brakuje jednej lub kilku innych oczywistych właściwości.
Kiedy myślimy o
=~
rodzinie relacji równości=~[Ɛ,t]
sparametryzowanej przezƐ
irelth
, można również dodaćƐ1 < Ɛ2
toa =~[Ɛ1,t] b
implikujea =~[Ɛ2,t] b
(równość dla danej tolerancji oznacza równość przy wyższej tolerancji)t1 < t2
toa =~[Ɛ,t1] b
implikujea =~[Ɛ,t2] b
(równość dla danej niedokładności implikuje równość przy większej nieprecyzyjności)Zaproponowane rozwiązanie również je weryfikuje.
źródło
(std::abs(a) + std::abs(b))
kiedykolwiek może być większe niżstd::numeric_limits<float>::max()
?Miałem problem z porównaniem liczb zmiennoprzecinkowych
A < B
iA > B
oto, co wydaje się działać: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"); }
Fabryki - wartość bezwzględna - dba o to, czy są zasadniczo równe.
źródło
fabs
W ogóle nie musisz go używać , jeśli zrobisz pierwszy testif (A - B < -Epsilon)
Musimy wybrać poziom tolerancji, aby porównać liczby zmiennoprzecinkowe. Na przykład,
final float TOLERANCE = 0.00001; if (Math.abs(f1 - f2) < TOLERANCE) Console.WriteLine("Oh yes!");
Jedna uwaga. Twój przykład jest dość zabawny.
double a = 1.0 / 3.0; double b = a + a + a; if (a != b) Console.WriteLine("Oh no!");
Tutaj trochę matematyki
a = 1/3 b = 1/3 + 1/3 + 1/3 = 1. 1/3 != 1
O tak..
Czy masz na myśli
if (b != 1) Console.WriteLine("Oh no!")
źródło
Pomysł, który miałem do szybkiego porównania zmiennoprzecinkowego
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) }
źródło
Adaptacja do PHP na podstawie odpowiedzi Michaela Borgwardta i bosonixa:
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; } } } }
źródło
Powinieneś zadać sobie pytanie, dlaczego porównujesz liczby. Jeśli znasz cel porównania, powinieneś także znać wymaganą dokładność swoich liczb. To jest inne w każdej sytuacji i w każdym kontekście aplikacji. Ale w prawie wszystkich praktycznych przypadkach wymagana jest absolutna dokładność. Rzadko kiedy ma zastosowanie względna dokładność.
Oto przykład: jeśli Twoim celem jest narysowanie wykresu na ekranie, prawdopodobnie chcesz, aby wartości zmiennoprzecinkowe były równe, jeśli odwzorowują ten sam piksel na ekranie. Jeśli rozmiar twojego ekranu wynosi 1000 pikseli, a twoje liczby mieszczą się w zakresie 1e6, prawdopodobnie będziesz chciał porównać 100 równe 200.
Biorąc pod uwagę wymaganą absolutną dokładność, algorytm wygląda następująco:
źródło
Standardową radą jest użycie małej wartości „epsilon” (wybranej prawdopodobnie w zależności od aplikacji) i uznanie, że liczby zmiennoprzecinkowe znajdujące się w obrębie epsilon są równe. np. coś takiego
#define EPSILON 0.00000001 if ((a - b) < EPSILON && (b - a) < EPSILON) { printf("a and b are about equal\n"); }
Bardziej kompletna odpowiedź jest skomplikowana, ponieważ błąd zmiennoprzecinkowy jest niezwykle subtelny i mylący w rozumowaniu. Jeśli naprawdę zależy Ci na równości w jakimkolwiek konkretnym sensie, prawdopodobnie szukasz rozwiązania, które nie obejmuje liczb zmiennoprzecinkowych.
źródło
if ((a - b) < EPSILON/a && (b - a) < EPSILON/a)
c
, ponieważ gdy twoja liczba jest wystarczająco duża, EPSILON będzie mniejszy niż precyzja maszynyc
. Załóżmy na przykładc = 1E+22; d=c/3; e=d+d+d;
. Wtedye-c
może być znacznie większe niż 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 i c nie są równe według pnt i nelhage) lubdouble a = pow(10,-14); double b = a/2; std::cout<<std::scientific<<a-b;
(a i b równe według pnt i nelhage)Próbowałem napisać funkcję równości mając na uwadze powyższe komentarze. Oto, co wymyśliłem:
Edycja: zmiana z Math.Max (a, b) na 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); }
Myśli? Nadal muszę wypracować większe niż i mniejsze niż również.
źródło
epsilon
powinno byćMath.abs(Math.Max(a, b)) * Double.Epsilon;
, lub zawsze będzie mniejsze niżdiff
dla wartości ujemneja
ib
. I myślę, że twójepsilon
jest za mały, funkcja może nie zwracać niczego innego niż==
operator. Większy niż jesta < b && !fpEqual(a,b)
.Musisz wziąć pod uwagę, że błąd obcięcia jest względny. Dwie liczby są mniej więcej równe, jeśli ich różnica jest mniej więcej tak duża, jak ich ulp (jednostka na ostatnim miejscu).
Jednakże, jeśli wykonujesz obliczenia zmiennoprzecinkowe, twój potencjał błędu rośnie z każdą operacją (szczególnie ostrożnie przy odejmowaniu!), Więc twoja tolerancja błędu musi odpowiednio wzrosnąć.
źródło
Najlepszym sposobem porównania podwójnych liczb pod względem równości / nierówności jest wzięcie bezwzględnej wartości ich różnicy i porównanie jej z wystarczająco małą (w zależności od kontekstu) wartością.
double eps = 0.000000001; //for instance double a = someCalc1(); double b = someCalc2(); double diff = Math.abs(a - b); if (diff < eps) { //equal }
źródło