Dlaczego liczby zmiennoprzecinkowe są niedokładne?

198

Dlaczego niektóre liczby tracą dokładność, gdy są przechowywane jako liczby zmiennoprzecinkowe?

Na przykład liczbę dziesiętną 9.2można wyrazić dokładnie jako stosunek dwóch liczb całkowitych dziesiętnych ( 92/10), z których oba można wyrazić dokładnie w postaci binarnej ( 0b1011100/0b1010). Jednak ten sam współczynnik zapisany jako liczba zmiennoprzecinkowa nigdy nie jest dokładnie równy 9.2:

32-bit "single precision" float: 9.19999980926513671875
64-bit "double precision" float: 9.199999999999999289457264239899814128875732421875

W jaki sposób tak pozornie prosta liczba może być „zbyt duża”, aby wyrazić ją w 64 bitach pamięci?

mhlester
źródło

Odpowiedzi:

241

W większości języków programowania liczby zmiennoprzecinkowe są podobne do notacji naukowej : z wykładnikiem wykładniczym i mantysą (zwaną także znaczeniem). Powiedzmy 9.2, że bardzo prosta liczba to właściwie ułamek:

5179139571476070 * 2 -49

Gdzie jest wykładnik -49i mantysa 5179139571476070. Nie można przedstawić w ten sposób niektórych liczb dziesiętnych, ponieważ wykładnik i mantysa muszą być liczbami całkowitymi. Innymi słowy, wszystkie zmiennoprzecinkowe muszą być liczbą całkowitą pomnożoną przez liczbę całkowitą równą 2 .

9.2może być po prostu 92/10, ale 10 nie może być wyrażone jako 2 n, jeżeli n jest ograniczone do wartości całkowitych.


Widząc dane

Najpierw kilka funkcji, aby zobaczyć komponenty, które tworzą wersje 32- i 64-bitowe float. Przejrzyj je, jeśli zależy ci tylko na wynikach (przykład w Pythonie):

def float_to_bin_parts(number, bits=64):
    if bits == 32:          # single precision
        int_pack      = 'I'
        float_pack    = 'f'
        exponent_bits = 8
        mantissa_bits = 23
        exponent_bias = 127
    elif bits == 64:        # double precision. all python floats are this
        int_pack      = 'Q'
        float_pack    = 'd'
        exponent_bits = 11
        mantissa_bits = 52
        exponent_bias = 1023
    else:
        raise ValueError, 'bits argument must be 32 or 64'
    bin_iter = iter(bin(struct.unpack(int_pack, struct.pack(float_pack, number))[0])[2:].rjust(bits, '0'))
    return [''.join(islice(bin_iter, x)) for x in (1, exponent_bits, mantissa_bits)]

Za tą funkcją kryje się duża złożoność i wyjaśnienie byłoby dość styczne, ale jeśli jesteś zainteresowany, ważnym zasobem dla naszych celów jest moduł struct .

Python floatjest 64-bitową liczbą o podwójnej precyzji. W innych językach, takich jak C, C ++, Java i C #, podwójna precyzja ma osobny typ double, który często jest implementowany jako 64 bity.

Kiedy wywołujemy tę funkcję w naszym przykładzie 9.2, oto, co otrzymujemy:

>>> float_to_bin_parts(9.2)
['0', '10000000010', '0010011001100110011001100110011001100110011001100110']

Interpretacja danych

Zobaczysz, że podzieliłem wartość zwracaną na trzy składniki. Te elementy to:

  • Znak
  • Wykładnik potęgowy
  • Mantissa (zwana także Znaczeniem lub Frakcją)

Znak

Znak jest przechowywany w pierwszym składniku jako pojedynczy bit. Łatwo to wytłumaczyć: 0oznacza, że ​​liczba zmiennoprzecinkowa jest liczbą dodatnią; 1oznacza, że ​​jest negatywny. Ponieważ 9.2jest dodatnia, naszą wartością znaku jest 0.

Wykładnik potęgowy

Wykładnik jest przechowywany w środkowym składniku jako 11 bitów. W naszym przypadku 0b10000000010. W systemie dziesiętnym oznacza to wartość 1026. Dziwactwo tego elementu polega na tym, że musisz odjąć liczbę równą 2 (liczba bitów) - 1 - 1, aby uzyskać prawdziwy wykładnik; w naszym przypadku oznacza to odejmowanie 0b1111111111(liczba dziesiętna 1023), aby uzyskać prawdziwy wykładnik 0b00000000011(liczba dziesiętna 3).

Mantissa

Mantysa jest przechowywana w trzecim składniku jako 52 bity. Jednak ten element ma również dziwactwo. Aby zrozumieć to dziwactwo, weź pod uwagę liczbę w notacji naukowej, taką jak:

6.0221413x10 23

Mantissa byłaby 6.0221413. Przypomnijmy, że mantysa w notacji naukowej zawsze zaczyna się od pojedynczej niezerowej cyfry. To samo dotyczy binarnych, z tym że binarne mają tylko dwie cyfry: 0i 1. Więc binarna mantysa zawsze zaczyna się od 1! Gdy pływak jest przechowywany, 1przednia część mantysy binarnej jest pomijana, aby zaoszczędzić miejsce; musimy umieścić go z tyłu naszego trzeciego elementu, aby uzyskać prawdziwą mantysę:

1.0010011001100110011001100110011001100110011001100110

Wymaga to czegoś więcej niż zwykłego dodania, ponieważ bity przechowywane w naszym trzecim składniku faktycznie reprezentują ułamkową część mantysy, po prawej stronie punktu podstawy .

Kiedy mamy do czynienia z liczbami dziesiętnymi, „przesuwamy przecinek dziesiętny” przez pomnożenie lub podzielenie przez potęgi 10. W trybie binarnym możemy zrobić to samo, mnożąc lub dzieląc przez potęgi 2. Ponieważ nasz trzeci element ma 52 bity, dzielimy przesuń go o 2 52, aby przesunąć o 52 miejsca w prawo:

0,0010011001100110011001100110011001100110011001100110

W notacji dziesiętnej, to tak samo jak dzielenie 675539944105574przez 4503599627370496dostać 0.1499999999999999. (Jest to jeden z przykładów współczynnika, który można wyrazić dokładnie w postaci binarnej, ale tylko w przybliżeniu w postaci dziesiętnej; więcej szczegółów: 675539944105574/4503599627370496 .)

Teraz, gdy przekształciliśmy trzeci składnik w liczbę ułamkową, dodanie 1daje prawdziwą mantysę.

Podsumowanie komponentów

  • Znak (pierwszy składnik): 0pozytywny, 1negatywny
  • Wykładnik (środkowy składnik): Odejmij 2 (liczba bitów) - 1 - 1, aby uzyskać prawdziwy wykładnik
  • Mantissa (ostatni komponent): Podziel przez 2 (# bitów) i dodaj, 1aby uzyskać prawdziwą mantysę

Obliczanie liczby

Łącząc wszystkie trzy części, otrzymujemy ten numer binarny:

1.0010011001100110011001100110011001100110011001100110 x 10 11

Które możemy następnie przekonwertować z binarnego na dziesiętny:

1,149999999999999999 x 2 3 (niedokładne!)

I pomnóż, aby odsłonić ostateczną reprezentację liczby, od której zaczęliśmy ( 9.2) po zapisaniu jako wartość zmiennoprzecinkowa:

9.199999999999999993


Reprezentowanie jako ułamek

9.2

Teraz, kiedy zbudowaliśmy liczbę, można ją zrekonstruować w prosty ułamek:

1.0010011001100110011001100110011001100110011001100110 x 10 11

Przesuń mantysę na liczbę całkowitą:

10010011001100110011001100110011001100110011001100110 x 10 11-110100

Konwertuj na dziesiętne:

5179139571476070 x 2 3-52

Odejmij wykładnik potęgi:

5179139571476070 x 2 -49

Zamień wykładnik ujemny na podział:

5179139571476070/2 49

Pomnóż wykładnik:

5179139571476070/562949953421312

Co równa się:

9.199999999999999993

9.5

>>> float_to_bin_parts(9.5)
['0', '10000000010', '0011000000000000000000000000000000000000000000000000']

Widać już, że mantysa ma tylko 4 cyfry, po których następuje wiele zer. Ale przejdźmy kroki.

Zbierz binarny zapis naukowy:

1,0011 x 10 11

Przesuń kropkę dziesiętną:

10011 x 10 11–100

Odejmij wykładnik potęgi:

10011 x 10 -1

Binarny na dziesiętny:

19 x 2 -1

Wykładnik ujemny do podziału:

19/2 1

Pomnóż wykładnik:

19/2

Równa się:

9.5



Dalsza lektura

mhlester
źródło
1
Istnieje również fajny samouczek, który pokazuje, jak iść w drugą stronę - biorąc pod uwagę dziesiętną reprezentację liczby, w jaki sposób konstruujesz ekwiwalent zmiennoprzecinkowy. Podejście „długiego podziału” bardzo wyraźnie pokazuje, w jaki sposób kończysz się „resztą” po próbie przedstawienia liczby. Należy dodać, jeśli chcesz być naprawdę „kanoniczny” ze swoją odpowiedzią.
Floris,
1
Jeśli mówisz o języku Python i zmiennoprzecinkowym, sugeruję przynajmniej włączenie samouczka języka Python do swoich linków: docs.python.org/3.4/tutorial/floatingpoint.html To powinien być jeden punkt wyjścia zasób dla problemów zmiennoprzecinkowych dla programistów Python. Jeśli czegoś brakuje (i prawie na pewno tak jest), otwórz problem w narzędziu do śledzenia błędów Pythona, aby uzyskać aktualizacje lub zmiany.
Mark Dickinson
@mhlester Jeśli zmieni się to w wiki społeczności, dołącz moją odpowiedź do swojej.
Nicu Stiurca,
5
Ta odpowiedź powinna zdecydowanie zawierać link do floating-point-gui.de , ponieważ jest to prawdopodobnie najlepsze wprowadzenie dla początkujących. IMO powinno nawet przewyższyć „To, co każdy informatyk powinien wiedzieć ...” - ludzie, którzy potrafią rozumieć pracę Goldberga, zwykle są już tego świadomi.
Daniel Pryden
1
„To jeden z przykładów współczynnika, który można wyrazić dokładnie w systemie binarnym, ale tylko w przybliżeniu w systemie dziesiętnym”. To nie jest prawda. Wszystkie te współczynniki „liczby na potęgę dwóch” są dokładne dziesiętnie. Wszelkie przybliżenia służą jedynie skróceniu liczby dziesiętnej - dla wygody.
Rick Regan,
29

To nie jest pełna odpowiedź ( mhlester omówił już wiele dobrych podstaw, których nie powielę ), ale chciałbym podkreślić, jak bardzo reprezentacja liczby zależy od podstawy, w której pracujesz.

Rozważ ułamek 2/3

W good-ol 'base 10 zwykle zapisujemy to jako coś w rodzaju

  • 0,666 ...
  • 0,666
  • 0,667

Kiedy patrzymy na te reprezentacje, staramy się kojarzyć każdą z nich z ułamkiem 2/3, chociaż tylko pierwsza reprezentacja jest matematycznie równa ułamkowi. Druga i trzecia reprezentacja / przybliżenie ma błąd rzędu 0,001, który jest w rzeczywistości znacznie gorszy niż błąd między 9,2 a 9,1999999999999993. W rzeczywistości druga reprezentacja nie jest nawet poprawnie zaokrąglona! Niemniej jednak nie mamy problemu z 0.666 jako przybliżeniem liczby 2/3, więc nie powinniśmy mieć problemu z przybliżeniem 9.2 w większości programów . (Tak, w niektórych programach ma to znaczenie.)

Bazy liczbowe

Oto, gdzie podstawy liczb są kluczowe. Gdybyśmy próbowali reprezentować 2/3 w bazie 3, wtedy

(2/3) 10 = 0,2 3

Innymi słowy, mamy dokładną, skończoną reprezentację dla tej samej liczby poprzez zamianę baz! Odejściem jest to, że nawet jeśli można przekonwertować dowolną liczbę na dowolną bazę, wszystkie liczby wymierne mają dokładne skończone reprezentacje w niektórych bazach, ale w innych nie .

Aby doprowadzić ten punkt do domu, spójrzmy na 1/2. Może cię zaskoczyć, że chociaż ta idealnie prosta liczba ma dokładną reprezentację w bazie 10 i 2, wymaga powtarzalnej reprezentacji w bazie 3.

(1/2) 10 = 0,5 10 = 0,1 2 = 0,1111 ... 3

Dlaczego liczby zmiennoprzecinkowe są niedokładne?

Ponieważ często są one przybliżeniami racjonalnymi, których nie można przedstawić w sposób skończony w podstawie 2 (cyfry powtarzają się), i ogólnie są przybliżeniem liczb rzeczywistych (być może nieracjonalnych), które mogą nie być reprezentowane w skończonej liczbie cyfr w żadnej bazie.

Nicu Stiurca
źródło
3
Innymi słowy, baza-3 byłaby idealna dla 1/3takiej samej, jak baza-10 jest idealna dla 1/10. Żadna frakcja nie działa w base-2
mhlester
2
@mhlester Tak. Zasadniczo zasada N jest idealna dla każdej frakcji, której mianownik jest Nwielokrotnością.
Nicu Stiurca,
2
I to jest jeden z powodów, dla których niektóre numeryczne skrzynki narzędziowe śledzą „to, co zostało podzielone przez co”, a tym samym mogą zachować „nieskończoną dokładność” dla wszystkich liczb wymiernych. Podobnie jak fizycy lubią utrzymywać swoje równania symboliczne do ostatniej możliwej chwili, na wypadek, gdyby czynniki πitp. Zostały usunięte.
Floris,
3
@Floris Widziałem również przypadki, w których algorytm, który wykonuje tylko podstawową arytmetykę (tj. Zachowuje racjonalność danych wejściowych), określa, czy dane wejściowe były (prawdopodobnie) racjonalne, wykonuje matematykę za pomocą normalnej arytmetyki zmiennoprzecinkowej, a następnie ponownie ocenia racjonalność przybliżenie na końcu, aby naprawić błędy zaokrąglania. W szczególności robi to algorytm Matlaba o zmniejszonym rzędzie w postaci szeregu, co znacznie poprawia stabilność numeryczną.
Nicu Stiurca,
@SchighSchagh - ciekawe, nie wiedziałem o tym. Wiem, że stabilność numeryczna jest czymś, czego nie uczy się wystarczająco w dzisiejszych czasach podwójnej podwójnej precyzji. Co oznacza, że ​​wielu nie rozumie elegancji wielu pięknych algorytmów. Naprawdę lubię algorytmy, które obliczają i naprawiają własne błędy.
Floris,
13

Podczas gdy wszystkie pozostałe odpowiedzi są dobre, wciąż brakuje jednej rzeczy:

To jest niemożliwe do reprezentowania liczb niewymiernych (np π, sqrt(2), log(3), itd.), Dokładnie!

I właśnie dlatego nazywane są irracjonalnymi. Żadna ilość bitów na świecie nie wystarczyłaby, aby pomieścić choć jedną z nich. Tylko arytmetyka symboliczna jest w stanie zachować ich precyzję.

Chociaż jeśli ograniczysz swoje potrzeby matematyczne do liczb wymiernych, tylko problem precyzji stanie się możliwy. Będziesz musiał przechowywać parę (prawdopodobnie bardzo dużych) liczb całkowitych ai bprzechowywać liczbę reprezentowaną przez ułamek a/b. Cała arytmetyka musiałaby być wykonywana na ułamkach, tak jak w liceum (np a/b * c/d = ac/bd.).

Ale oczywiście będzie nadal działać w tym samym rodzaju kłopoty, kiedy pi, sqrt, log, sin, itp są zaangażowane.

TL; DR

W przypadku arytmetyki przyspieszanej sprzętowo można przedstawić tylko ograniczoną liczbę liczb wymiernych. Każda niereprezentatywna liczba jest przybliżona. Niektóre liczby (tj. Irracjonalne) nigdy nie mogą być reprezentowane bez względu na system.

LumpN
źródło
4
Co ciekawe, istnieją irracjonalne podstawy. Na przykład fiński .
Veedrac
5
liczby niewymierne mogą być (tylko) reprezentowane w swojej podstawie. Na przykład pi wynosi 10 w podstawie pi
phuclv
4
Punkt pozostaje ważny: niektóre liczby nigdy nie mogą być reprezentowane bez względu na system. Niczego nie zyskujesz, zmieniając bazę, ponieważ wtedy niektóre inne liczby nie mogą być już reprezentowane.
LumpN
4

Istnieje nieskończenie wiele liczb rzeczywistych (tak wielu, że nie można ich wyliczyć), i istnieje nieskończenie wiele liczb wymiernych (można je wyliczyć).

Reprezentacja zmiennoprzecinkowa jest skończona (jak wszystko w komputerze), więc nieuchronnie wiele wielu liczb jest niemożliwych do przedstawienia. W szczególności 64 bity pozwalają jedynie rozróżnić tylko 18 446,744,073,709,551,616 różnych wartości (co jest niczym w porównaniu z nieskończonością). Zgodnie ze standardową konwencją 9.2 nie jest jednym z nich. Te, które mogą mieć postać m.2 ^ e dla niektórych liczb całkowitych mi.


Możesz wymyślić inny system numeracji, na przykład 10, gdzie 9.2 miałoby dokładną reprezentację. Ale inne liczby, powiedzmy 1/3, nadal byłyby niemożliwe do przedstawienia.


Należy również pamiętać, że liczby zmiennoprzecinkowe podwójnej precyzji są niezwykle dokładne. Mogą reprezentować dowolną liczbę w bardzo szerokim zakresie z maksymalnie 15 cyframi. Do codziennych obliczeń wystarczą 4 lub 5 cyfr. Nigdy tak naprawdę nie będziesz potrzebował tych 15, chyba że chcesz liczyć każdą milisekundę swojego życia.

Yves Daoust
źródło
1

Dlaczego nie możemy reprezentować 9.2 w binarnym zmiennoprzecinkowym?

Numery zmiennoprzecinkowe to (nieco upraszczając) system numeracji pozycyjnej z ograniczoną liczbą cyfr i ruchomym punktem bazowym.

Ułamek można wyrazić dokładnie za pomocą skończonej liczby cyfr w systemie numeracji pozycyjnej, jeżeli czynniki pierwsze mianownika (gdy ułamek jest wyrażony w najniższych wartościach) są czynnikami podstawy.

Pierwszymi czynnikami 10 są 5 i 2, więc w podstawie 10 możemy reprezentować dowolną frakcję postaci a / (2 b 5 c ).

Z drugiej strony jedynym czynnikiem podstawowym 2 jest 2, więc w podstawie 2 możemy reprezentować tylko ułamki postaci a / (2 b )

Dlaczego komputery używają tej reprezentacji?

Ponieważ jest to prosty format do pracy i jest wystarczająco dokładny do większości celów. Zasadniczo ten sam powód, dla którego naukowcy używają „notacji naukowej” i zaokrąglają swoje wyniki do rozsądnej liczby cyfr na każdym etapie.

Z pewnością byłoby możliwe zdefiniowanie formatu ułamkowego, z (na przykład) 32-bitowym licznikiem i 32-bitowym mianownikiem. Byłby w stanie reprezentować liczby, których zmiennoprzecinkowy podwójnej precyzji IEEE nie mógłby, ale równie wiele liczb mogłoby być reprezentowanych w zmiennoprzecinkowym podwójnej precyzji, których nie można przedstawić w formacie ułamka o stałym rozmiarze.

Jednak dużym problemem jest to, że taki format jest trudny do wykonania obliczeń. Z dwóch powodów.

  1. Jeśli chcesz mieć dokładnie jedną reprezentację każdej liczby, po każdym obliczeniu musisz zredukować ułamek do najniższych wartości. Oznacza to, że dla każdej operacji zasadniczo musisz wykonać największe wspólne obliczenie dzielnika.
  2. Jeśli po obliczeniach uzyskasz niereprezentatywny wynik, ponieważ licznik lub mianownik musisz znaleźć najbliższy reprezentowalny wynik. To nie jest trywialne.

Niektóre języki oferują typy ułamkowe, ale zwykle robią to w połączeniu z arbitralną precyzją, dzięki czemu nie trzeba martwić się przybliżeniem ułamków, ale stwarza to własny problem, gdy liczba przechodzi przez dużą liczbę kroków obliczeniowych wielkości mianownika i dlatego miejsce potrzebne na ułamek może eksplodować.

Niektóre języki oferują również dziesiętne typy liczb zmiennoprzecinkowych, są one głównie używane w scenariuszach, w których ważne jest, aby wyniki były zgodne z wcześniejszymi regułami zaokrąglania, które zostały napisane z myślą o ludziach (głównie obliczenia finansowe). Są nieco trudniejsze w obsłudze niż binarne zmiennoprzecinkowe, ale największym problemem jest to, że większość komputerów nie oferuje wsparcia sprzętowego.

płyn do płukania
źródło
-4

Spróbuj tego

DecimalFormat decimalFormat = new DecimalFormat("#.##");
String.valueOf(decimalFormat.format(decimalValue))));

decimalValue” to twoja wartość do przeliczenia.

Popal
źródło