Rozważ następujący kod:
0.1 + 0.2 == 0.3 -> false
0.1 + 0.2 -> 0.30000000000000004
Dlaczego zdarzają się te nieścisłości?
math
language-agnostic
floating-point
floating-accuracy
Cato Johnston
źródło
źródło
Odpowiedzi:
Binarna matematyka zmiennoprzecinkowa jest taka. W większości języków programowania jest oparty na standardzie IEEE 754 . Sedno problemu polega na tym, że liczby są reprezentowane w tym formacie jako liczba całkowita razy potęga dwóch; liczb wymiernych (takich jak
0.1
, które są1/10
), których mianownik nie jest potęgą dwóch, nie można dokładnie przedstawić.W
0.1
standardowymbinary64
formacie reprezentację można zapisać dokładnie tak, jak0.1000000000000000055511151231257827021181583404541015625
dziesiętnie, lub0x1.999999999999ap-4
w notacji heksafloatacyjnej C99 .Natomiast liczba wymierna
0.1
, którą1/10
można zapisać, może być dokładnie taka jak0.1
dziesiętnie, lub0x1.99999999999999...p-4
w analogicznym zapisie heksfloatacyjnym C99, gdzie...
reprezentuje niekończącą się sekwencję 9.Stałe
0.2
i0.3
w twoim programie będą również przybliżeniami do ich prawdziwych wartości. Zdarza się, że najbliżejdouble
celu0.2
jest większa niż liczba racjonalnego0.2
ale że najbliżejdouble
celu0.3
jest mniejsza niż liczba wymierna0.3
. Suma0.1
i0.2
kończy się na wartości większej niż liczba wymierna,0.3
a zatem nie zgadza się ze stałą w kodzie.Dość kompleksowe podejście do zagadnień arytmetyki zmiennoprzecinkowej jest tym, co każdy informatyk powinien wiedzieć o arytmetyki zmiennoprzecinkowej . Aby uzyskać łatwiejsze do zrozumienia wyjaśnienie, zobacz floating-point-gui.de .
Uwaga dodatkowa: Wszystkie systemy liczb pozycyjnych (podstawa N) dzielą ten problem z precyzją
Zwykłe stare liczby dziesiętne (podstawa 10) mają te same problemy, dlatego liczby takie jak 1/3 kończą się jako 0.333333333 ...
Właśnie natknąłeś się na liczbę (3/10), która jest łatwa do przedstawienia w systemie dziesiętnym, ale nie pasuje do systemu binarnego. Działa to również w dwie strony (w pewnym stopniu): 1/16 to brzydka liczba dziesiętna (0,0625), ale w systemie dwójkowym wygląda równie porządnie jak 10.000 w systemie dziesiętnym (0,0001) ** - gdybyśmy byli w nawyk korzystania z systemu liczb bazowych 2 w naszym codziennym życiu, można nawet spojrzeć na tę liczbę i instynktownie zrozumieć, że można tam dotrzeć, dzieląc coś o połowę, ponownie o połowę, raz za razem.
** Oczywiście nie tak dokładnie są przechowywane liczby zmiennoprzecinkowe w pamięci (używają one formy notacji naukowej). Jednak ilustruje to, że binarne zmiennoprzecinkowe błędy precyzji mają tendencję do pojawiania się, ponieważ liczby w „świecie rzeczywistym”, z którymi zwykle jesteśmy zainteresowani, to często potęgi dziesięciu - ale tylko dlatego, że używamy dziesiętnego systemu liczb dziesiętnych - dzisiaj. Dlatego też powiemy 71% zamiast „5 na każde 7” (71% to przybliżenie, ponieważ 5/7 nie może być dokładnie przedstawione za pomocą żadnej liczby dziesiętnej).
Więc nie: binarne liczby zmiennoprzecinkowe nie są łamane, po prostu są tak niedoskonałe jak każdy inny system liczb bazowych N :)
Uwaga: Praca z pływakami w programowaniu
W praktyce ten problem precyzji oznacza, że musisz użyć funkcji zaokrąglania, aby zaokrąglić liczby zmiennoprzecinkowe do dowolnej liczby miejsc dziesiętnych, którą jesteś zainteresowany przed ich wyświetleniem.
Trzeba również zastąpić testy równości porównaniami, które dopuszczają pewną tolerancję, co oznacza:
Czy nie robić
if (x == y) { ... }
Zamiast zrobić
if (abs(x - y) < myToleranceValue) { ... }
.gdzie
abs
jest wartość bezwzględna.myToleranceValue
należy wybrać dla konkretnej aplikacji - i będzie to miało wiele wspólnego z tym, na ile „pokoju wiggle” jesteś gotów pozwolić i jaka może być największa liczba, którą będziesz porównywać (z powodu problemów z precyzją ). Uważaj na stałe w stylu „epsilon” w wybranym języku. Nie należy ich traktować jako wartości tolerancji.źródło
Perspektywa projektanta sprzętu
Uważam, że powinienem dodać do tego perspektywę projektanta sprzętu, ponieważ projektuję i buduję sprzęt zmiennoprzecinkowy. Znajomość źródła błędu może pomóc w zrozumieniu tego, co dzieje się w oprogramowaniu, i ostatecznie mam nadzieję, że to pomoże wyjaśnić przyczyny, dla których błędy zmiennoprzecinkowe zdarzają się i wydają się kumulować w miarę upływu czasu.
1. Przegląd
Z punktu widzenia inżynierii większość operacji zmiennoprzecinkowych będzie zawierała element błędu, ponieważ sprzęt, który wykonuje obliczenia zmiennoprzecinkowe, musi mieć tylko błąd mniejszy niż połowa jednej jednostki na ostatnim miejscu. Dlatego wiele urządzeń zatrzyma się z dokładnością, która jest niezbędna tylko do uzyskania błędu mniejszego niż połowa jednej jednostki w ostatnim miejscu dla pojedynczej operacji, co jest szczególnie problematyczne w przypadku podziału zmiennoprzecinkowego. To, co stanowi pojedynczą operację, zależy od liczby operandów przyjętych przez jednostkę. W większości są to dwa, ale niektóre jednostki przyjmują 3 lub więcej operandów. Z tego powodu nie ma gwarancji, że powtarzające się operacje spowodują pożądany błąd, ponieważ błędy sumują się z czasem.
2. Normy
Większość procesorów jest zgodna ze standardem IEEE-754, ale niektóre wykorzystują normy zdenormalizowane lub inne. Na przykład w IEEE-754 istnieje tryb zdenormalizowany, który pozwala na reprezentację bardzo małych liczb zmiennoprzecinkowych kosztem precyzji. Poniższe informacje dotyczą jednak znormalizowanego trybu IEEE-754, który jest typowym trybem działania.
W standardzie IEEE-754 projektantom sprzętu dozwolona jest dowolna wartość błędu / epsilon, o ile na ostatnim miejscu jest mniejsza niż połowa jednej jednostki, a wynik musi być mniejszy niż połowa jednej jednostki w ostatnim miejsce na jedną operację. To wyjaśnia, dlaczego przy powtarzających się operacjach błędy sumują się. W przypadku podwójnej precyzji IEEE-754 jest to 54-ty bit, ponieważ 53 bity są używane do reprezentowania części liczbowej (znormalizowanej), zwanej także mantysą, liczby zmiennoprzecinkowej (np. 5,3 w 5.3e5). Kolejne sekcje zawierają bardziej szczegółowe informacje na temat przyczyn błędów sprzętowych w różnych operacjach zmiennoprzecinkowych.
3. Przyczyna błędu zaokrąglania w podziale
Główną przyczyną błędu w dzieleniu zmiennoprzecinkowym są algorytmy podziału stosowane do obliczania ilorazu. Większość systemów komputerowych oblicza dzielenie za pomocą mnożenia przez odwrotność, głównie w
Z=X/Y
,Z = X * (1/Y)
. Podział jest obliczany iteracyjnie, tj. Każdy cykl oblicza niektóre bity ilorazu, aż do osiągnięcia pożądanej precyzji, co w przypadku IEEE-754 oznacza wszystko z błędem mniejszym niż jedna jednostka na ostatnim miejscu. Tabela odwrotności Y (1 / Y) jest znana jako tablica wyboru ilorazów (QST) w wolnym podziale, a rozmiar w bitach tabeli selekcji ilorazów jest zwykle szerokością podstawki lub liczby bitów iloraz obliczany w każdej iteracji plus kilka bitów ochronnych. Dla standardu IEEE-754, podwójna precyzja (64-bit), byłby to rozmiar podstawy dzielnika, plus kilka bitów ochronnych k, gdziek>=2
. Na przykład typową tabelą wyboru ilorazu dla dzielnika, który oblicza 2 bity ilorazu na raz (podstawa 4), byłyby2+2= 4
bity (plus kilka opcjonalnych bitów).3.1 Błąd zaokrąglenia podziału: przybliżenie wzajemności
To, co znajduje się w tabeli wyboru ilorazów, zależy od metody podziału : dzielenie wolne, takie jak podział SRT lub szybkie dzielenie, takie jak podział Goldschmidta; każdy wpis jest modyfikowany zgodnie z algorytmem podziału w celu uzyskania możliwie najniższego błędu. W każdym razie wszystkie wzajemności są przybliżeniamirzeczywistej wzajemności i wprowadzić pewien element błędu. Zarówno metody powolnego, jak i szybkiego dzielenia obliczają iloraz iteracyjnie, tj. Pewna liczba bitów ilorazu jest obliczana na każdym etapie, następnie wynik jest odejmowany od dywidendy, a dzielnik powtarza kroki, aż błąd będzie mniejszy niż połowa jednego jednostka na ostatnim miejscu. Metody powolnego dzielenia obliczają stałą liczbę cyfr ilorazu na każdym etapie i zwykle są tańsze w budowie, a metody szybkiego dzielenia obliczają zmienną liczbę cyfr na krok i są zwykle droższe w budowaniu. Najważniejszą częścią metod dzielenia jest to, że większość z nich polega na powtarzanym pomnożeniu przez przybliżenie odwrotności, więc są one podatne na błędy.
4. Błędy zaokrąglania w innych operacjach: Obcinanie
Inną przyczyną błędów zaokrąglania we wszystkich operacjach są różne tryby obcinania ostatecznej odpowiedzi, na które zezwala IEEE-754. Są one obcinane, zaokrąglane w kierunku zera, zaokrąglane do najbliższego (domyślnie), zaokrąglane w dół i zaokrąglające w górę. Wszystkie metody wprowadzają element błędu mniejszy niż jedna jednostka w ostatnim miejscu dla pojedynczej operacji. W miarę upływu czasu i powtarzanych operacji obcinanie dodaje się łącznie do powstałego błędu. Ten błąd obcięcia jest szczególnie problematyczny w potęgowaniu, który obejmuje pewną formę powtarzanego mnożenia.
5. Powtarzane operacje
Ponieważ sprzęt, który wykonuje obliczenia zmiennoprzecinkowe, musi tylko dać wynik z błędem mniejszym niż połowa jednej jednostki w ostatnim miejscu dla pojedynczej operacji, błąd będzie narastał w wyniku powtarzanych operacji, jeśli nie będzie obserwowany. Jest to powód, dla którego w obliczeniach wymagających ograniczonego błędu matematycy stosują metody, takie jak użycie zaokrąglenia do najbliższej parzystej cyfry w ostatnim miejscu IEEE-754, ponieważ z czasem błędy częściej się znoszą out i arytmetyka interwałowa w połączeniu z odmianami trybów zaokrąglania IEEE 754przewidywać błędy zaokrąglania i je poprawiać. Z powodu niskiego błędu względnego w porównaniu z innymi trybami zaokrąglania, zaokrąglanie do najbliższej parzystej cyfry (na ostatnim miejscu) jest domyślnym trybem zaokrąglania IEEE-754.
Należy pamiętać, że domyślny tryb zaokrąglania, zaokrąglanie do najbliższej parzystej cyfry w ostatnim miejscu , gwarantuje błąd mniejszy niż połowa jednej jednostki w ostatnim miejscu dla jednej operacji. Samo obcięcie, zaokrąglenie w górę i zaokrąglenie w dół może spowodować błąd, który jest większy niż połowa jednej jednostki na ostatnim miejscu, ale mniejsza niż jedna jednostka na ostatnim miejscu, więc te tryby nie są zalecane, chyba że są używane w arytmetyki interwałowej.
6. Podsumowanie
Krótko mówiąc, podstawową przyczyną błędów w operacjach zmiennoprzecinkowych jest kombinacja obcięcia w sprzęcie i obcięcia odwrotności w przypadku podziału. Ponieważ standard IEEE-754 wymaga tylko błędu mniejszego niż połowa jednej jednostki w ostatnim miejscu dla pojedynczej operacji, błędy zmiennoprzecinkowe w przypadku powtarzanych operacji sumują się, o ile nie zostaną poprawione.
źródło
Kiedy konwertujesz .1 lub 1/10 na bazę 2 (binarnie), otrzymujesz powtarzający się wzorzec po przecinku, podobnie jak próba przedstawienia 1/3 w podstawie 10. Wartość nie jest dokładna i dlatego nie możesz tego zrobić dokladna matematyka z wykorzystaniem normalnych metod zmiennoprzecinkowych.
źródło
Większość odpowiedzi tutaj odnosi się do tego pytania w bardzo suchych, technicznych terminach. Chciałbym rozwiązać ten problem w sposób zrozumiały dla zwykłych ludzi.
Wyobraź sobie, że próbujesz kroić pizze. Masz automatyczny nóż do pizzy, który może pokroić plasterki pizzy dokładnie na pół. Może przekroić całą pizzę o połowę lub może zmniejszyć o połowę istniejący plasterek, ale w każdym razie połówka jest zawsze dokładna.
Ten nóż do pizzy ma bardzo delikatne ruchy, a jeśli zaczynasz od całej pizzy, to przekrój ją na pół i kontynuuj za każdym razem o połowę najmniejszy plasterek, możesz zrobić o połowę 53 razy, zanim plasterek będzie zbyt mały, aby nawet jego precyzyjne zdolności . W tym momencie nie możesz już zmniejszyć o połowę tego bardzo cienkiego plasterka, ale musisz go uwzględnić lub wykluczyć w obecnej postaci.
W jaki sposób poskładałeś wszystkie plasterki w taki sposób, aby tworzyły jedną dziesiątą (0,1) lub jedną piątą (0,2) pizzy? Naprawdę pomyśl o tym i spróbuj to wypracować. Możesz nawet spróbować użyć prawdziwej pizzy, jeśli masz pod ręką mityczny precyzyjny nóż do pizzy. :-)
Oczywiście najbardziej doświadczeni programiści znają prawdziwą odpowiedź, a mianowicie, że nie ma sposobu na ułożenie dokładnie jednej dziesiątej lub piątej pizzy za pomocą tych plasterków, bez względu na to, jak dobrze je pokroisz. Możesz zrobić całkiem dobre przybliżenie, a jeśli dodasz przybliżenie 0,1 do przybliżenia 0,2, otrzymasz całkiem dobre przybliżenie 0,3, ale nadal jest to tylko przybliżenie.
W przypadku liczb o podwójnej precyzji (czyli precyzji, która umożliwia 53-krotne zmniejszenie pizzy), liczby bezpośrednio mniejsze i większe niż 0,1 to 0,09999999999999999167332731531132594682276248931884765625 i 0.1000000000000000055511151231257827021181583404541015625. Ten drugi jest nieco bliższy 0,1 niż poprzedni, więc parser liczbowy, przy danych wejściowych 0,1, faworyzuje ten drugi.
(Różnica między tymi dwiema liczbami to „najmniejszy wycinek”, który musimy zdecydować albo uwzględnić, który wprowadza odchylenie w górę, albo wykluczyć, który wprowadza odchylenie w dół. Terminem technicznym dla tego najmniejszego wycinka jest wrzód ).
W przypadku 0,2 liczby są takie same, po prostu powiększone o współczynnik 2. Ponownie preferujemy wartość nieco wyższą niż 0,2.
Zauważ, że w obu przypadkach przybliżenia dla 0,1 i 0,2 mają nieznaczne odchylenie w górę. Jeśli dodamy wystarczającą liczbę tych uprzedzeń, będą one popychać liczbę coraz dalej od tego, czego chcemy, aw rzeczywistości, w przypadku 0,1 + 0,2, odchylenie jest wystarczająco wysokie, aby wynikowa liczba nie była już najbliższą liczbą do 0,3.
W szczególności, 0,1 + 0,2 jest naprawdę 0.1000000000000000055511151231257827021181583404541015625 + 0.200000000000000011102230246251565404236316680908203125 = 0.3000000000000000444089209850062616169452667236328125, podczas gdy liczba najbliżej 0,3 jest rzeczywiście 0,299999999999999988897769753748434595763683319091796875.
PS Niektóre języki programowania zapewniają również krajalnice do pizzy, które mogą dzielić plasterki na dokładnie jedną dziesiątą . Chociaż takie krajalnice do pizzy są rzadkie, jeśli masz dostęp do jednego, powinieneś go użyć, gdy ważne jest, aby móc uzyskać dokładnie jedną dziesiątą lub jedną piątą plasterka.
(Pierwotnie opublikowane na Quora.)
źródło
Błędy zaokrąglania zmiennoprzecinkowego. 0,1 nie może być reprezentowane tak dokładnie w bazie-2, jak w bazie-10 z powodu brakującego współczynnika liczby pierwszej równej 5. Tak jak 1/3 przyjmuje nieskończoną liczbę cyfr do reprezentacji dziesiętnej, ale wynosi „0,1” w podstawie-3, 0,1 przyjmuje nieskończoną liczbę cyfr w bazie-2, a nie w bazie-10. A komputery nie mają nieskończonej ilości pamięci.
źródło
Oprócz innych poprawnych odpowiedzi, możesz rozważyć skalowanie wartości, aby uniknąć problemów z arytmetyką zmiennoprzecinkową.
Na przykład:
... zamiast:
Wyrażenie
0.1 + 0.2 === 0.3
powracafalse
w JavaScript, ale na szczęście arytmetyka liczb całkowitych w liczbach zmiennoprzecinkowych jest dokładna, więc skalowania można uniknąć błędów reprezentacji dziesiętnej.Jako praktyczny przykład, aby uniknąć problemów z liczbą zmiennoprzecinkową, w których dokładność jest najważniejsza, zaleca się 1 obsługę pieniędzy jako liczby całkowitej reprezentującej liczbę centów:
2550
centów zamiast25.50
dolarów.1 Douglas Crockford: JavaScript: Dobre części : Dodatek A - Okropne części (strona 105) .
źródło
Moja odpowiedź jest dość długa, dlatego podzieliłem ją na trzy części. Ponieważ pytanie dotyczy matematyki zmiennoprzecinkowej, położyłem nacisk na to, co faktycznie robi maszyna. Uczyniłem go także specyficznym dla podwójnej (64-bitowej) precyzji, ale ten argument stosuje się jednakowo do dowolnej arytmetyki zmiennoprzecinkowej.
Preambuła
IEEE 754 o podwójnej precyzji w formacie binarnym zmiennoprzecinkowych (binary64) liczba oznacza numer formularza
w 64 bitach:
1
jeśli liczba jest ujemna, w0
przeciwnym razie 1 .1.
jest zawsze pomijane 2, ponieważ najbardziej znaczącym bitem dowolnej wartości binarnej jest1
.1 - IEEE 754 pozwala na koncepcję zerowego znaku -
+0
i-0
są traktowane inaczej:1 / (+0)
jest dodatnią nieskończonością;1 / (-0)
jest ujemną nieskończonością. W przypadku wartości zerowych bity mantysy i wykładnika są równe zero. Uwaga: wartości zerowe (+0 i -0) wyraźnie nie są klasyfikowane jako denormal 2 .2 - Nie dotyczy to liczb normalnych , które mają wykładnik przesunięcia równy zero (i domniemane
0.
). Zakres denormalnych liczb podwójnej precyzji wynosi d min ≤ | x | ≤ d max , gdzie d min (Najmniejsza ilość niezerowa) wynosi 2 -1023 - 51 (≈ 4,94 * 10 -324 ) i D max (najwięcej Brak reprezentacji, na którym Mantysa składa się całkowicie z1
S) 2 -1023 + 1 - 2 -1023 - 51 (≈ 2.225 * 10 -308 ).Przekształcanie liczby podwójnej precyzji na binarną
Istnieje wiele konwerterów online do konwersji liczb zmiennoprzecinkowych o podwójnej precyzji na binarne (np. Na binaryconvert.com ), ale oto przykładowy kod C # w celu uzyskania reprezentacji IEEE 754 dla liczby podwójnej precyzji (trzy części oddzielam dwukropkami (
:
) :Przechodząc do sedna: oryginalne pytanie
(Przejdź do dołu dla wersji TL; DR)
Cato Johnston (pytający) zadał pytanie, dlaczego 0,1 + 0,2! = 0,3.
Zapisane w formacie binarnym (z dwukropkami oddzielającymi trzy części) reprezentacje wartości IEEE 754 to:
Zauważ, że mantysa składa się z powtarzających się cyfr
0011
. Jest to kluczem do tego, dlaczego występuje błąd w obliczeniach - 0,1, 0,2 i 0,3 nie mogą być reprezentowane binarnie dokładnie w skończonej liczbie bitów binarnych, a więcej niż 1/9, 1/3 lub 1/7 może być reprezentowane dokładnie w cyfry dziesiętne .Zauważ też, że możemy zmniejszyć moc wykładnika o 52 i przesunąć punkt w reprezentacji binarnej w prawo o 52 miejsca (podobnie jak 10-3 * 1,23 == 10-5 * 123). To pozwala nam reprezentować reprezentację binarną jako dokładną wartość, którą reprezentuje w postaci * 2 p . gdzie „a” jest liczbą całkowitą.
Konwertowanie wykładników wykładniczych na dziesiętne, usuwanie przesunięcia i ponowne dodawanie domyślnych
1
(w nawiasach kwadratowych), 0,1 i 0,2 to:Aby dodać dwie liczby, wykładnik musi być taki sam, tj .:
Ponieważ suma nie ma postaci 2 n * 1. {bbb} zwiększamy wykładnik o jeden i przesuwamy punkt dziesiętny ( binarny ), aby uzyskać:
W mantysie jest teraz 53 bitów (53. jest w nawiasach kwadratowych w linii powyżej). Domyślny zaokrąglenia tryb IEEE 754 „ okrągły do najbliższego ” - to znaczy czy liczba x mieści się pomiędzy dwoma wartościami a i b , wartości, w której najmniej znaczący bit jest zero, jest wybrany.
Zauważ, że i b różnią się tylko w ostatnim bitem; + = . W tym przypadku wartością najmniej znaczącego bitu zero jest b , więc suma wynosi:
...0011
1
...0100
podczas gdy dwójkowa reprezentacja 0,3 to:
która różni się tylko od dwójkowej reprezentacji sumy 0,1 i 0,2 przez 2 -54 .
Binarna reprezentacja 0,1 i 0,2 to najdokładniejsze reprezentacje liczb dopuszczalnych przez IEEE 754. Dodanie tych reprezentacji, ze względu na domyślny tryb zaokrąglania, daje wartość, która różni się tylko bitem najmniej znaczącym.
TL; DR
Pisząc
0.1 + 0.2
w reprezentacji binarnej IEEE 754 (z dwukropkami oddzielającymi trzy części) i porównując ją0.3
, to jest (umieściłem różne bity w nawiasach kwadratowych):Przeliczone z powrotem na dziesiętne, te wartości to:
Różnica wynosi dokładnie 2 -54 , czyli ~ 5,5511151231258 × 10 -17 - nieistotna (dla wielu aplikacji) w porównaniu z wartościami pierwotnymi.
Porównanie ostatnich bitów liczby zmiennoprzecinkowej jest z natury niebezpieczne, ponieważ każdy, kto czyta słynne „ Co każdy informatyk powinien wiedzieć o arytmetyki zmiennoprzecinkowej ” (który obejmuje wszystkie główne części tej odpowiedzi), będzie wiedział.
Większość kalkulatorów używa dodatkowych cyfr ochronnych, aby obejść ten problem, i tak
0.1 + 0.2
by to dało0.3
: kilka ostatnich bitów jest zaokrąglanych.źródło
Liczby zmiennoprzecinkowe przechowywane w komputerze składają się z dwóch części, liczby całkowitej i wykładnika, do którego podstawa jest pobierana i mnożona przez część całkowitą.
Gdyby komputer działał w bazie 10,
0.1
byłby1 x 10⁻¹
,0.2
byłby2 x 10⁻¹
i0.3
byłby3 x 10⁻¹
. Matematyka liczb całkowitych jest łatwa i dokładna, więc dodawanie0.1 + 0.2
oczywiście przyniesie skutek0.3
.Komputery zwykle nie działają w bazie 10, działają w bazie 2. Nadal możesz uzyskać dokładne wyniki dla niektórych wartości, na przykład
0.5
jest1 x 2⁻¹
i0.25
jest1 x 2⁻²
, i dodając je do3 x 2⁻²
, lub0.75
. Dokładnie.Problem dotyczy liczb, które można przedstawić dokładnie w bazie 10, ale nie w bazie 2. Liczby te należy zaokrąglić do ich najbliższego odpowiednika. Zakładając bardzo popularny 64-bitowy format zmiennoprzecinkowy IEEE, najbliższą liczbą
0.1
jest3602879701896397 x 2⁻⁵⁵
, a najbliższą liczbą0.2
jest7205759403792794 x 2⁻⁵⁵
; zsumowanie ich powoduje10808639105689191 x 2⁻⁵⁵
uzyskanie dokładnej wartości dziesiętnej lub0.3000000000000000444089209850062616169452667236328125
. Numery zmiennoprzecinkowe są na ogół zaokrąglane w celu wyświetlenia.źródło
Błąd zaokrąglenia zmiennoprzecinkowego. Z tego, co każdy informatyk powinien wiedzieć o arytmetyki zmiennoprzecinkowej :
źródło
Moje obejście:
precyzja odnosi się do liczby cyfr, które chcesz zachować po przecinku podczas dodawania.
źródło
Opublikowano wiele dobrych odpowiedzi, ale chciałbym dołączyć jeszcze jedną.
Nie wszystkie numery mogą być reprezentowane przez pływaków / podwaja Na przykład numer „0.2” zostanie przedstawiony jako „0,200000003” w pojedynczej precyzji w standardzie IEEE 754 pkt pływaka.
Model do przechowywania liczb rzeczywistych pod maską reprezentuje liczby zmiennoprzecinkowe jako
Nawet jeśli możesz
0.2
łatwo pisać ,FLT_RADIX
aDBL_RADIX
jest to 2; nie 10 dla komputera z FPU, który wykorzystuje „Standard IEEE dla binarnej arytmetyki zmiennoprzecinkowej (ISO / IEEE Std 754–1985)”.Trudno więc dokładnie przedstawić takie liczby. Nawet jeśli podasz tę zmienną jawnie, bez żadnych pośrednich obliczeń.
źródło
Niektóre statystyki związane z tym słynnym pytaniem o podwójnej precyzji.
Przy dodawaniu wszystkich wartości ( a + b ) z krokiem 0,1 (od 0,1 do 100) mamy ~ 15% szansy na błąd precyzji . Zauważ, że błąd może spowodować nieco większe lub mniejsze wartości. Oto kilka przykładów:
Odejmując wszystkie wartości ( a - b, gdzie a> b ), stosując krok 0,1 (od 100 do 0,1), mamy ~ 34% szansy na błąd precyzji . Oto kilka przykładów:
* 15% i 34% są rzeczywiście ogromne, dlatego zawsze używaj BigDecimal, gdy precyzja ma duże znaczenie. Przy 2 cyfrach dziesiętnych (krok 0,01) sytuacja pogarsza się nieco bardziej (18% i 36%).
źródło
Nie, nie rozbite, ale większość ułamków dziesiętnych musi być przybliżona
Arytmetyka zmiennoprzecinkowa jest dokładna, niestety, nie zgadza się dobrze z naszą zwykłą reprezentacją liczby podstawowej-10, więc okazuje się, że często dajemy jej dane wejściowe nieco odbiegające od tego, co napisaliśmy.
Nawet proste liczby, takie jak 0,01, 0,02, 0,03, 0,04 ... 0,24, nie są reprezentowane dokładnie jako ułamki dwójkowe. Jeśli policzysz 0,01, 0,02, 0,03 ..., to dopóki nie dojdziesz do 0,25, otrzymasz pierwszą frakcję reprezentowaną w bazie 2 . Jeśli spróbowałbyś tego za pomocą FP, twój 0,01 byłby nieco mniejszy, więc jedyny sposób na dodanie 25 z nich do dokładnego 0,25 wymagałby długiego łańcucha przyczynowego obejmującego bity ochronne i zaokrąglanie. Trudno przewidzieć, więc podnosimy ręce i mówimy „FP jest niedokładny”, ale to nie jest tak naprawdę prawda.
Stale dajemy sprzętowi FP coś, co wydaje się proste w bazie 10, ale jest powtarzalną frakcją w bazie 2.
Kiedy piszemy w systemie dziesiętnym, każda część (w szczególności każda liczba dziesiętna kończąca się) jest liczbą wymierną postaci
a / (2 n x 5 m )
W trybie binarnym otrzymujemy tylko termin 2 n , to znaczy:
a / 2 n
Więc po przecinku, nie możemy reprezentować 1 / 3 . Ponieważ podstawa 10 zawiera 2 jako czynnik główny, każda liczba, którą możemy zapisać jako ułamek binarny, może być również zapisana jako ułamek podstawowy 10. Jednak prawie wszystko, co piszemy jako ułamek podstawowy 10, jest reprezentowalne w postaci binarnej. W zakresie od 0,01, 0,02, 0,03 ... 0,99 tylko trzy liczby mogą być reprezentowane w naszym formacie FP: 0,25, 0,50 i 0,75, ponieważ są to 1/4, 1/2 i 3/4, wszystkie liczby z czynnikiem podstawowym przy użyciu tylko terminu 2 n .
W podstawie 10 nie może oznaczać 1 / 3 . Ale w binarnym, nie możemy zrobić 1 / 10 lub 1 / 3 .
Tak więc, chociaż każda część binarna może być zapisywana dziesiętnie, odwrotność nie jest prawdą. W rzeczywistości większość ułamków dziesiętnych powtarza się w systemie binarnym.
Programiści są zwykle instruowani, aby robić < porównania epsilon , lepsza rada może być zaokrąglanie do wartości całkowitych (w bibliotece C: round () i roundf (), tj. Pozostanie w formacie FP), a następnie porównanie. Zaokrąglenie do określonej długości ułamka dziesiętnego rozwiązuje większość problemów z wydajnością.
Ponadto w przypadku problemów z liczbami rzeczywistymi (problemy, dla których FP został wymyślony na wczesnych, strasznie drogich komputerach) stałe fizyczne wszechświata i wszystkie inne pomiary są znane tylko stosunkowo niewielkiej liczbie znaczących liczb, więc cała przestrzeń problemu i tak był „niedokładny”. „Dokładność” FP nie stanowi problemu w tego rodzaju aplikacjach.
Cały problem naprawdę powstaje, gdy ludzie próbują wykorzystywać FP do liczenia ziaren. Działa w tym celu, ale tylko wtedy, gdy trzymasz się wartości całkowitych, co w pewnym sensie nie pozwala na ich użycie. Właśnie dlatego mamy te wszystkie biblioteki oprogramowania ułamków dziesiętnych.
Uwielbiam odpowiedź Chrisa autorstwa Chrisa , ponieważ opisuje on rzeczywisty problem, a nie tylko zwykłe wymyślanie „niedokładności”. Gdyby FP były po prostu „niedokładne”, moglibyśmy to naprawić i zrobilibyśmy to dekady temu. Nie zrobiliśmy tego dlatego, że format FP jest kompaktowy i szybki i jest to najlepszy sposób na rozbicie wielu liczb. Jest to również dziedzictwo epoki kosmicznej i wyścigu zbrojeń oraz wczesnych prób rozwiązywania dużych problemów z bardzo powolnymi komputerami używającymi małych systemów pamięci. (Czasami pojedyncze rdzenie magnetyczne do przechowywania 1-bitowego, ale to inna historia. )
Jeśli liczysz tylko fasolę w banku, rozwiązania programowe wykorzystujące w pierwszej kolejności reprezentacje ciągów dziesiętnych działają doskonale. Ale nie można w ten sposób wykonywać kwantowej chromodynamiki ani aerodynamiki.
źródło
nextafter()
z przyrostem całkowitym lub dekrementacją binarnej reprezentacji liczb zmiennoprzecinkowych IEEE. Ponadto można porównywać liczby zmiennoprzecinkowe jako liczby całkowite i uzyskać poprawną odpowiedź, z wyjątkiem sytuacji, gdy oba są ujemne (z powodu wielkości znaku w porównaniu z uzupełnieniem 2).Czy wypróbowałeś taśmę klejącą?
Spróbuj ustalić, kiedy wystąpią błędy i napraw je za pomocą krótkich instrukcji if, jeśli nie są ładne, ale w przypadku niektórych problemów jest to jedyne rozwiązanie i jest to jedno z nich.
Miałem ten sam problem w projekcie symulacji naukowej w języku C # i mogę powiedzieć, że jeśli zignorujesz efekt motyla, zmieni się on w dużego, grubego smoka i ugryzie cię w **
źródło
Aby zaoferować najlepsze rozwiązanie, które mogę powiedzieć, odkryłem następującą metodę:
Pozwól mi wyjaśnić, dlaczego jest to najlepsze rozwiązanie. Jak wspomniano w powyższych odpowiedziach, dobrym rozwiązaniem jest skorzystanie z gotowej do użycia funkcji JavaScript toFixed () w celu rozwiązania problemu. Ale najprawdopodobniej napotkasz pewne problemy.
Wyobraź sobie, masz zamiar dodać dwa numery pływaka jak
0.2
i0.7
to jest tutaj:0.2 + 0.7 = 0.8999999999999999
.Oczekiwany wynik oznaczał,
0.9
że w tym przypadku potrzebujesz wyniku z dokładnością do 1 cyfry. Więc powinieneś był użyć(0.2 + 0.7).tofixed(1)
ale nie możesz po prostu podać określonego parametru toFixed (), ponieważ na przykład zależy on od podanej liczbyW tym przykładzie potrzebujesz dokładności 2 cyfr, więc powinna być
toFixed(2)
, więc jaki powinien być parametr pasujący do każdej podanej liczby zmiennoprzecinkowej?Można powiedzieć, że w każdej sytuacji będzie to 10:
Cholera! Co zamierzasz zrobić z tymi niechcianymi zerami po 9? Nadszedł czas, aby przekonwertować go na zmiennoprzecinkowy, aby uzyskać pożądany efekt:
Teraz, gdy znalazłeś rozwiązanie, lepiej zaoferować je jako funkcję taką jak ta:
Spróbujmy samemu:
Możesz użyć tego w ten sposób:
Jak sugeruje W3SCHOOLS , istnieje również inne rozwiązanie, możesz pomnożyć i podzielić, aby rozwiązać powyższy problem:
Pamiętaj, że
(0.2 + 0.1) * 10 / 10
to w ogóle nie zadziała, choć wydaje się takie samo! Wolę pierwsze rozwiązanie, ponieważ mogę zastosować je jako funkcję, która przekształca liczbę zmiennoprzecinkową na dokładną liczbę zmiennoprzecinkową.źródło
Te dziwne liczby pojawiają się, ponieważ komputery używają systemu liczb binarnych (podstawa 2) do obliczeń, podczas gdy my używamy liczb dziesiętnych (podstawa 10).
Istnieje większość liczb ułamkowych, których nie można dokładnie przedstawić w postaci binarnej, dziesiętnej ani obu. Wynik - zaokrąglona w górę (ale precyzyjna) liczba wyników.
źródło
Wiele z wielu duplikatów tego pytania pyta o wpływ zaokrąglania zmiennoprzecinkowego na określone liczby. W praktyce łatwiej jest zrozumieć, jak to działa, patrząc na dokładne wyniki obliczeń zainteresowania, a nie tylko czytając o tym. Niektóre języki dostarczają sposobów działania, które - jak konwertowania
float
lubdouble
doBigDecimal
w Javie.Ponieważ jest to pytanie niezależne od języka, potrzebuje narzędzi niezależnych od języka, takich jak konwerter dziesiętny na zmiennoprzecinkowy .
Zastosowanie go do liczb w pytaniu, traktowanych jako podwójne:
0,1 konwertuje na 0.1000000000000000055511151231257827021181583404541015625,
0,2 konwertuje na 0.200000000000000011102230246251565404236316680908203125,
0,3 konwertuje na 0,299999999999999988897769753748434595763683319091796875 i
0.30000000000000004 konwertuje na 0.3000000000000000444089209850062616169452667236328125.
Dodawanie pierwszych dwóch liczb ręcznie lub w kalkulatorze dziesiętnym, takim jak kalkulator pełnej precyzji , pokazuje, że dokładna suma rzeczywistych danych wejściowych wynosi 0,3000000000000000166533453693773481063544750213623046875.
Gdyby zaokrąglić w dół do ekwiwalentu 0,3, błąd zaokrąglenia wyniósłby 0,0000000000000000277555756156289135105907917022705078125. Zaokrąglanie w górę do ekwiwalentu 0,30000000000000004 daje również błąd zaokrąglania 0,000000000000000027275555756156289135105907917022705078125. Obowiązuje równomierny remis zaokrąglający.
Wracając do konwertera zmiennoprzecinkowego, surowy szesnastkowy dla 0.30000000000000004 to 3fd3333333333334, który kończy się na parzystej cyfrze, a zatem jest poprawnym wynikiem.
źródło
Biorąc pod uwagę, że nikt nie wspomniał o tym ...
Niektóre języki wysokiego poziomu, takie jak Python i Java, są wyposażone w narzędzia pozwalające pokonać ograniczenia binarne zmiennoprzecinkowe. Na przykład:
decimal
Moduł Pythona iBigDecimal
klasa Java , które wewnętrznie reprezentują liczby z notacją dziesiętną (w przeciwieństwie do notacji binarnej). Oba mają ograniczoną precyzję, więc nadal są podatne na błędy, jednak rozwiązują najczęstsze problemy z binarną arytmetyką zmiennoprzecinkową.Liczby dziesiętne są bardzo miłe w przypadku pieniędzy: dziesięć centów plus dwadzieścia centów to zawsze dokładnie trzydzieści centów:
decimal
Moduł Pythona oparty jest na standardzie IEEE 854-1987 .fractions
Moduł Pythona iBigFraction
klasa Apache Common . Obie reprezentują liczby wymierne jako(numerator, denominator)
pary i mogą dawać dokładniejsze wyniki niż dziesiętna arytmetyka zmiennoprzecinkowa.Żadne z tych rozwiązań nie jest idealne (zwłaszcza jeśli spojrzymy na wyniki lub wymagamy bardzo wysokiej precyzji), ale nadal rozwiązują one wiele problemów z binarną arytmetyką zmiennoprzecinkową.
źródło
Czy mogę po prostu dodać; ludzie zawsze zakładają, że jest to problem z komputerem, ale jeśli policzysz rękami (baza 10), nie możesz tego zrobić
(1/3+1/3=2/3)=true
dopóki nie masz nieskończoności, aby dodać 0,333 ... do 0,333 ... tak jak w przypadku(1/10+2/10)!==3/10
problemu w bazie 2, obcinasz go do 0,333 + 0,333 = 0,666 i prawdopodobnie zaokrąglasz do 0,667, co również byłoby technicznie niedokładne.Policz w trójskładniku, a trzecie nie stanowią problemu - być może jakiś wyścig z 15 palcami na każdej ręce zapytałby, dlaczego twoja matematyka dziesiętna została złamana ...
źródło
Rodzaj matematyki zmiennoprzecinkowej, który może być zaimplementowany w komputerze cyfrowym, koniecznie wykorzystuje przybliżenie rzeczywistych liczb i operacji na nich. ( Standardowa wersja obejmuje ponad pięćdziesiąt stron dokumentacji i ma komitet zajmujący się jej błędami i dalszymi udoskonaleniami).
To przybliżenie jest mieszanką różnych rodzajów przybliżeń, z których każde może zostać zignorowane lub dokładnie uwzględnione ze względu na specyficzny sposób odchylenia od dokładności. Dotyczy to również wielu wyraźnych wyjątkowych przypadków, zarówno na poziomie sprzętu, jak i oprogramowania, które większość ludzi przechodzi obok, udając, że ich nie zauważa.
Jeśli potrzebujesz nieskończonej precyzji (na przykład używając liczby π zamiast jednego z wielu krótszych stand-ins), powinieneś napisać lub użyć symbolicznego programu matematycznego.
Ale jeśli zgadzasz się z pomysłem, że czasami matematyka zmiennoprzecinkowa jest rozmyta pod względem wartości, a logika i błędy mogą się kumulować szybko, a Ty możesz napisać swoje wymagania i testy, aby to umożliwić, Twój kod często radzi sobie z tym, co jest w środku twój FPU.
źródło
Dla zabawy bawiłem się reprezentacją liczb zmiennoprzecinkowych, postępując zgodnie z definicjami standardu C99 i napisałem poniższy kod.
Kod drukuje binarną reprezentację liczb zmiennoprzecinkowych w 3 oddzielnych grupach
a następnie wypisuje sumę, która, zsumowana z wystarczającą precyzją, pokaże wartość, która naprawdę istnieje w sprzęcie.
Więc kiedy piszesz
float x = 999...
, kompilator przekształci tę liczbę w reprezentację bitową wydrukowaną przez funkcję,xx
tak aby suma wydrukowana przez funkcjęyy
była równa podanej liczbie.W rzeczywistości suma ta jest jedynie przybliżeniem. Dla liczby 999,999,999 kompilator wstawi w reprezentacji bitowej liczby zmiennoprzecinkowej liczbę 1 000 000 000
Po kodzie dołączam sesję konsoli, w której obliczam sumę warunków dla obu stałych (minus PI i 999999999), które naprawdę istnieją w sprzęcie, wstawione tam przez kompilator.
Oto sesja konsoli, w której obliczam rzeczywistą wartość liczby zmiennoprzecinkowej, która istnieje w sprzęcie. Kiedyś
bc
drukowałem sumę terminów wyprowadzanych przez program główny. Tę sumę można wstawić do pytonarepl
lub czegoś podobnego.Otóż to. Wartość 999999999 jest w rzeczywistości
Możesz również sprawdzić,
bc
że -3,14 jest również zaburzony. Nie zapomnij ustawićscale
współczynnikabc
.Wyświetlana suma jest zawarta w sprzęcie. Wartość uzyskana przez obliczenie zależy od ustawionej skali. Ustawiłem
scale
współczynnik na 15. Matematycznie, z nieskończoną precyzją, wydaje się, że jest to 1 000 000 000.źródło
Inny sposób spojrzenia na to: używane są 64 bity do reprezentowania liczb. W rezultacie nie można w żaden sposób podać więcej niż 2 ** 64 = 18 446,744,073,709,551,616 różnych liczb.
Jednak matematyka mówi, że istnieje już nieskończenie wiele miejsc po przecinku między 0 a 1. IEE 754 definiuje kodowanie, aby efektywnie wykorzystywać te 64 bity dla znacznie większej liczby cyfr plus NaN i +/- nieskończoność, więc istnieją luki między dokładnie reprezentowanymi liczbami wypełnionymi liczby tylko przybliżone.
Niestety 0,3 pozostaje w przerwie.
źródło
Wyobraź sobie pracę w bazie dziesiątej z, powiedzmy, 8 cyframi dokładności. Sprawdzasz czy
i dowiedz się, że to powraca
false
. Dlaczego? Cóż, jako liczby rzeczywiste mamy1/3 = 0,333 .... i 2/3 = 0,666 ....
Obcinamy z dokładnością do ośmiu miejsc po przecinku
co oczywiście różni się od
1.00000000
dokładnie0.00000001
.Sytuacja dla liczb binarnych ze stałą liczbą bitów jest dokładnie analogiczna. Jako liczby rzeczywiste mamy
1/10 = 0,0001100110011001100 ... (podstawa 2)
i
1/5 = 0,0011001100110011001 ... (podstawa 2)
Jeśli skrócimy je do, powiedzmy, siedmiu bitów, wtedy dostaniemy
z drugiej strony
3/10 = 0,01001100110011 ... (podstawa 2)
który jest obcięty do siedmiu bitów
0.0100110
i różnią się one dokładnie0.0000001
.Dokładna sytuacja jest nieco bardziej subtelna, ponieważ liczby te są zwykle przechowywane w notacji naukowej. Tak więc, na przykład, zamiast przechowywać 1/10, ponieważ
0.0001100
możemy go przechowywać jako coś w rodzaju1.10011 * 2^-4
, w zależności od liczby bitów, które przydzieliliśmy wykładnikowi i mantysie. Wpływa to na liczbę cyfr dokładności uzyskanych w obliczeniach.Rezultatem jest to, że z powodu tych błędów zaokrąglania zasadniczo nigdy nie chcesz używać == na liczbach zmiennoprzecinkowych. Zamiast tego możesz sprawdzić, czy wartość bezwzględna ich różnicy jest mniejsza niż jakaś stała mała liczba.
źródło
Od wersji Python 3.5 możesz używać
math.isclose()
funkcji do testowania przybliżonej równości:źródło
Ponieważ ten wątek nieco rozgałęził się na ogólną dyskusję na temat bieżących implementacji zmiennoprzecinkowych, dodam, że istnieją projekty naprawiania ich problemów.
Spójrz na przykład na https://posithub.org/ , który pokazuje typ liczbowy o nazwie posit (i jego poprzednik unum), który obiecuje zaoferować lepszą dokładność przy mniejszej liczbie bitów. Jeśli moje rozumowanie jest poprawne, rozwiązuje to również rodzaj problemów w pytaniu. Całkiem interesujący projekt, osobą stojącą za nim jest matematyk dr John Gustafson . Całość jest open source, z wieloma rzeczywistymi implementacjami w C / C ++, Python, Julia i C # ( https://hastlayer.com/arithmetics ).
źródło
Od https://0.30000000000000004.com/
źródło
Liczby dziesiętne, takie jak
0.1
,0.2
i0.3
nie są reprezentowane dokładnie w typach zmiennoprzecinkowych kodowanych binarnie. Suma aproksymacji0.1
i0.2
różni się od aproksymacji zastosowanej dla0.3
, stąd kłamstwo,0.1 + 0.2 == 0.3
co można lepiej zobaczyć tutaj:Wynik:
Aby obliczenia te były oceniane w bardziej niezawodny sposób, należy użyć reprezentacji dziesiętnej dla wartości zmiennoprzecinkowych. Standard C domyślnie nie określa takich typów, ale jako rozszerzenie opisane w raporcie technicznym .
Te
_Decimal32
,_Decimal64
i_Decimal128
typy mogą być dostępne w systemie (na przykład GCC obsługuje je na wybrane cele , ale Clang nie obsługuje ich na OS X ).źródło
Math.sum (javascript) .... rodzaj zamiany operatora
Chodzi o to, aby zamiast operatorów liczb zmiennoprzecinkowych używać matematyki
Math.sum automatycznie wykrywa precyzję użycia
Math.sum akceptuje dowolną liczbę argumentów
źródło
Właśnie zobaczyłem ten interesujący problem wokół zmiennoprzecinkowych:
Rozważ następujące wyniki:
Widzimy wyraźnie punkt przerwania, kiedy
2**53+1
- wszystko działa dobrze do2**53
.Dzieje się tak z powodu podwójnej precyzji pliku binarnego: binarny zmiennoprzecinkowy format podwójnej precyzji IEEE 754: binary64
Ze strony Wikipedii dla formatu zmiennoprzecinkowego o podwójnej precyzji :
Dzięki @a_guest za wskazanie mi tego.
źródło
Inne pytanie zostało nazwane jako duplikat tego:
W C ++ dlaczego wynik
cout << x
różni się od wartości, dla której pokazuje debuggerx
?x
W pytaniu jestfloat
zmienna.Przykładem może być
Debuger pokazuje
9.89999962
, że wynikiemcout
działania jest9.9
.Okazuje się, że
cout
domyślna precyzjafloat
wynosi 6, więc zaokrągla do 6 cyfr dziesiętnych.Zobacz tutaj w celach informacyjnych
źródło