Zgodnie z tą stroną java.sun ==
jest operatorem porównania równości dla liczb zmiennoprzecinkowych w Javie.
Jednak gdy wpisuję ten kod:
if(sectionID == currentSectionID)
do mojego edytora i uruchamiam analizę statyczną, otrzymuję: „JAVA0078 Wartości zmiennoprzecinkowe w porównaniu z ==”
Co jest złego w używaniu ==
do porównywania wartości zmiennoprzecinkowych? Jaki jest właściwy sposób, aby to zrobić?
java
equality
floating-accuracy
user128807
źródło
źródło
Odpowiedzi:
prawidłowy sposób testowania elementów zmiennoprzecinkowych pod kątem „równości” to:
gdzie epsilon to bardzo mała liczba, np. 0,00000001, w zależności od pożądanej precyzji.
źródło
if(Math.abs(sectionID - currentSectionID) < epsilon*sectionID
aby rozwiązać ten problem?Math.ulp()
w mojej odpowiedzi na to pytanie.Wartości zmiennoprzecinkowe mogą się nieco różnić, więc mogą nie być tak dokładnie równe. Na przykład, ustawiając wartość zmiennoprzecinkową na „6,1”, a następnie drukując ją ponownie, można otrzymać raportowaną wartość w rodzaju „6.099999904632568359375”. Jest to fundamentalne dla sposobu działania pływaków; dlatego nie chcesz ich porównywać za pomocą równości, ale raczej porównania w zakresie, to znaczy, jeśli różnica liczby zmiennoprzecinkowej do liczby, z którą chcesz ją porównać, jest mniejsza niż pewna wartość bezwzględna.
Ten artykuł w rejestrze daje dobry przegląd powodów takiego stanu rzeczy; przydatna i ciekawa lektura.
źródło
Tylko po to, aby podać powód tego, co mówią wszyscy inni.
Binarna reprezentacja pływaka jest trochę denerwująca.
W systemie binarnym większość programistów zna korelację między 1b = 1d, 10b = 2d, 100b = 4d, 1000b = 8d
Cóż, działa też w drugą stronę.
.1b = .5d, .01b = .25d, .001b = .125, ...
Problem polega na tym, że nie ma dokładnego sposobu przedstawienia większości liczb dziesiętnych, takich jak .1, .2, .3 itd. Wszystko, co możesz zrobić, to przybliżenie w systemie dwójkowym. System trochę zaokrągla, gdy drukowane są liczby, tak że wyświetla .1 zamiast .10000000000001 lub .999999999999 (które są prawdopodobnie tak samo zbliżone do zapisanej reprezentacji jak .1)
Edytuj z komentarza: Powodem, dla którego jest to problem, są nasze oczekiwania. W pełni spodziewamy się, że 2/3 zostanie sfałszowane w pewnym momencie, gdy zamienimy je na dziesiętne, albo .7, .67 lub .666667 .. Ale nie oczekujemy, że .1 zostanie automatycznie zaokrąglone w taki sam sposób, jak 2/3 - i to jest dokładnie to, co się dzieje.
Nawiasem mówiąc, jeśli jesteś ciekawy, liczba, którą przechowuje wewnętrznie, jest czystą reprezentacją binarną przy użyciu binarnej „notacji naukowej”. Więc jeśli powiesz mu, aby zapisał liczbę dziesiętną 10,75d, zapisze 1010b dla 10 i .11b dla dziesiętnej. Więc zapisze .101011, a następnie zapisuje kilka bitów na końcu, aby powiedzieć: Przenieś przecinek dziesiętny o cztery miejsca w prawo.
(Chociaż technicznie nie jest to już przecinek dziesiętny, jest to teraz punkt binarny, ale ta terminologia nie uczyniłaby rzeczy bardziej zrozumiałymi dla większości ludzi, którzy uznaliby tę odpowiedź za użyteczną).
źródło
Ponieważ to nieprawda
0.1 + 0.2 == 0.3
źródło
Float.compare(0.1f+0.2f, 0.3f) == 0
?Myślę, że wokół floatów (i dubletów) jest sporo zamieszania, dobrze jest to wyjaśnić.
Nie ma nic złego w używaniu liczb zmiennoprzecinkowych jako identyfikatorów w zgodnej ze standardami JVM [*]. Jeśli po prostu ustawisz identyfikator float na x, nic z nim nie rób (tj. Żadnych arytmetyki), a później przetestujesz y == x, wszystko będzie dobrze. Nie ma też nic złego w używaniu ich jako kluczy w HashMap. To, czego nie możesz zrobić, to zakładać równości, takie jak
x == (x - y) + y
itp. To powiedziawszy, ludzie zwykle używają typów całkowitych jako identyfikatorów i możesz zauważyć, że większość ludzi tutaj jest zniechęcona tym kodem, więc ze względów praktycznych lepiej jest przestrzegać konwencji . Zauważ, że jest tyle różnychdouble
wartości, ile jest długościvalues
, więc nic nie zyskujesz używającdouble
. Ponadto generowanie „następnego dostępnego identyfikatora” może być trudne w przypadku podwójnych i wymaga pewnej znajomości arytmetyki zmiennoprzecinkowej. Nie warto.Z drugiej strony opieranie się na liczbowej równości wyników dwóch matematycznie równoważnych obliczeń jest ryzykowne. Dzieje się tak z powodu błędów zaokrąglania i utraty precyzji podczas konwersji z reprezentacji dziesiętnej na dwójkową. Zostało to omówione na śmierć w SO.
[*] Kiedy powiedziałem „JVM zgodny ze standardami”, chciałem wykluczyć niektóre implementacje JVM z uszkodzeniem mózgu. Zobacz to .
źródło
==
zamiastequals
, albo też upewnić się, że żadna liczba zmiennoprzecinkowa, która porównuje ze sobą nierówne, nie zostanie zapisana w tabeli. W przeciwnym razie program, który próbuje np. Policzyć, ile unikatowych wyników może zostać wygenerowanych z wyrażenia po podaniu różnych danych wejściowych, może uznać każdą wartość NaN za unikalną.Float
, a nie dofloat
.Float
? Jeśli ktoś spróbuje zbudować tabelę unikalnychfloat
wartości i porównać je z==
okropnymi regułami porównania IEEE-754, spowoduje to zalanie tabeliNaN
wartościami.float
typ nie maequals
metody.equals
metody instancji, ale raczej metodę statyczną (chyba wFloat
klasie), która porównuje dwie wartości typufloat
.Jest to problem, który nie dotyczy języka java. Użycie == do porównania dwóch liczb zmiennoprzecinkowych / podwójnych / dowolnej liczby dziesiętnej może potencjalnie spowodować problemy ze względu na sposób ich przechowywania. Liczba zmiennoprzecinkowa pojedynczej precyzji (zgodnie ze standardem IEEE 754) ma 32 bity, rozmieszczone w następujący sposób:
1 bit - Znak (0 = dodatni, 1 = ujemny)
8 bitów - Wykładnik (specjalna (bias-127) reprezentacja xw 2 ^ x)
23 bity - Mantisa. Rzeczywisty numer, który jest przechowywany.
To właśnie modliszka powoduje problem. To trochę jak notacja naukowa, tylko liczba o podstawie 2 (binarnej) wygląda na 1,110011 x 2 ^ 5 lub coś podobnego. Ale w systemie binarnym pierwsza 1 to zawsze 1 (z wyjątkiem reprezentacji 0)
Dlatego, aby zaoszczędzić trochę miejsca w pamięci (gra słów zamierzona), IEEE zdecydowało, że należy założyć 1. Na przykład modliszka 1011 to tak naprawdę 1,1011.
Może to powodować pewne problemy z porównaniem, szczególnie w przypadku 0, ponieważ 0 nie może być dokładnie reprezentowane w zmiennej zmiennoprzecinkowej. Jest to główny powód, dla którego odradza się stosowanie ==, oprócz problemów z matematyką zmiennoprzecinkową opisanych w innych odpowiedziach.
Java ma wyjątkowy problem polegający na tym, że język jest uniwersalny na wielu różnych platformach, z których każda może mieć swój własny, unikalny format zmiennoprzecinkowy. Dlatego jeszcze ważniejsze jest unikanie ==.
Prawidłowy sposób porównywania dwóch zmiennych (umysł niezwiązanych z językiem) pod kątem równości jest następujący:
gdzie ACCEPTABLE_ERROR jest #defined lub inną stałą równą 0,000000001 lub inną wymaganą precyzją, jak wspomniał już Victor.
Niektóre języki mają wbudowaną tę funkcję lub tę stałą, ale generalnie jest to dobry zwyczaj.
źródło
Na dzień dzisiejszy szybki i łatwy sposób to:
Jednak dokumenty nie określają jasno wartości różnicy marginesów ( epsilon z odpowiedzi @Victor), która jest zawsze obecna w obliczeniach na liczbach zmiennoprzecinkowych, ale powinna być rozsądna, ponieważ jest częścią standardowej biblioteki językowej.
Jeśli jednak potrzebna jest wyższa lub dostosowana do potrzeb precyzja, to
to kolejna opcja rozwiązania.
źródło
(sectionId == currentSectionId)
co nie jest dokładne dla punktów zmiennoprzecinkowych. metoda epsilon jest lepszym podejściem, co jest w tej odpowiedzi: stackoverflow.com/a/1088271/4212710Wartości punktów spłaty nie są wiarygodne z powodu błędu zaokrąglenia.
W związku z tym prawdopodobnie nie powinny być używane jako wartości klucza, takie jak identyfikator sekcji. Zamiast tego użyj liczb całkowitych lub
long
jeśliint
nie zawiera wystarczającej liczby możliwych wartości.źródło
double
są znacznie dokładniejsze, ale są to również wartości zmiennoprzecinkowe, więc moja odpowiedź miała zawierać zarównofloat
idouble
.Oprócz wcześniejszych odpowiedzi powinieneś mieć świadomość, że istnieją dziwne zachowania związane z
-0.0f
i+0.0f
(są,==
ale nieequals
) iFloat.NaN
(jest,equals
ale nie==
) (mam nadzieję, że mam rację - argh, nie rób tego!).Edycja: Sprawdźmy!
Witamy w IEEE / 754.
źródło
==
liczb zmiennoprzecinkowych nie oznacza, że liczby są „identyczne z bitem” (ta sama liczba może być reprezentowana za pomocą różnych wzorów bitowych, chociaż tylko jeden z nich ma postać znormalizowaną). Jak dobrze,-0.0f
i0.0f
są reprezentowane przez różne wzorce bitowe (bit znaku jest inny), ale porównują jako równe z==
(ale nie zequals
). Twoje założenie, że==
jest to porównanie bitowe, jest ogólnie rzecz biorąc błędne.Oto bardzo długa (ale miejmy nadzieję, że użyteczna) dyskusja na temat tego i wielu innych problemów zmiennoprzecinkowych, z którymi możesz się spotkać: Co każdy informatyk powinien wiedzieć o arytmetyce zmiennoprzecinkowej
źródło
Możesz użyć Float.floatToIntBits ().
źródło
Po pierwsze, czy pływają czy pływają? Jeśli jednym z nich jest Float, należy użyć metody equals (). Ponadto prawdopodobnie najlepiej jest użyć statycznej metody Float.compare.
źródło
Następujące automatycznie używa najlepszej precyzji:
Oczywiście możesz wybrać więcej lub mniej niż 5 ULP („jednostka na ostatnim miejscu”).
Jeśli jesteś w bibliotece Apache Commons,
Precision
klasa macompareTo()
iequals()
z epsilon i ULP.źródło
double
to pokryć.możesz chcieć, żeby to było ==, ale 123.4444444444443! = 123.4444444444442
źródło
Jeśli * musisz * używać pływaków, przydatne może być słowo kluczowe strictfp.
http://en.wikipedia.org/wiki/strictfp
źródło
Dwa różne obliczenia, które dają równe liczby rzeczywiste, niekoniecznie dają równe liczby zmiennoprzecinkowe. Osoby, które używają == do porównywania wyników obliczeń, zwykle są tym zaskoczone, więc ostrzeżenie pomaga oznaczyć to, co w przeciwnym razie mogłoby być subtelnym i trudnym do odtworzenia błędem.
źródło
Czy masz do czynienia z zewnętrznym kodem, który używałby liczb zmiennoprzecinkowych do rzeczy o nazwach sectionID i currentSectionID? Po prostu ciekawy.
@Bill K: „Binarna reprezentacja liczby zmiennoprzecinkowej jest trochę denerwująca”. Jak to? Jak byś to zrobił lepiej? Istnieją pewne liczby, których nie można właściwie przedstawić w żadnej bazie, ponieważ nigdy się nie kończą. Pi jest dobrym przykładem. Możesz to tylko przybliżyć. Jeśli masz lepsze rozwiązanie, skontaktuj się z firmą Intel.
źródło
Jak wspomniano w innych odpowiedziach, gry podwójne mogą mieć niewielkie odchylenia. Mógłbyś napisać własną metodę porównywania ich przy użyciu „dopuszczalnego” odchylenia. Jednak ...
Istnieje klasa Apache do porównywania gier podwójnych: org.apache.commons.math3.util.Precision
Zawiera kilka interesujących stałych:
SAFE_MIN
iEPSILON
, które są maksymalnymi możliwymi odchyleniami prostych operacji arytmetycznych.Zapewnia również niezbędne metody porównywania, równych lub okrągłych gier podwójnych. (przy użyciu ulps lub bezwzględnego odchylenia)
źródło
W jednej linii odpowiedzi mogę powiedzieć, powinieneś użyć:
Aby dowiedzieć się więcej o prawidłowym używaniu operatorów pokrewnych, omówię tutaj kilka przypadków: Ogólnie rzecz biorąc, istnieją trzy sposoby testowania ciągów znaków w Javie. Możesz użyć ==, .equals () lub Objects.equals ().
Czym się różnią? == testuje jakość referencyjną w ciągach znaków, co oznacza sprawdzenie, czy te dwa obiekty są takie same. Z drugiej strony .equals () sprawdza logicznie, czy dwa łańcuchy mają taką samą wartość. Na koniec Objects.equals () sprawdza wszystkie wartości null w dwóch ciągach, a następnie określa, czy wywołać .equals ().
Idealny operator w użyciu
Cóż, było to przedmiotem wielu debat, ponieważ każdy z trzech operatorów ma swój unikalny zestaw mocnych i słabych stron. Na przykład == jest często preferowaną opcją podczas porównywania odwołań do obiektu, ale są przypadki, w których może się wydawać, że porównuje również wartości ciągów.
Jednak to, co dostajesz, jest wartością spadkową, ponieważ Java tworzy iluzję, że porównujesz wartości, ale w rzeczywistości tak nie jest. Rozważ dwa poniższe przypadki:
Przypadek 1:
Przypadek 2:
Dlatego lepiej jest użyć każdego operatora podczas testowania określonego atrybutu, dla którego został zaprojektowany. Jednak w prawie wszystkich przypadkach Objects.equals () jest operatorem bardziej uniwersalnym, dlatego też decydują się na to programiści.
Tutaj możesz uzyskać więcej informacji: http://fluentthemes.com/use-compare-strings-java/
źródło
Właściwy sposób byłby
źródło
Float.compare(1.1 + 2.2, 3.3) != 0
Jednym ze sposobów zmniejszenia błędu zaokrąglania jest użycie wartości double zamiast float. To nie sprawi, że problem zniknie, ale zmniejszy ilość błędów w programie, a float prawie nigdy nie jest najlepszym wyborem. MOIM ZDANIEM.
źródło