Wiem, że większość liczb dziesiętnych nie ma dokładnej reprezentacji zmiennoprzecinkowej ( czy matematyka zmiennoprzecinkowa jest zepsuta? ).
Ale nie rozumiem, dlaczego 4*0.1
jest ładnie drukowane 0.4
, ale 3*0.1
nie 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')
0.3000000000000000444089209850062616169452667236328125
jako0.30000000000000004
i0.40000000000000002220446049250313080847263336181640625
tak,.4
jakby miały taką samą dokładność, a zatem nie odpowiada na pytanie.Odpowiedzi:
Prosta odpowiedź jest taka, ponieważ z
3*0.1 != 0.3
powodu 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
.hex
metody 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 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ęc
4*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
repr
został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.3
i0.1*3
dokł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 rezultacierepr
silnik Pythona 3 wybiera wyświetlanie jednego z niewielkim widocznym błędem.źródło
.hex()
; nie wiedziałem, że istnieje.)e
to, że jest to już cyfra szesnastkowa. Możep
dla potęgi zamiast wykładnika .p
w 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). Kiedyfloat.hex
ifloat.fromhex
został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ć.repr
(orazstr
w 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ć:
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:źródło
3*0.1 == 0.3
i4*0.1 == 0.4
?Oto uproszczony wniosek z innych odpowiedzi.
źródło
str
irepr
są identyczne dla liczb zmiennoprzecinkowych. W przypadku Pythona 2.7repr
ma właściwości, które identyfikujesz, alestr
jest znacznie prostszy - po prostu oblicza 12 cyfr znaczących i na ich podstawie tworzy łańcuch wyjściowy. W przypadku Pythona <= 2,6 obarepr
istr
są oparte na ustalonej liczbie cyfr znaczących (17 dlarepr
, 12 dlastr
). (I nikogo nie obchodzi Python 3.0 czy Python 3.1 :-)repr
więc zachowanie Pythona 2.7 byłoby identyczne ...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
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).
źródło