Powszechnie wiadomo, że porównywanie liczb zmiennoprzecinkowych w celu zapewnienia równości jest trochę kłopotliwe z powodu problemów z zaokrąglaniem i precyzją.
Na przykład: https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
Jaki jest zalecany sposób radzenia sobie z tym w Pythonie?
Na pewno jest gdzieś standardowa funkcja biblioteki?
python
floating-point
Gordon Wrigley
źródło
źródło
all
,any
,max
,min
to każdy w zasadzie jednej wkładki, i nie są one przewidziane tylko w bibliotece, są wbudowane funkcje. Powody BDFL nie są takie. Jedna linia kodu, którą większość ludzi pisze, jest dość niewyszukana i często nie działa, co jest silnym powodem, aby zapewnić coś lepszego. Oczywiście każdy moduł zapewniający inne strategie musiałby również zawierać zastrzeżenia opisujące, kiedy są odpowiednie, a co ważniejsze, gdy nie są. Analiza numeryczna jest trudna, nie jest wielką hańbą, że projektanci języków zwykle nie próbują narzędzi, które mogłyby w tym pomóc.Odpowiedzi:
Python 3.5 dodaje funkcje
math.isclose
icmath.isclose
zgodnie z opisem w PEP 485 .Jeśli używasz wcześniejszej wersji Pythona, równoważna funkcja jest podana w dokumentacji .
rel_tol
jest względną tolerancją, pomnożoną przez większą z wielkości dwóch argumentów; wraz ze wzrostem wartości rośnie dozwolona różnica między nimi, jednocześnie uznając je za równe.abs_tol
jest bezwzględną tolerancją, którą stosuje się w obecnej postaci we wszystkich przypadkach. Jeśli różnica jest mniejsza niż jedna z tych tolerancji, wartości są uważane za równe.źródło
a
lubb
jest anumpy
array
,numpy.isclose
działa.rel_tol
jest względną tolerancją , pomnożoną przez większą z wielkości dwóch argumentów; wraz ze wzrostem wartości rośnie dozwolona różnica między nimi, jednocześnie uznając je za równe.abs_tol
jest bezwzględną tolerancją, którą stosuje się w obecnej postaci we wszystkich przypadkach. Jeśli różnica jest mniejsza niż jedna z tych tolerancji, wartości są uważane za równe.isclose
funkcja (powyżej) nie jest pełną implementacją.isclose
zawsze spełnia ono mniej konserwatywne kryterium. Wspominam o tym tylko dlatego, że takie zachowanie jest dla mnie sprzeczne z intuicją. Gdybym określił dwa kryteria, zawsze oczekiwałbym, że mniejsza tolerancja zastąpi większą.Czy coś tak prostego jak poniższe nie jest wystarczająco dobre?
źródło
abs(f1-f2) < tol*max(abs(f1),abs(f2))
. Ten rodzaj tolerancji względnej jest jedynym sensownym sposobem na porównanie pływaków w ogóle, ponieważ zwykle występuje w nich błąd zaokrągleń w małych miejscach dziesiętnych.>>> abs(0.04 - 0.03) <= 0.01
:, dajeFalse
. UżywamPython 2.7.10 [GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
abs(f1 - f2) <= allowed_error
nie działa zgodnie z oczekiwaniami.Zgadzam się, że odpowiedź Garetha jest prawdopodobnie najbardziej odpowiednia jako lekka funkcja / rozwiązanie.
Pomyślałem jednak, że przydałoby się zauważyć, że jeśli używasz NumPy lub zastanawiasz się nad tym, istnieje do tego funkcja spakowana.
Małe zastrzeżenie: instalacja NumPy może być łatwym doświadczeniem w zależności od platformy.
źródło
pip
Windows.Użyj
decimal
modułu Pythona , który udostępniaDecimal
klasę.Z komentarzy:
źródło
Nie znam niczego w standardowej bibliotece Pythona (ani gdzie indziej), która implementuje
AlmostEqual2sComplement
funkcję Dawsona . Jeśli chcesz tego rodzaju zachowanie, musisz je wdrożyć samodzielnie. (W takim przypadku, zamiast używać sprytnych bitowych hacków Dawsona, prawdopodobnie lepiej byłoby użyć bardziej konwencjonalnych testów formularzaif abs(a-b) <= eps1*(abs(a)+abs(b)) + eps2
lub podobnego. Aby uzyskać zachowanie podobne do Dawsona, możesz powiedzieć cośif abs(a-b) <= eps*max(EPS,abs(a),abs(b))
w stylu małej poprawkiEPS
; to nie jest dokładnie tak samo jak Dawson, ale ma podobny duch.źródło
eps1
ieps2
zdefiniuj względną i bezwzględną tolerancję: jesteś gotów pozwolića
ib
różnić się oeps1
czasy, o ile są one większeeps2
.eps
jest pojedynczą tolerancją; jesteś przygotowany, aby pozwolića
ib
różnić się w przybliżeniueps
czasami, jak duże są, z zastrzeżeniem, żeEPS
zakłada się , że coś wielkości lub mniejszy jest wielkościEPS
. Jeśli weźmieszEPS
pod uwagę najmniejszą niedenormalną wartość typu zmiennoprzecinkowego, jest to bardzo podobne do komparatora Dawsona (z wyjątkiem współczynnika 2 ^ # bitów, ponieważ Dawson mierzy tolerancję w ulps).Powszechna mądrość, że liczb zmiennoprzecinkowych nie można porównywać dla równości, jest niedokładna. Liczby zmiennoprzecinkowe nie różnią się od liczb całkowitych: Jeśli ocenisz „a == b”, otrzymasz prawdę, jeśli są to liczby identyczne, a fałsz w przeciwnym razie (przy założeniu, że dwa NaN nie są oczywiście liczbami identycznymi).
Rzeczywisty problem jest następujący: jeśli wykonałem kilka obliczeń i nie jestem pewien, czy dwie liczby, które muszę porównać, są dokładnie poprawne, to co? Ten problem jest taki sam dla liczb zmiennoprzecinkowych, jak dla liczb całkowitych. Jeśli ocenisz wyrażenie całkowite „7/3 * 3”, nie będzie ono porównywane z „7 * 3/3”.
Załóżmy więc, że zapytaliśmy „Jak porównać liczby całkowite w celu zapewnienia równości?” w takiej sytuacji. Nie ma jednej odpowiedzi; to, co powinieneś zrobić, zależy od konkretnej sytuacji, w szczególności od rodzaju błędów i tego, co chcesz osiągnąć.
Oto kilka możliwych opcji.
Jeśli chcesz uzyskać „prawdziwy” wynik, jeśli matematycznie dokładne liczby byłyby równe, możesz spróbować użyć właściwości obliczeń, które wykonujesz, aby udowodnić, że otrzymujesz te same błędy w tych dwóch liczbach. Jeśli jest to wykonalne, a porównasz dwie liczby, które wynikają z wyrażeń, które dałyby równe liczby, jeśli zostałyby dokładnie obliczone, uzyskasz „prawdziwość” z porównania. Inne podejście polega na tym, że możesz przeanalizować właściwości obliczeń i udowodnić, że błąd nigdy nie przekracza określonej kwoty, być może kwoty bezwzględnej lub kwoty odnoszącej się do jednego z danych wejściowych lub jednego z danych wyjściowych. W takim przypadku możesz zapytać, czy dwie obliczone liczby różnią się co najwyżej o tę kwotę, i zwrócić „prawda”, jeśli mieszczą się w przedziale. Jeśli nie możesz udowodnić błędu, możesz zgadywać i mieć nadzieję na najlepsze. Jednym ze sposobów zgadywania jest ocena wielu losowych próbek i sprawdzenie, jaki rozkład otrzymujesz w wynikach.
Oczywiście, ponieważ ustaliliśmy wymóg, abyś był „prawdziwy”, jeśli matematycznie dokładne wyniki są równe, pozostawiliśmy otwartą możliwość, abyś był „prawdziwy”, nawet jeśli są nierówne. (W rzeczywistości możemy spełnić ten wymóg, zawsze zwracając „prawda”. To sprawia, że obliczenia są proste, ale generalnie niepożądane, dlatego omówię poprawę sytuacji poniżej.)
Jeśli chcesz uzyskać „fałszywy” wynik, jeśli matematyczne dokładne liczby byłyby nierówne, musisz udowodnić, że twoja ocena liczb daje różne liczby, jeśli matematyczne dokładne liczby byłyby nierówne. Może to być niemożliwe ze względów praktycznych w wielu typowych sytuacjach. Rozważmy więc alternatywę.
Przydatnym wymogiem może być uzyskanie „fałszywego” wyniku, jeśli matematyczne dokładne liczby różnią się o więcej niż pewną liczbę. Na przykład być może obliczymy, gdzie wędrowała piłka rzucona w grę komputerową, i chcemy wiedzieć, czy uderzyła w nietoperza. W tym przypadku z pewnością chcemy uzyskać „prawdziwą”, jeśli piłka uderzy w nietoperza, a także „fałszywą”, jeśli piłka jest daleko od nietoperza, i możemy zaakceptować niepoprawną „prawdziwą” odpowiedź, jeśli piłka matematycznie dokładna symulacja nie trafiła nietoperza, ale znajduje się w odległości milimetra od uderzenia nietoperza. W takim przypadku musimy udowodnić (lub zgadnąć / oszacować), że nasze obliczenia pozycji piłki i pozycji nietoperza mają łączny błąd co najwyżej jednego milimetra (dla wszystkich pozycji zainteresowania). To pozwoli nam zawsze wracać ”
To, jak zdecydujesz, co zwrócić, porównując liczby zmiennoprzecinkowe, zależy w dużej mierze od konkretnej sytuacji.
Jeśli chodzi o sprawdzanie granic błędów w obliczeniach, może to być skomplikowany temat. Każda implementacja zmiennoprzecinkowa wykorzystująca standard IEEE 754 w trybie zaokrąglania do najbliższego zwraca liczbę zmiennoprzecinkową najbliższą dokładnemu wynikowi dla dowolnej podstawowej operacji (w szczególności mnożenie, dzielenie, dodawanie, odejmowanie, pierwiastek kwadratowy). (W przypadku remisu, zaokrąglenia, tak więc niski bit jest parzysty.) (Zachowaj szczególną ostrożność przy pierwiastku kwadratowym i dzieleniu; twoja implementacja języka może używać metod, które nie są zgodne z IEEE 754). Z tego powodu wiemy, że błąd w pojedynczym wyniku wynosi najwyżej 1/2 wartości najmniej znaczącego bitu. (Gdyby było więcej, zaokrąglenie byłoby ustawione na inną liczbę, która mieści się w zakresie 1/2 wartości.)
Dalsza praca staje się znacznie bardziej skomplikowana; następnym krokiem jest wykonanie operacji, w której jedno z wejść ma już jakiś błąd. W przypadku prostych wyrażeń błędy te można śledzić przez obliczenia, aby osiągnąć granicę błędu końcowego. W praktyce odbywa się to tylko w kilku sytuacjach, takich jak praca nad wysokiej jakości biblioteką matematyczną. I oczywiście potrzebujesz dokładnej kontroli dokładnie nad tym, które operacje są wykonywane. Języki wysokiego poziomu często dają kompilatorowi dużo luzu, więc możesz nie wiedzieć, w jakiej kolejności są wykonywane operacje.
Jest o wiele więcej, co można napisać (i jest) na ten temat, ale muszę się na tym zatrzymać. Podsumowując, odpowiedź brzmi: nie ma procedury bibliotecznej dla tego porównania, ponieważ nie ma jednego rozwiązania, które byłoby w stanie zaspokoić większość potrzeb, które byłoby warte zastosowania w bibliotece. (Jeśli porównanie z przedziałem błędu względnego lub bezwzględnego wystarcza, możesz to zrobić po prostu bez procedury bibliotecznej).
źródło
(7/3*3 == 7*3/3)
. WydrukowanoFalse
.from __future__ import division
. Jeśli tego nie zrobisz, nie ma liczb zmiennoprzecinkowych, a porównanie dotyczy dwóch liczb całkowitych.Jeśli chcesz używać go w kontekście testowania / TDD, powiedziałbym, że jest to standardowy sposób:
źródło
math.isclose () został w tym celu dodany do Pythona 3.5 ( kod źródłowy ). Oto jego port dla Pythona 2. Różnica w stosunku do jednowierszowej Mark Ransom polega na tym, że może on poprawnie obsługiwać „inf” i „-inf”.
źródło
Pomocne okazało się następujące porównanie:
źródło
str(.1 + .2) == str(.3)
zwraca False. Metoda opisana powyżej działa tylko dla Pythona 2.W niektórych przypadkach, w których możesz wpłynąć na reprezentację numeru źródłowego, możesz przedstawić je jako ułamki zamiast liczb zmiennoprzecinkowych, używając liczb całkowitych i mianowników. W ten sposób możesz mieć dokładne porównania.
Aby uzyskać szczegółowe informacje, zobacz Moduł Frakcja z ułamków.
źródło
Podobała mi się sugestia @Sesquipedal, ale z modyfikacją (specjalny przypadek użycia, gdy obie wartości wynoszą 0, zwraca False). W moim przypadku korzystałem z Pythona 2.7 i użyłem prostej funkcji:
źródło
Przydatne w przypadku, gdy chcesz się upewnić, że 2 liczby są takie same „do precyzji”, nie musisz określać tolerancji:
Znajdź minimalną precyzję 2 liczb
Zaokrąglij oba z nich do minimalnej precyzji i porównaj
Jak napisano, działa tylko dla liczb bez „e” w ich reprezentacji ciągu (co oznacza 0,9999999999995e-4 <liczba <= 0,9999999999995e11)
Przykład:
źródło
isclose(1.0, 1.1)
produkujeFalse
iisclose(0.1, 0.000000000001)
zwracaTrue
.Aby porównać do podanego miejsca po przecinku bez
atol/rtol
:źródło
To może być trochę brzydki hack, ale działa całkiem dobrze, gdy nie potrzebujesz więcej niż domyślna precyzja float (około 11 miejsc po przecinku).
Funkcja round_to używa metody formatowania z wbudowanej klasy str, aby zaokrąglić liczbę zmiennoprzecinkową do ciągu, który reprezentuje liczbę zmiennoprzecinkową z wymaganą liczbą miejsc po przecinku, a następnie stosuje wbudowaną funkcję eval do zaokrąglonego ciągu zmiennoprzecinkowego, aby wrócić na zmiennoprzecinkowy typ liczbowy.
Funkcja is_close stosuje prosty warunek do zaokrąglonej liczby zmiennoprzecinkowej.
Aktualizacja:
Jak sugeruje @stepehjfox, czystszym sposobem na zbudowanie funkcji rount_to unikającej „eval” jest użycie zagnieżdżonego formatowania :
Zgodnie z tym samym pomysłem kod może być jeszcze prostszy przy użyciu świetnych nowych ciągów F (Python 3.6+):
Moglibyśmy nawet wszystko to zawrzeć w jednej prostej i czystej funkcji „is_close” :
źródło
eval()
aby uzyskać sparametryzowane formatowanie. Coś jakreturn '{:.{precision}f'.format(float_num, precision=decimal_precision)
powinno to zrobićreturn '{:.{precision}}f'.format(float_num, precision=decimal_precision)
Jeśli chodzi o błąd bezwzględny, możesz po prostu sprawdzić
Niektóre informacje o tym, dlaczego float działa tak dziwnie w Pythonie https://youtu.be/v4HhvoNLILk?t=1129
Możesz także użyć math.isclose do względnych błędów
źródło