Dlaczego wartość zmiennoprzecinkowa 4 * 0,1 wygląda ładnie w Pythonie 3, a 3 * 0,1 nie?


158

Wiem, że większość liczb dziesiętnych nie ma dokładnej reprezentacji zmiennoprzecinkowej ( czy matematyka zmiennoprzecinkowa jest zepsuta? ).

Ale nie rozumiem, dlaczego 4*0.1jest ładnie drukowane 0.4, ale 3*0.1nie jest, gdy obie wartości mają w rzeczywistości brzydkie reprezentacje dziesiętne:

>>> 3*0.1
0.30000000000000004
>>> 4*0.1
0.4
>>> from decimal import Decimal
>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')

7
Ponieważ niektóre liczby mogą być reprezentowane dokładnie, a inne nie.
Morgan Thrapp

58
@MorganThrapp: nie, nie jest. PO pyta o raczej arbitralny wybór formatowania. Ani 0,3, ani 0,4 nie można przedstawić dokładnie w postaci binarnej zmiennoprzecinkowej.
Batszeba

42
@BartoszKP: Kilkakrotne przeczytanie tego dokumentu nie wyjaśnia, dlaczego Python wyświetla się 0.3000000000000000444089209850062616169452667236328125jako 0.30000000000000004i 0.40000000000000002220446049250313080847263336181640625tak, .4jakby miały taką samą dokładność, a zatem nie odpowiada na pytanie.
Mooing Duck

6
Zobacz także stackoverflow.com/questions/28935257/… - Jestem trochę zirytowany, że został zamknięty jako duplikat, ale ten nie.
Random832

12
Ponownie otwarte, proszę nie zamykać tego, ponieważ duplikat komunikatu „czy matematyka zmiennoprzecinkowa jest uszkodzony” .
Antti Haapala

Odpowiedzi:


301

Prosta odpowiedź jest taka, ponieważ z 3*0.1 != 0.3powodu błędu kwantyzacji (zaokrąglenia) (podczas gdy4*0.1 == 0.4 gdy mnożenie przez potęgę dwójki jest zwykle operacją „dokładną”).

Możesz użyć tej .hexmetody w Pythonie, aby wyświetlić wewnętrzną reprezentację liczby (w zasadzie dokładną binarną wartość zmiennoprzecinkową zamiast przybliżenia podstawy 10). Może to pomóc wyjaśnić, co się dzieje pod maską.

>>> (0.1).hex()
'0x1.999999999999ap-4'
>>> (0.3).hex()
'0x1.3333333333333p-2'
>>> (0.1*3).hex()
'0x1.3333333333334p-2'
>>> (0.4).hex()
'0x1.999999999999ap-2'
>>> (0.1*4).hex()
'0x1.999999999999ap-2'

0,1 to 0x1.999999999999a razy 2 ^ -4. „A” na końcu oznacza cyfrę 10 - innymi słowy, 0,1 binarnie zmiennoprzecinkowe jest bardzo nieznacznie większe niż „dokładna” wartość 0,1 (ponieważ końcowe 0x0,99 jest zaokrąglane w górę do 0x0.a). Kiedy pomnożymy to przez 4, potęgę dwóch, wykładnik przesuwa się w górę (z 2 ^ -4 do 2 ^ -2), ale poza tym liczba pozostaje niezmieniona, więc4*0.1 == 0.4 .

Jednak gdy pomnożymy przez 3, niewielka różnica między 0x0.99 a 0x0.a0 (0x0.07) powiększa się do błędu 0x0,15, który pojawia się jako jednocyfrowy błąd na ostatniej pozycji. Powoduje to, że 0,1 * 3 jest bardzo nieznacznie większe niż zaokrąglona wartość 0,3.

Zmienna zmiennoprzecinkowa Pythona 3 reprzostała zaprojektowana tak, aby była możliwa do przełączenia w obie strony , to znaczy pokazana wartość powinna być dokładnie zamienialna na wartość oryginalną. W związku z tym nie może wyświetlać 0.3i 0.1*3dokładnie w ten sam sposób, w przeciwnym razie dwie różne liczby zakończyłyby się tak samo po zadziałaniu w obie strony. W rezultacie reprsilnik Pythona 3 wybiera wyświetlanie jednego z niewielkim widocznym błędem.


25
Dziękuję, to niezwykle wyczerpująca odpowiedź. (W szczególności dziękuję za pokazanie .hex(); nie wiedziałem, że istnieje.)
NPE

21
@supercat: Python próbuje znaleźć najkrótszy ciąg, który zaokrągliłby się do żądanej wartości , cokolwiek by to nie było. Oczywiście oszacowana wartość musi mieścić się w granicach 0,5 μg (lub zaokrągliłaby się do czegoś innego), ale może wymagać większej liczby cyfr w niejednoznacznych przypadkach. Kod jest bardzo trudny, ale jeśli chcesz rzucić
nneonneo

2
@supercat: Zawsze najkrótszy ciąg znajdujący się w granicach 0,5 ulp. ( Ściśle wewnątrz, jeśli patrzymy na zmiennoprzecinkowy z nieparzystym LSB; tj. Najkrótszy ciąg, który sprawia, że ​​działa z łącznikami okrągłymi do parzystych). Wszelkie wyjątki od tej reguły są błędami i należy je zgłosić.
Mark Dickinson,

7
@MarkRansom Z pewnością użyli czegoś innego niż eto, że jest to już cyfra szesnastkowa. Może pdla potęgi zamiast wykładnika .
Bergi,

11
@Bergi: Stosowanie pw tym kontekście sięga (przynajmniej) do C99, a także pojawia się w IEEE 754 oraz w różnych innych językach (w tym w Javie). Kiedy float.hexi float.fromhexzostały wdrożone (przeze mnie :-), Python po prostu kopiował to, co było wówczas ustaloną praktyką. Nie wiem, czy intencją było „p” dla „Power”, ale wydaje się, że to dobry sposób, aby o tym pomyśleć.
Mark Dickinson,

75

repr(oraz strw Pythonie 3) wypisze tyle cyfr, ile potrzeba, aby wartość była jednoznaczna. W tym przypadku wynik mnożenia3*0.1 nie jest najbliższą wartości 0,3 (0x1,33333333333p-2 w zapisie szesnastkowym), w rzeczywistości jest o jeden LSB wyższy (0x1,3333333333334p-2), więc potrzeba więcej cyfr, aby odróżnić go od 0,3.

Z drugiej strony mnożenie 4*0.1 daje wartość najbliższą 0,4 (0x1.999999999999ap-2 w zapisie szesnastkowym), więc nie wymaga żadnych dodatkowych cyfr.

Możesz to dość łatwo zweryfikować:

>>> 3*0.1 == 0.3
False
>>> 4*0.1 == 0.4
True

Użyłem powyższej notacji szesnastkowej, ponieważ jest ładny i zwarty i pokazuje różnicę bitową między tymi dwiema wartościami. Możesz to zrobić samodzielnie używając np (3*0.1).hex(). Jeśli wolisz zobaczyć je w całej ich dziesiętnej okazałości, proszę bardzo:

>>> Decimal(3*0.1)
Decimal('0.3000000000000000444089209850062616169452667236328125')
>>> Decimal(0.3)
Decimal('0.299999999999999988897769753748434595763683319091796875')
>>> Decimal(4*0.1)
Decimal('0.40000000000000002220446049250313080847263336181640625')
>>> Decimal(0.4)
Decimal('0.40000000000000002220446049250313080847263336181640625')

2
(+1) Dobra odpowiedź, dzięki. Czy uważasz, że warto byłoby zilustrować punkt „nie jest to najbliższa wartość”, dołączając wynik 3*0.1 == 0.3i 4*0.1 == 0.4?
NPE

@NPE Powinienem był to zrobić zaraz za bramą, dzięki za sugestię.
Mark Ransom

Zastanawiam się, czy warto byłoby zwrócić uwagę na dokładne wartości dziesiętne najbliższych „podwójnych” do 0,1, 0,3 i 0,4, ponieważ wiele osób nie potrafi czytać zmiennoprzecinkowego szesnastkowego.
supercat

@supercat masz rację. Umieszczenie tych super dużych podwójnych w tekście byłoby rozpraszające, ale pomyślałem o sposobie ich dodania.
Mark Ransom

25

Oto uproszczony wniosek z innych odpowiedzi.

Jeśli zaznaczysz zmiennoprzecinkowy w wierszu poleceń Pythona lub wydrukujesz go, przejdzie on przez funkcję, reprktóra tworzy swoją reprezentację w postaci ciągu.

Począwszy od wersji 3.2, Python strirepr używanie złożonego systemu zaokrąglania, która preferuje ładną dziesiętne jeśli to możliwe, ale wymaga więcej cyfr, gdzie niezbędne do bijective gwarancji (jeden do jednego) mapowanie pomiędzy pływaków i ich reprezentacje smyczkowych.

Ten schemat gwarantuje, że wartość repr(float(s))wygląda ładnie dla prostych liczb dziesiętnych, nawet jeśli nie można ich dokładnie przedstawić jako liczby zmiennoprzecinkowe (np. Kiedy s = "0.1").

Jednocześnie gwarantuje utrzymanie float(repr(x)) == xkażdego spławikax


2
Twoja odpowiedź jest poprawna dla wersji Pythona> = 3.2, gdzie stri reprsą identyczne dla liczb zmiennoprzecinkowych. W przypadku Pythona 2.7 reprma właściwości, które identyfikujesz, ale strjest znacznie prostszy - po prostu oblicza 12 cyfr znaczących i na ich podstawie tworzy łańcuch wyjściowy. W przypadku Pythona <= 2,6 oba repri strsą oparte na ustalonej liczbie cyfr znaczących (17 dla repr, 12 dla str). (I nikogo nie obchodzi Python 3.0 czy Python 3.1 :-)
Mark Dickinson

Dzięki @MarkDickinson! W odpowiedzi zawarłem Twój komentarz.
Aivar

2
Zauważ, że zaokrąglenie z powłoki pochodzi z, reprwięc zachowanie Pythona 2.7 byłoby identyczne ...
Antti Haapala

5

Niezbyt specyficzne dla implementacji Pythona, ale powinno mieć zastosowanie do wszystkich funkcji typu float do dziesiętnych funkcji łańcuchowych.

Liczba zmiennoprzecinkowa jest zasadniczo liczbą binarną, ale w notacji naukowej z ustalonym limitem cyfr znaczących.

Odwrotność dowolnej liczby, która ma współczynnik liczby pierwszej, który nie jest dzielony z podstawą, zawsze spowoduje powtarzającą się reprezentację kropki. Na przykład 1/7 ma czynnik pierwszy, 7, który nie jest dzielony z 10, a zatem ma powtarzającą się reprezentację dziesiętną, i to samo dotyczy 1/10 z czynnikami pierwszymi 2 i 5, które nie są dzielone z 2 ; oznacza to, że 0,1 nie może być dokładnie reprezentowane przez skończoną liczbę bitów po kropce.

Ponieważ 0.1 nie ma dokładnej reprezentacji, funkcja, która konwertuje przybliżenie na ciąg przecinka dziesiętnego, zwykle próbuje aproksymować pewne wartości, aby nie uzyskać nieintuicyjnych wyników, takich jak 0,1000000000004121.

Ponieważ liczba zmiennoprzecinkowa jest zapisana w notacji naukowej, mnożenie przez potęgę podstawy wpływa tylko na wykładnik liczby. Na przykład 1,231e + 2 * 100 = 1,231e + 4 dla notacji dziesiętnej i podobnie 1,00101010e11 * 100 = 1,00101010e101 w notacji binarnej. Jeśli pomnożę przez nie potęgę podstawy, wpłynie to również na cyfry znaczące. Na przykład 1,2e1 * 3 = 3,6e1

W zależności od zastosowanego algorytmu może próbować odgadnąć typowe liczby dziesiętne na podstawie tylko cyfr znaczących. Zarówno 0,1, jak i 0,4 mają te same cyfry znaczące w systemie dwójkowym, ponieważ ich liczby zmiennoprzecinkowe są zasadniczo obcięciami odpowiednio (8/5) (2 ^ -4) i (8/5) (2 ^ -6). Jeśli algorytm zidentyfikuje wzorzec sigfig 8/5 jako dziesiętną 1,6, to będzie działał na 0,1, 0,2, 0,4, 0,8 itd. Może również mieć magiczne wzorce sigfig dla innych kombinacji, takich jak liczba zmiennoprzecinkowa 3 podzielona przez liczbę zmiennoprzecinkową 10 i inne magiczne wzorce, które statystycznie prawdopodobnie zostaną utworzone przez podzielenie przez 10.

W przypadku 3 * 0,1 kilka ostatnich cyfr znaczących prawdopodobnie będzie się różnić od dzielenia liczby zmiennoprzecinkowej 3 przez liczbę zmiennoprzecinkową 10, co spowoduje, że algorytm nie rozpozna magicznej liczby dla stałej 0,3 w zależności od tolerancji utraty precyzji.

Edycja: https://docs.python.org/3.1/tutorial/floatingpoint.html

Co ciekawe, istnieje wiele różnych liczb dziesiętnych, które mają ten sam najbliższy ułamek dwójkowy. Na przykład liczby 0,1 i 0,10000000000000001 i 0,1000000000000000055511151231257827021181583404541015625 są wszystkie w przybliżeniu przybliżone przez 3602879701896397/2 ** 55. Ponieważ wszystkie te wartości dziesiętne mają to samo przybliżenie, każda z nich może zostać wyświetlona przy jednoczesnym zachowaniu odwrotnej (odwrotnej) ) == x.

Nie ma tolerancji dla utraty precyzji, jeśli float x (0,3) nie jest dokładnie równe float y (0,1 * 3), to repr (x) nie jest dokładnie równe repr (y).


4
To tak naprawdę nie dodaje wiele do istniejących odpowiedzi.
Antti Haapala

1
„W zależności od zastosowanego algorytmu może próbować odgadnąć typowe ułamki dziesiętne na podstawie tylko cyfr znaczących”. <- To wygląda na czystą spekulację. Inne odpowiedzi opisują, co właściwie robi Python .
Mark Dickinson
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.