Wiem, że 0.1
liczby dziesiętnej nie można dokładnie przedstawić za pomocą skończonej liczby binarnej ( wyjaśnienie ), więc double n = 0.1
straci pewną precyzję i nie będzie dokładnie 0.1
. Z drugiej strony 0.5
może być reprezentowany właśnie dlatego, że jest 0.5 = 1/2 = 0.1b
.
Powiedziawszy, że jest zrozumiałe, 0.1
trzykrotne dodanie nie da dokładnie, 0.3
więc wypisuje następujący kod false
:
double sum = 0, d = 0.1;
for (int i = 0; i < 3; i++)
sum += d;
System.out.println(sum == 0.3); // Prints false, OK
Ale jak to się dzieje, że dodanie 0.1
pięciu razy da dokładnie 0.5
? Następujący kod drukuje true
:
double sum = 0, d = 0.1;
for (int i = 0; i < 5; i++)
sum += d;
System.out.println(sum == 0.5); // Prints true, WHY?
Jeśli 0.1
nie można go dokładnie przedstawić, jak to się dzieje, że dodanie go 5 razy daje dokładnie to, 0.5
co można dokładnie przedstawić?
sum
miała taką samą wartość końcową, jak gdyby pętla została naprawdę wykonana. W standardzie C ++ nazywa się to „regułą as-if” lub „tym samym obserwowalnym zachowaniem”.Odpowiedzi:
Błąd zaokrąglania nie jest przypadkowy i sposób jego implementacji stara się zminimalizować błąd. Oznacza to, że czasami błąd nie jest widoczny lub nie ma błędu.
Na przykład
0.1
nie jest dokładnie0.1
tj.,new BigDecimal("0.1") < new BigDecimal(0.1)
Ale0.5
jest dokładnie1.0/2
Ten program pokazuje prawdziwe wartości.
wydruki
Uwaga: to
0.3
jest nieco wyłączone, ale kiedy dojdziesz do0.4
bitów, musisz przesunąć o jeden w dół, aby zmieścić się w 53-bitowym limicie, a błąd jest odrzucany. Ponownie, skrada się do tyłu o błędach0.6
i0.7
lecz0.8
się1.0
błąd zostanie odrzucony.Przyczyną wystąpienia błędu jest ograniczona precyzja. tj. 53-bity. Oznacza to, że ponieważ liczba zużywa więcej bitów, gdy staje się większa, bity muszą być odrzucane z końca. Powoduje to zaokrąglanie, które w tym przypadku jest na Twoją korzyść.
Możesz uzyskać odwrotny efekt, gdy uzyskasz mniejszą liczbę, np.
0.1-0.0999
=>1.0000000000000286E-4
I zobaczysz więcej błędów niż wcześniej.Przykładem tego jest dlaczego w Javie 6 Dlaczego Math.round (0.49999999999999994) zwraca 1 W tym przypadku utrata bitu w obliczeniach skutkuje dużą różnicą w odpowiedzi.
źródło
strictfp
Time do rozważenia liczb całkowitych o stałym punkcie. (lub BigDecimal)Zakaz przepełnienia, w zmiennoprzecinkowym,
x + x + x
jest dokładnie zaokrągloną (tj. Najbliższą) liczbą zmiennoprzecinkową do rzeczywistej 3 *x
,x + x + x + x
wynosi dokładnie 4 *x
ix + x + x + x + x
ponownie jest poprawnie zaokrąglonym przybliżeniem zmiennoprzecinkowym dla 5 *x
.Pierwszy wynik
x + x + x
wynika z faktu, żex + x
jest dokładny.x + x + x
jest zatem wynikiem tylko jednego zaokrąglenia.Drugi wynik jest trudniejszy, jeden z jego wykazów omówiono tutaj (a Stephen Canon nawiązuje do innego dowodu poprzez analizę przypadku na ostatnich 3 cyfrach
x
). Podsumowując, albo 3 *x
jest w tej samej binadzie co 2 *x
albo jest w tej samej binadzie co 4 *x
, iw każdym przypadku można wywnioskować, że błąd trzeciego dodawania anuluje błąd drugiego dodawania ( pierwszy dodatek jest dokładny, jak już powiedzieliśmy).Trzeci wynik, „
x + x + x + x + x
jest prawidłowo zaokrąglony”, pochodzi z drugiego w taki sam sposób, w jaki pierwszy wynika z dokładnościx + x
.Drugi wynik wyjaśnia, dlaczego
0.1 + 0.1 + 0.1 + 0.1
jest to liczba zmiennoprzecinkowa: liczby0.4
wymierne 1/10 i 4/10 są aproksymowane w ten sam sposób, z tym samym błędem względnym, po konwersji na zmiennoprzecinkowe. Te liczby zmiennoprzecinkowe mają stosunek dokładnie 4 między nimi. Pierwszy i trzeci wynik pokazują, że0.1 + 0.1 + 0.1
i0.1 + 0.1 + 0.1 + 0.1 + 0.1
można oczekiwać, że będą miały mniej błędów, niż można by wywnioskować na podstawie naiwnej analizy błędów, ale same w sobie odnoszą się one tylko do wyników odpowiednio3 * 0.1
i5 * 0.1
, co do których można oczekiwać, że będą bliskie, ale niekoniecznie identyczne z0.3
i0.5
.Jeśli będziesz kontynuować dodawanie
0.1
po czwartym dodaniu, w końcu zauważysz błędy zaokrągleń, które powodują, że „0.1
dodane do siebie n razy” odbiegająn * 0.1
od n / 10, a jeszcze bardziej odbiegają od n / 10. Gdybyś miał wykreślić wartości „0,1 dodane do siebie n razy” jako funkcję n, zobaczyłbyś linie o stałym nachyleniu w binadach (gdy tylko wynik n-tego dodania ma wpaść do określonej binady, można oczekiwać, że właściwości dodatku będą podobne do poprzednich dodatków, które dały wynik w tej samej binadzie). W tej samej binadzie błąd będzie się zwiększał lub zmniejszał. Gdybyś spojrzał na sekwencję zboczy od binade do binade, rozpoznałbyś powtarzające się cyfry0.1
binarnie przez chwilę. Po tym nastąpiłaby absorpcja i krzywa byłaby płaska.źródło
x + x + x
jest to dokładnie poprawnie zaokrąglona liczba zmiennoprzecinkowa do rzeczywistej 3 *x
. „Prawidłowo zaokrąglony” oznacza „najbliższy” w tym kontekście.Systemy zmiennoprzecinkowe wykonują różne magie, w tym mają kilka dodatkowych bitów precyzji do zaokrąglania. Tak więc bardzo mały błąd spowodowany niedokładną reprezentacją 0,1 kończy się zaokrągleniem do 0,5.
Pomyśl o zmiennoprzecinkowych jako świetnym, ale NIESKŁADNYM sposobie przedstawiania liczb. Nie wszystkie możliwe liczby można łatwo przedstawić na komputerze. Liczby nieracjonalne, takie jak PI. Lub jak SQRT (2). (Symboliczne systemy matematyczne mogą je reprezentować, ale powiedziałem „łatwo”).
Wartość zmiennoprzecinkowa może być bardzo bliska, ale nie dokładna. Może być tak blisko, że możesz nawigować do Plutona i oddalać się o kilka milimetrów. Ale nadal nie jest dokładne w sensie matematycznym.
Nie używaj zmiennoprzecinkowych, gdy chcesz być dokładny, a nie przybliżony. Na przykład aplikacje księgowe chcą dokładnie śledzić określoną liczbę groszy na koncie. Liczby całkowite są do tego dobre, ponieważ są dokładne. Podstawowym problemem, na który należy zwrócić uwagę w przypadku liczb całkowitych, jest przepełnienie.
Używanie BigDecimal dla waluty działa dobrze, ponieważ podstawową reprezentacją jest liczba całkowita, choć duża.
Uznając, że liczby zmiennoprzecinkowe są niedokładne, nadal mają wiele zastosowań. Systemy współrzędnych do nawigacji lub współrzędne w systemach graficznych. Wartości astronomiczne. Wartości naukowe. (Prawdopodobnie i tak nie możesz znać dokładnej masy piłki baseballowej do masy elektronu, więc niedokładność nie ma znaczenia).
W przypadku aplikacji liczących (w tym księgowych) użyj liczb całkowitych. Aby policzyć liczbę osób przechodzących przez bramę, użyj wartości int lub long.
źródło
strictfp
). Tylko dlatego, że wyrzekłeś się zrozumienia czegoś, nie oznacza, że jest to niezgłębione, ani że inni powinni wyrzec się zrozumienia tego. Zobacz stackoverflow.com/questions/18496560 jako przykład długości implementacji języka Java w celu zaimplementowania definicji języka (która nie zawiera żadnych przepisów dotyczących bitów o dodatkowej precyzji, anistrictfp
żadnych dodatkowych bitów exp)