Jaki byłby najbardziej efektywny sposób porównania dwóch double
lub dwóch float
wartości?
Po prostu robienie tego jest nieprawidłowe:
bool CompareDoubles1 (double A, double B)
{
return A == B;
}
Ale coś takiego:
bool CompareDoubles2 (double A, double B)
{
diff = A - B;
return (diff < EPSILON) && (-diff < EPSILON);
}
Wydaje się do przetwarzania odpadów.
Czy ktoś zna mądrzejszego porównywania pływaka?
<invoke Knuth>
Przedwczesna optymalizacja jest źródłem wszelkiego zła.</invoke Knuth>
Po prostu skorzystaj z abs (ab) <EPS, jak wspomniano powyżej, jest to jasne i łatwe do zrozumienia.==
może być całkowicie poprawne, ale to całkowicie zależy od kontekstu nie podanego w pytaniu. Do czasu poznania tego kontekstu==
nadal pozostaje „najbardziej wydajnym sposobem” .Odpowiedzi:
Zachowaj szczególną ostrożność, korzystając z innych sugestii. Wszystko zależy od kontekstu.
Spędziłem dużo czasu na śledzeniu błędów w systemie, który zakładał, że
a==b
jeśli|a-b|<epsilon
. Podstawowymi problemami były:Domniemane domniemanie w algorytmie, że jeśli
a==b
ib==c
wtedya==c
.Korzystanie z tego samego epsilonu dla linii mierzonych w calach i linii mierzonych w milsach (0,001 cala). To jest
a==b
ale1000a!=1000b
. (Właśnie dlatego AlmostEqual2sComplement prosi o epsilon lub max ULPS).Zastosowanie tego samego epsilonu zarówno dla cosinusa kątów, jak i długości linii!
Korzystanie z takiej funkcji porównywania do sortowania elementów w kolekcji. (W tym przypadku użycie wbudowanego operatora C ++ == dla podwójnych dało poprawne wyniki.)
Tak jak powiedziałem: wszystko zależy od kontekstu i oczekiwanego rozmiaru
a
ib
.BTW,
std::numeric_limits<double>::epsilon()
to „maszyna epsilon”. Jest to różnica między 1,0 a następną wartością reprezentowaną przez podwójny. Myślę, że można go użyć w funkcji porównania, ale tylko wtedy, gdy oczekiwane wartości są mniejsze niż 1. (Jest to odpowiedź na odpowiedź @ cdv ...)Ponadto, jeśli w zasadzie masz
int
arytmetykędoubles
(tutaj używamy podwójnych wartości do utrzymania wartości int w niektórych przypadkach), twoja arytmetyka będzie poprawna. Na przykład 4.0 / 2.0 będzie taki sam jak 1.0 + 1.0. Tak długo, jak nie robisz rzeczy, które powodują frakcje (4.0 / 3.0) lub nie wychodzą poza rozmiar int.źródło
fabs(a)+fabs(b)
ale z kompensacją NaN, sumy 0 i przelewu, staje się to dość skomplikowane.float
/double
to MANTISSA x 2 ^ EXP .epsilon
będzie zależeć od wykładnika. Na przykład jeśli mantysa ma 24 bity, a wykładnik ma 8 bitów, to dla niektórych wartości jest1/(2^24)*2^127
lub ; czy to odnosi się do minimalnego epsilon ?~2^103
epsilon
|a-b|<epsilon
, to nie jest poprawne. Dodaj ten link do swojej odpowiedzi; jeśli zgadzasz się z cygnus-software.com/papers/comparingfloats/comparingfloats.htm i mogę usunąć moje głupie komentarze.Porównanie z wartością epsilon jest tym, co robi większość ludzi (nawet w programowaniu gier).
Powinieneś jednak nieco zmienić swoją implementację:
Edycja: Christer dodał stos świetnych informacji na ten temat w ostatnim poście na blogu . Cieszyć się.
źródło
float a = 3.4; if(a == 3.4){...}
tzn. Kiedy porównujesz zapisany zmiennoprzecinkowy z literałem | W takim przypadku obie liczby są przechowywane, więc będą miały taką samą reprezentację, jeśli będą równe, więc jaka szkoda to zrobića == b
?EPSILON
jest zdefiniowany jakoDBL_EPSILON
. Zwykle będzie to konkretna wartość wybierana w zależności od wymaganej dokładności porównania.EPSILON
porównanie nie działa, gdy zmiennoprzecinkowe są duże, ponieważ różnica między kolejnymi zmiennoprzecinkowymi również staje się duża. Zobacz ten artykuł .EPSILON
jest prawie bezużyteczne. Musisz porównać z progiem, który ma sens dla dostępnych jednostek. Użyj również,std::abs
ponieważ jest przeciążony dla różnych typów zmiennoprzecinkowych.Odkryłem, że platforma testowa Google C ++ zawiera ładną wieloplatformową implementację AlmostEqual2sComplement, która działa zarówno na podwójnych, jak i zmiennoprzecinkowych. Biorąc pod uwagę, że jest on wydany na licencji BSD, użycie go we własnym kodzie nie powinno stanowić problemu, pod warunkiem zachowania licencji. Wyodrębniłem poniższy kod z
http://code.google.com/p/googletest/source/browse/trunk/include/gtest/internal/gtest-internal.hhttps://github.com/google/googletest/blob /master/googletest/include/gtest/internal/gtest-internal.h i dodał licencję na górze.Pamiętaj, aby # zdefiniować GTEST_OS_WINDOWS do pewnej wartości (lub zmienić kod, w którym jest on przyzwyczajony, na coś, co pasuje do twojej bazy kodowej - w końcu jest to licencja BSD).
Przykład użycia:
Oto kod:
EDYCJA: Ten post ma 4 lata. Prawdopodobnie jest nadal poprawny, a kod jest fajny, ale niektórzy znaleźli ulepszenia. Najlepiej skorzystaj z najnowszej wersji
AlmostEquals
kodu źródłowego Google Test, a nie tej, którą tutaj wkleiłem.źródło
Porównanie liczb zmiennoprzecinkowych dla zależy od kontekstu. Ponieważ nawet zmiana kolejności operacji może dawać różne wyniki, ważne jest, aby wiedzieć, jak „równe” mają być liczby.
Porównywanie liczb zmiennoprzecinkowych przez Bruce'a Dawsona jest dobrym miejscem do rozpoczęcia, patrząc na porównanie liczb zmiennoprzecinkowych.
Poniższe definicje pochodzą ze sztuki programowania komputerowego Knutha :
Oczywiście wybór epsilon zależy od kontekstu i określa, jak równe mają być liczby.
Inną metodą porównywania liczb zmiennoprzecinkowych jest spojrzenie na ULP (jednostki na ostatnim miejscu) liczb. Nie zajmując się konkretnie porównaniami, artykuł To, co każdy informatyk powinien wiedzieć o liczbach zmiennoprzecinkowych, jest dobrym źródłem informacji na temat tego, jak działa zmiennoprzecinkowy i jakie są pułapki, w tym czym jest ULP.
źródło
fabs(a - b) <= ( (fabs(a) < fabs(b) ? fabs(b) : fabs(a)) * epsilon);
uratował mi życie. LOL Zauważ, że ta wersja (nie sprawdziłem, czy dotyczy również innych) również uwzględnia zmianę, która może wystąpić w integralnej części liczby zmiennoprzecinkowej (przykład:2147352577.9999997616 == 2147352576.0000000000
gdzie wyraźnie widać, że istnieje prawie różnica między2
między dwiema liczbami), co jest całkiem miłe! Dzieje się tak, gdy skumulowany błąd zaokrąglenia przepełnia dziesiętną część liczby.std::max(std::abs(a), std::abs(b))
(lub zstd::min()
);std::abs
w C ++ jest przeciążony typami float i double, więc działa dobrze (zawsze możesz zachowaćfabs
czytelność).Aby uzyskać bardziej szczegółowe podejście, przeczytaj Porównanie liczb zmiennoprzecinkowych . Oto fragment kodu z tego linku:
źródło
*(int*)&A;
” naruszy ścisłą zasadę aliasingu?Uświadomienie sobie, że jest to stary wątek, ale ten artykuł jest jednym z najprostszych, jakie znalazłem na temat porównywania liczb zmiennoprzecinkowych, a jeśli chcesz dowiedzieć się więcej, ma również bardziej szczegółowe odniesienia, a strona główna zawiera pełny zakres zagadnień radzenie sobie z liczbami zmiennoprzecinkowymi Przewodnik zmiennoprzecinkowy: porównanie .
Możemy znaleźć nieco bardziej praktyczny artykuł w zmienionym zmiennym punkcie tolerancji i zauważa, że istnieje test absolutnej tolerancji , który sprowadza się do tego w C ++:
i test tolerancji względnej :
Notatki artykule, że bezwzględna test nie powiedzie się, gdy
x
iy
są duże i nie we względnym przypadku, gdy są one małe. Zakładając, że tolerancja bezwzględna i względna jest taka sama, test łączony wyglądałby tak:źródło
Przenośnym sposobem na uzyskanie epsilon w C ++ jest
Wtedy staje się funkcja porównania
źródło
Skończyłem spędzać sporo czasu na przeglądaniu materiału w tym wielkim wątku. Wątpię, czy wszyscy chcą spędzać tak dużo czasu, dlatego chciałbym podkreślić podsumowanie tego, czego się nauczyłem i rozwiązania, które wdrożyłem.
Szybkie podsumowanie
numeric_limits::epsilon()
taki sam, jak FLT_EPSILON w float.h. Jest to jednak problematyczne, ponieważ epsilon do porównywania wartości takich jak 1.0 nie jest taki sam jak epsilon dla wartości takich jak 1E9. FLT_EPSILON jest zdefiniowany dla 1.0.fabs(a-b) <= epsilon
nie działa jednak, ponieważ domyślna epsilon jest zdefiniowana dla 1.0. Musimy skalować epsilon w górę lub w dół w kategoriach a i b.max(a,b)
albo możesz uzyskać kolejne reprezentatywne liczby wokół a, a następnie sprawdź, czy b mieści się w tym zakresie. Ten pierwszy nazywa się metodą „względną”, a później nazywa się metodą ULP.Implementacja funkcji narzędziowych (C ++ 11)
źródło
isDefinitelyLessThan
kontrolediff < tolerance
, co oznacza, że aib są prawie równe (a więc a nie jest zdecydowanie mniejsze niż b). Czy nie ma większego sensu sprawdzanie różnicy tolerancji w obu przypadkach? Lub może dodaćorEqualTo
argument, który kontroluje, czy przybliżona kontrola równości powinna zwrócić wartość true, czy nie.Napisany kod jest nieprawidłowy:
Poprawny kod to:
(... i tak, to jest inne)
Zastanawiam się, czy fabs nie sprawi, że w niektórych przypadkach stracisz leniwą ocenę. Powiedziałbym, że to zależy od kompilatora. Możesz spróbować obu. Jeśli są one średnio równoważne, weź implementację z fabs.
Jeśli masz jakieś informacje na temat tego, który z dwóch pływaków jest bardziej prawdopodobny niż inny, możesz grać w kolejności porównania, aby lepiej wykorzystać leniwą ocenę.
Wreszcie możesz uzyskać lepszy wynik, wprowadzając tę funkcję. Prawdopodobnie nie poprawi się znacznie ...
Edycja: OJ, dziękuję za poprawienie kodu. Odpowiednio usunąłem swój komentarz
źródło
Jest to w porządku, jeśli:
Ale w przeciwnym razie popadniesz w kłopoty. Liczby o podwójnej precyzji mają rozdzielczość około 16 miejsc po przecinku. Jeśli dwie liczby, które porównujesz, są większe niż EPSILON * 1.0E16, możesz równie dobrze powiedzieć:
Zbadam inne podejście, które zakłada, że musisz się martwić o pierwszy problem i zakładam, że drugi jest w porządku. Rozwiązaniem byłoby coś takiego:
Jest to drogie obliczeniowo, ale czasem wymaga tego. To właśnie musimy zrobić w mojej firmie, ponieważ mamy do czynienia z biblioteką inżynieryjną, a nakłady mogą się różnić o kilkadziesiąt rzędów wielkości.
W każdym razie chodzi o to (i dotyczy praktycznie każdego problemu programistycznego): oceń swoje potrzeby, a następnie wymyśl rozwiązanie odpowiadające twoim potrzebom - nie zakładaj, że łatwa odpowiedź zaspokoi twoje potrzeby. Jeśli po dokonaniu oceny okaże się, że
fabs(a-b) < EPSILON
to wystarczy, doskonale - skorzystaj z niego! Pamiętaj jednak o jego wadach i innych możliwych rozwiązaniach.źródło
Jak zauważyli inni, użycie epsilonu o stałym wykładniku (np. 0,0000001) będzie bezużyteczne dla wartości odbiegających od wartości epsilon. Na przykład, jeśli dwie wartości to 10000.000977 i 10000, to NIE 32-bitowych wartości zmiennoprzecinkowych między tymi dwiema liczbami - 10000 i 10000.000977 są tak bliskie, jak to tylko możliwe, bez bycia identycznym dla każdego bitu. Tutaj epsilon mniejszy niż 0,0009 jest bez znaczenia; równie dobrze możesz użyć prostego operatora równości.
Podobnie, gdy obie wartości zbliżają się do wielkości epsilon, błąd względny rośnie do 100%.
Zatem próba zmieszania stałej liczby punktów, takiej jak 0,00001 z wartościami zmiennoprzecinkowymi (gdzie wykładnik jest arbitralny), jest bezcelowym ćwiczeniem. To zadziała tylko wtedy, gdy będziesz mieć pewność, że wartości argumentu znajdują się w wąskiej domenie (to znaczy blisko jakiegoś konkretnego wykładnika) i jeśli właściwie wybierzesz wartość epsilon dla tego konkretnego testu. Jeśli wyciągniesz liczbę z powietrza („Hej! 0,00001 jest mały, więc to musi być dobry!”), Jesteś skazany na błędy numeryczne. Spędziłem dużo czasu na debugowaniu złego kodu numerycznego, w którym niektóre kiepskie tępaki rzucają losowymi wartościami epsilon, aby kolejny test zadziałał.
Jeśli wykonujesz programowanie numeryczne i uważasz, że musisz sięgnąć po epsilony o stałym punkcie, PRZECZYTAJ ARTYKUŁ BRUCE NA TEMAT PORÓWNANIA LICZB PUNKTOWYCH .
Porównywanie liczb zmiennoprzecinkowych
źródło
Qt implementuje dwie funkcje, być może możesz się z nich nauczyć:
Od tego czasu możesz potrzebować następujących funkcji
źródło
Ogólne porównanie liczb zmiennoprzecinkowych jest na ogół bez znaczenia. Sposób porównania naprawdę zależy od konkretnego problemu. W wielu problemach liczby są wystarczająco dyskrecjonalne, aby umożliwić porównanie ich w ramach danej tolerancji. Niestety jest tyle problemów, w których taka sztuczka tak naprawdę nie działa. Na przykład rozważ rozważenie funkcji Heaviside (krok) dla danej liczby (przychodzą na myśl cyfrowe opcje akcji), gdy obserwacje są bardzo blisko bariery. Przeprowadzenie porównania opartego na tolerancji nie przyniosłoby wiele dobrego, ponieważ skutecznie przeniósłoby problem z pierwotnej bariery na dwie nowe. Ponownie, nie ma ogólnego rozwiązania takich problemów, a konkretne rozwiązanie może wymagać posunięcia aż do zmiany metody numerycznej w celu osiągnięcia stabilności.
źródło
Niestety, nawet twój „marnotrawny” kod jest niepoprawny. EPSILON to najmniejsza wartość, którą można dodać do 1,0 i zmienić jej wartość. Wartość 1,0 jest bardzo ważna - większe liczby nie zmieniają się po dodaniu do EPSILON. Teraz możesz przeskalować tę wartość do liczb, które porównujesz, aby stwierdzić, czy są różne, czy nie. Prawidłowe wyrażenie do porównania dwóch podwójnych to:
To jest minimum. Ogólnie rzecz biorąc, chciałbyś uwzględnić szum w swoich obliczeniach i zignorować kilka najmniej znaczących bitów, więc bardziej realistyczne porównanie wyglądałoby następująco:
Jeśli wydajność porównania jest dla Ciebie bardzo ważna i znasz zakres swoich wartości, powinieneś zamiast tego użyć liczb stałych.
źródło
EPSILON
w pytaniu jestDBL_EPSILON
lubFLT_EPSILON
? Problem leży w twojej wyobraźni, gdzie podstawiłeśDBL_EPSILON
(co byłoby niewłaściwym wyborem) w kodzie, który go nie używał.Moja klasa na podstawie wcześniej opublikowanych odpowiedzi. Bardzo podobny do kodu Google, ale używam błędu, który popycha wszystkie wartości NaN powyżej 0xFF000000. To pozwala na szybsze sprawdzenie NaN.
Ten kod ma na celu zademonstrowanie koncepcji, a nie ogólne rozwiązanie. Kod Google już pokazuje, jak obliczyć wszystkie wartości specyficzne dla platformy i nie chciałem tego wszystkiego kopiować. Przeprowadziłem ograniczone testy tego kodu.
źródło
Oto dowód, że użycie
std::numeric_limits::epsilon()
nie jest odpowiedzią - nie powiedzie się dla wartości większych niż jeden:Dowód mojego komentarza powyżej:
Uruchomienie daje wynik:
Zauważ, że w drugim przypadku (jeden i tylko większy niż jeden), dwie wartości wejściowe są tak bliskie, jak to tylko możliwe, i nadal są porównywane jako niezamknięte. Tak więc, dla wartości większych niż 1.0, równie dobrze możesz po prostu użyć testu równości. Naprawione epsilony nie uratują cię podczas porównywania wartości zmiennoprzecinkowych.
źródło
return *(reinterpret_cast<double*>(&x));
chociaż zwykle działa, jest w rzeczywistości nieokreślonym zachowaniem.numeric_limits<>::epsilon
i punktu podłogowego IEEE 754.Znalazłem inną ciekawą implementację na: https://en.cppreference.com/w/cpp/types/numeric_limits/epsilon
źródło
Byłbym bardzo ostrożny wobec którejkolwiek z tych odpowiedzi, która obejmuje odejmowanie zmiennoprzecinkowe (np. Fabs (ab) <epsilon). Po pierwsze, liczby zmiennoprzecinkowe stają się bardziej rzadkie przy większych wielkościach i przy wystarczająco dużych wielkościach, w których odstępy są większe niż epsilon, równie dobrze możesz po prostu wykonać a == b. Po drugie, odejmowanie dwóch bardzo bliskich liczb zmiennoprzecinkowych (jak to zwykle bywa, biorąc pod uwagę, że szukasz prawie równości) jest dokładnie tym, jak dostajesz katastrofalne anulowanie .
Chociaż nie jest przenośny, myślę, że odpowiedź Grom najlepiej radzi sobie z unikaniem tych problemów.
źródło
a
ib
siebie. Nie ma absolutnie żadnego problemu z użyciem odejmowania zmiennoprzecinkowego jako części rozmytego porównania (chociaż jak powiedzieli inni, bezwzględna wartość epsilon może, ale nie musi być odpowiednia dla danego przypadku użycia)W oprogramowaniu numerycznym istnieją przypadki, w których chcesz sprawdzić, czy dwie liczby zmiennoprzecinkowe są dokładnie takie same. Wysłałem to na podobne pytanie
https://stackoverflow.com/a/10973098/1447411
Nie można więc powiedzieć, że „CompareDoubles1” jest ogólnie błędne.
źródło
To zależy od tego, jak dokładne ma być porównanie. Jeśli chcesz porównać dokładnie ten sam numer, po prostu idź z ==. (Prawie nigdy nie chcesz tego robić, chyba że faktycznie chcesz dokładnie tego samego numeru.) Na dowolnej przyzwoitej platformie możesz również wykonać następujące czynności:
ponieważ
fabs
wydaje się być dość szybki. Przez dość szybko mam na myśli, że jest to trochę bitowe ORAZ, więc lepiej być szybkim.A sztuczki na liczbach całkowitych do porównywania podwójnych i zmiennoprzecinkowych są fajne, ale zwykle utrudniają efektywną obsługę różnych potoków procesora. I na pewno nie jest to szybsze na niektórych architekturach w kolejności ze względu na użycie stosu jako tymczasowego obszaru przechowywania dla często używanych wartości. (Load-hit-store dla tych, którym zależy.)
źródło
Pod względem skali ilości:
Jeśli
epsilon
niewielki ułamek wielkości (tj. Wartość względna) w pewnym sensie fizycznymA
iB
typy są porównywalne w tym samym sensie, niż myślę, że następujące jest całkiem poprawne:źródło
Używam tego kodu:
źródło
epsilon
jest.epsilon
jest jedynie odległość między 1 a następnego numeru przedstawialnym po 1. w najlepszym razie, że kod jest po prostu staramy się, by sprawdzić, czy oba numery są dokładnie równe względem siebie, ale ponieważ nie-potęgi 2 są mnożone przezepsilon
, to nawet nie robi tego poprawnie.std::fabs(std::min(v1, v2))
jest niepoprawny - dla wejść ujemnych wybiera ten o większej wielkości.Piszę to dla java, ale może uznasz to za przydatne. Używa długich zamiast podwójnych, ale dba o NaN, podnormalne itp.
Należy pamiętać, że po wielu operacjach zmiennoprzecinkowych liczba może się znacznie różnić od oczekiwanych. Nie ma kodu, aby to naprawić.
źródło
Co powiesz na to?
Widziałem różne podejścia - ale nigdy tego nie widziałem, więc jestem ciekawy, czy mogę o nich komentować!
źródło
Użyłem tej funkcji do mojego małego projektu i działa, ale zwróć uwagę na następujące:
Błąd podwójnej precyzji może cię zaskoczyć. Powiedzmy, że epsilon = 1.0e-6, wtedy 1.0 i 1.000001 NIE powinny być uważane za równe zgodnie z powyższym kodem, ale na moim komputerze funkcja uważa je za równe, ponieważ 1,000001 nie może być dokładnie przetłumaczone na format binarny, jest to prawdopodobnie 1.0000009xxx. Testuję to z 1.0 i 1.0000011 i tym razem otrzymuję oczekiwany wynik.
źródło
To kolejne rozwiązanie z lambda:
źródło
Moja droga może być niepoprawna, ale przydatna
Konwertuj oba zmiennoprzecinkowe na ciągi, a następnie porównuj ciągi
można również wykonać nadpisywanie operatora
źródło
Nie można porównać dwóch
double
z ustalonymEPSILON
. W zależności od wartościdouble
,EPSILON
jest różna.Lepszym podwójnym porównaniem byłoby:
źródło
W bardziej ogólny sposób:
źródło
a
ib
są już mniejsze niżepsilon()
tam, różnica może być nadal znacząca. I odwrotnie, jeśli liczby są bardzo duże, to nawet kilka bitów błędu spowoduje, że porównanie się nie powiedzie, nawet jeśli chciałbyś, aby liczby były równe. Ta odpowiedź to dokładnie ten rodzaj „ogólnego” algorytmu porównania, którego chcesz uniknąć.Dlaczego nie wykonać bitowego XOR? Dwie liczby zmiennoprzecinkowe są równe, jeśli odpowiadające im bity są równe. Myślę, że decyzja o umieszczeniu bitów wykładniczych przed mantysą została podjęta w celu przyspieszenia porównania dwóch pływaków. Myślę, że wiele odpowiedzi tutaj nie ma sensu w porównaniu epsilon. Wartość Epsilon zależy tylko od tego, z jaką dokładnością porównywane są liczby zmiennoprzecinkowe. Na przykład po wykonaniu arytmetyki z liczbami zmiennoprzecinkowymi otrzymujesz dwie liczby: 2.5642943554342 i 2.5642943554345. Nie są one równe, ale dla rozwiązania liczą się tylko 3 cyfry dziesiętne, więc są one równe: 2,564 i 2,564. W takim przypadku wybierasz epsilon równy 0,001. Porównanie Epsilon jest również możliwe w bitowym XOR. Popraw mnie, jeśli się mylę.
źródło