Dlaczego komputery nie przechowują liczb dziesiętnych jako drugiej liczby całkowitej?

24

Komputery mają problemy z przechowywaniem liczb ułamkowych, w których mianownik jest czymś innym niż rozwiązaniem 2 ^ x. Jest tak, ponieważ pierwsza cyfra po przecinku jest warta 1/2, druga 1/4 (lub 1 / (2 ^ 1) i 1 / (2 ^ 2)) itp.

Po co radzić sobie z różnego rodzaju błędami zaokrąglania, gdy komputer mógł właśnie zapisać dziesiętną część liczby jako kolejną liczbę całkowitą (która jest zatem dokładna?)

Jedyne, co mogę wymyślić, to radzenie sobie z powtarzaniem miejsc po przecinku (w bazie 10), ale mogło być rozwiązanie skrajne (tak jak obecnie mamy nieskończoność).

SomeKittens
źródło
8
Powinieneś sprawdzić, jak są przechowywane typy dziesiętne, w przeciwieństwie do typów zmiennoprzecinkowych / podwójnych.
Oded
9
Nie wiem, jak to jest bardziej dokładne. Pierwsza cyfra po przecinku to 1/10, druga 1/100 itd. Jak to jest, że w przypadku dokładności nadal występują problemy z zaokrąglaniem (jak reprezentujesz 1/3)? Jedyna różnica polega na tym, które wartości można dokładnie przedstawić.
Martin York,
17
Dziesiętna liczba zmiennoprzecinkowa (do której mówisz dwa, tylko w bardziej niezręcznej reprezentacji) nie jest bardziej niedokładna niż binarna zmiennoprzecinkowa. Jedyna różnica polega na tym, które wartości nie mogą być reprezentowane, a ponieważ jesteśmy przyzwyczajeni do systemu dziesiętnego, nie zauważamy błędów wersji dziesiętnej. I nie, żadne nie może reprezentować wszystkich liczb racjonalnych i irracjonalnych.
1
Ostatecznie sprowadza się do wydajności. Komputery są binarne, a obwody do pracy z tą reprezentacją binarną są znacznie mniej skomplikowane. Znaczenie tego może być dziś nieco zmniejszone, ale był to czas, kiedy było to bardzo znaczące. Również każda reprezentacja, którą wybierzesz do przechowywania swojego numeru (w skończonej przestrzeni) na komputerze, będzie miała skończony zestaw wartości, które może reprezentować, i wszystkie będą wykazywać błędy zaokrąglania przy niektórych danych wejściowych. Typowy format zmiennoprzecinkowy z Mantissą i wykładnikiem oferuje znacznie większy zakres niż byłby możliwy przy użyciu dwóch liczb całkowitych.
Mr.Mindor,
1
Zdecydowanie polecam przeczytanie niektórych artykułów, do których odwołuje się moja odpowiedź na pytanie Co powoduje błędy zaokrąglania zmiennoprzecinkowego? które właśnie zaktualizowałem ze szczegółami ostatniego artykułu w odnośnej serii. W szczególności spójrz na Dlaczego Fixed Point nie wyleczy twojego zmiennoprzecinkowego bluesa .
Mark Booth

Odpowiedzi:

35

Istnieją w rzeczywistości tryby liczb, które to robią.

Arytmetyka dziesiętna (BCD) dziesiętna (BCD) powoduje, że komputer działa w bazie 10. Powodem, dla którego tak rzadko się zdarza, jest to, że marnuje miejsce: każda pojedyncza cyfra liczby zajmuje minimum cztery bity, podczas gdy komputer mógłby przechowywać do 16 wartości w tej przestrzeni. (Może być również wolniejszy, ale możliwe jest, że matematyka BCD z akceleracją sprzętową działa dobrze). To właśnie robi większość kalkulatorów, dlatego istnieją pewne klasy problemów z zaokrąglaniem, których nigdy nie trafisz na Casio za 5 USD, które zje lunch na komputerze stacjonarnym.

Inną drogą, którą możesz wybrać, jest użycie liczb wymiernych - to znaczy licznika i mianownika, zapisanych jako liczby całkowite. Jest to faktycznie dostępne w prawie wszystkich językach, jest dokładne i pozwala przechowywać wszystko w natywnych formatach binarnych. Problem polega na tym, że na koniec użytkownicy prawdopodobnie nie chcą widzieć ułamków takich jak 463/13, a nawet 35 i 8/13. Chcą zobaczyć 35,155 ... a gdy tam dotrzesz, napotkasz wszystkie typowe problemy. Dodaj, że ten format zajmuje jeszcze więcej miejsca i może być znacznie wolniejszy niż arytmetyka zmiennoprzecinkowa, a domyślnie żaden komputer nie używa tego formatu.

Tak więc: komputery mogą robić, co chcesz, ale są wolne i marnują miejsce, więc robią to tylko wtedy, gdy naprawdę muszą. Reszta czasu, szybkość i oszczędność miejsca zmiennoprzecinkowego są lepszym kompromisem.

Benjamin Pollack
źródło
Czy nie masz na myśli czterech bitów (nie bajtów) w akapicie BCD?
3
Inną opcją jest arytmetyka stałoprzecinkowa, gdzie liczba całkowita reprezentuje ułamek dziesiętny, jeśli liczba - np. Przechowywanie wartości pieniężnych (bez obliczeń z ułamkami dziesiętnymi lub procentowymi), gdzie 1 oznacza 0,01 USD.
mattnz
1
@mattnz: True - punkty stałe są szczególnym przypadkiem racjonalności.
Jon Purdy
Wspaniale, nie wiedziałem, że kalkulatorzy to zrobili.
SomeKittens
3
Istnieje trzecia opcja. Punkt zmiennoprzecinkowy z wykładnikiem dziesiętnym, na przykład sposób decimalimplementacji języka C # : stackoverflow.com/a/5019178/174335 To nie jest BCD, ponieważ nie ma indywidualnej reprezentacji cyfr dziesiętnych i nie jest stałym punktem.
Joren
38

Istnieje wiele sposobów przechowywania liczb ułamkowych, a każdy z nich ma zalety i wady.

Punkt zmiennoprzecinkowy jest zdecydowanie najpopularniejszym formatem. Działa poprzez kodowanie znaku, mantysy i podpisanego wykładnika wykładnika base-2 w liczbach całkowitych i upakowanie ich w wiązkę bitów. Na przykład, możesz mieć 32-bitową mantysę 0.5(zakodowaną jako 0x88888888) i 32-bitowy wykładnik wykładniczy +3( 0x00000003), który dekodowałby do 4.0(0.5 * 2 ^ 3). Liczby zmiennoprzecinkowe są szybkie, ponieważ są implementowane sprzętowo, a ich precyzja skaluje się z rozmiarem bezwzględnym, to znaczy im mniejsza liczba, tym lepsza jest twoja absolutna precyzja, więc względny błąd zaokrąglenia pozostaje stały przy wielkości bezwzględnej. Pływaki są doskonałe dla wartości próbkowanych z ciągłej dziedziny, takich jak długości, poziomy ciśnienia akustycznego, poziomy światła itp. Z tego powodu są one powszechnie stosowane w przetwarzaniu dźwięku i obrazu, a także w analizach statystycznych i symulacjach fizycznych. Ich największą wadą jest to, że nie są dokładne, to znaczy są podatne na błędy zaokrąglania i nie mogą dokładnie przedstawić wszystkich ułamków dziesiętnych. Wszystkie języki programowania głównego nurtu mają pewnego rodzaju zmiennoprzecinkowe.

Punkt stałydziała, używając wystarczająco dużych liczb całkowitych i niejawnie rezerwując część swoich bitów na część ułamkową. Na przykład 24,8-bitowa liczba stałoprzecinkowa rezerwuje 24 bity dla części całkowitej (łącznie ze znakiem) i 8 bitów dla części ułamkowej. Przesunięcie w prawo tej liczby o 8 bitów daje nam liczbę całkowitą. Numery stałoprzecinkowe były popularne, gdy sprzętowe jednostki zmiennoprzecinkowe były rzadkie lub co najmniej znacznie wolniejsze niż ich odpowiedniki liczb całkowitych. Chociaż liczby stałoprzecinkowe są nieco łatwiejsze do obsługi pod względem dokładności (choćby dlatego, że łatwiej je zrozumieć), są gorsze od liczb zmiennoprzecinkowych pod każdym innym względem - mają mniejszą precyzję, mniejszy zakres i ponieważ dodatkowe potrzebne są operacje, aby skorygować obliczenia dla niejawnego przesunięcia, matematyka stałoprzecinkowa jest dziś często wolniejsza niż matematyka zmiennoprzecinkowa.

Typy dziesiętne działają podobnie do liczb zmiennoprzecinkowych lub liczb stałych, ale przyjmują układ dziesiętny, to znaczy, że ich wykładnik (niejawny lub jawny) koduje potęgę-10, a nie potęgę-2. Liczba dziesiętna może na przykład zakodować mantysę 23456i wykładnik wykładni -2, a to rozszerzy się do234.56. Dziesiętne, ponieważ arytmetyka nie jest wbudowana w procesor, są wolniejsze niż zmiennoprzecinkowe, ale idealnie nadają się do wszystkiego, co wymaga liczb dziesiętnych i wymaga, aby te liczby były dokładne, z zaokrąglaniem występującym w dobrze określonych miejscach - obliczenia finansowe, tablice wyników itp. Niektóre języki programowania mają wbudowane typy dziesiętne (np. C #), inne wymagają bibliotek do ich implementacji. Zauważ, że chociaż dziesiętne mogą dokładnie reprezentować nie powtarzające się ułamki dziesiętne, ich dokładność nie jest lepsza niż w przypadku liczb zmiennoprzecinkowych; wybranie liczb dziesiętnych oznacza po prostu uzyskanie dokładnych reprezentacji liczb, które mogą być reprezentowane dokładnie w systemie dziesiętnym (podobnie jak zmienne mogą dokładnie reprezentować ułamki binarne).

Liczby wymierne przechowują licznik i denumerator, zwykle przy użyciu pewnego rodzaju liczb całkowitych bignum (typ liczbowy, który może rosnąć tak duże, jak pozwalają na to ograniczenia pamięci komputera). Jest to jedyny typ danych z grupy, który może dokładnie modelować liczby takie jak 1/3lub 3/17, a także operacje na nich - racjonalne, w przeciwieństwie do innych typów danych, będą dawać poprawne wyniki dla rzeczy takich jak3 * 1/3. Matematyka jest dość prosta, choć wymyślenie wydajnego algorytmu faktoringowego jest dość trudne. Niektóre języki programowania mają wbudowane racjonalne typy (np. Common Lisp). Wady racjonalności obejmują to, że są one powolne (wiele operacji wymaga zmniejszenia ułamków i faktoryzacji ich komponentów) oraz że wiele typowych operacji jest trudnych lub niemożliwych do wdrożenia, a większość implementacji zdegraduje racjonalność do liczby zmiennoprzecinkowej, gdy to nastąpi (np. Gdy wywołujesz sin()racjonalny).

BCD (Binary Coded Decimal) wykorzystuje „skubki” (grupy 4 bitów) do kodowania poszczególnych cyfr; ponieważ skrobak może pomieścić 16 różnych wartości, ale liczby dziesiętne wymagają tylko 10, istnieje 6 „nielegalnych” wartości na skubanie. Podobnie jak ułamki dziesiętne, liczby BCD są dokładne dziesiętnie, to znaczy obliczenia wykonywane na liczbach dziesiętnych działają tak samo, jak gdyby były zrobione za pomocą pióra i papieru. Reguły arytmetyczne dla BCD są nieco niezdarne, ale zaletą jest to, że konwersja ich na ciągi znaków jest łatwiejsza niż w przypadku niektórych innych formatów, co jest szczególnie interesujące w środowiskach o niskim zużyciu zasobów, takich jak systemy osadzone.

Ciągi , tak, zwykłe stare ciągi, mogą być również użyte do przedstawienia liczb ułamkowych. Technicznie jest to bardzo podobne do BCD, tyle że istnieje wyraźna kropka dziesiętna i używasz jednego pełnego bajtu na cyfrę dziesiętną. W związku z tym format jest marnotrawny (używanych jest tylko 11 z 256 możliwych wartości), ale łatwiej go analizować i generować niż BCD. Dodatkowo, ponieważ wszystkie użyte wartości są „niepomyślne”, nieszkodliwe i neutralne dla platformy, liczby zakodowane w łańcuchach mogą bez problemu podróżować po sieci. Rzadko zdarza się, aby arytmetyka była wykonywana bezpośrednio na ciągach, ale jest to możliwe, a gdy to zrobisz, są one tak samo dokładne dziesiętnie jak inne formaty dziesiętne (dziesiętne i BCD).

tdammers
źródło
Z pewnością 32-bitowy punkt stały ma większą precyzję niż 32-bitowy zmiennoprzecinkowy, ponieważ reprezentacje punktów stałych nie zawierają mantysy.
han
4
@han: Zależy od wielkości numeru, który chcesz zapisać. Liczby zmiennoprzecinkowe (z grubsza) zapewniają tę samą precyzję, bez względu na to, jak duża lub mała jest liczba, podczas gdy punkt stały daje pełną precyzję tylko wtedy, gdy liczba, którą chcesz zapisać, idealnie pasuje do jej zakresu.
Leo
@han Niekoniecznie oba nadal mogą reprezentować 2 ^ 32 różnych wartości. Ilość przenoszonych informacji jest identyczna, niezależnie od prezentacji. Zasięg i precyzja idą jednak w parze, więc pod tym względem arytmetyka punktu stałego może być dokładniejsza w niektórych zakresach. I pozwala uniknąć nieprzyjemnych problemów z zaokrąglaniem losowym, jeśli znasz granice, w których możesz pracować.
zxcdw
@han: mają taką samą precyzję (lub prawie). Różnica polega na tym, że w przypadku liczb stałych, precyzja (jak w wielkości dyskretnego kroku od jednej liczby do jej następcy) jest stała, podobnie jak w przypadku liczb całkowitych, podczas gdy w przypadku liczb zmiennoprzecinkowych rośnie mniej więcej liniowo z wartością bezwzględną - liczbą zmiennoprzecinkową liczba 1.0 ma większą precyzję niż liczba 10.000.000.0 (z grubsza milion razy więcej).
tdammers
6

Liczby zmiennoprzecinkowe reprezentują szeroki zakres wartości, co jest bardzo przydatne, gdy nie wiesz z góry, jakie mogą być wartości, ale jest to kompromis. Reprezentowanie 1/10 ^ 100 z drugą liczbą całkowitą nie działałoby.

Niektóre języki (i niektóre biblioteki) mają inne cechy. Lisp tradycyjnie ma nieskończoną liczbę całkowitą precyzji. Cobol ma obliczenia z liczbami stałymi dziesiętnymi.

Musisz wybrać reprezentację numeru odpowiednią dla domeny problemu.

ddyer
źródło
1

Brzmi, jakbyś opisywał liczby w punktach stałych .

Należy pamiętać, że przechowywanie części ułamkowej liczby w oddzielnym miejscu jest dokładnie identyczne z tworzeniem pojedynczej przestrzeni, dwa razy większej, i przechowywaniem całej i części ułamkowej w dwóch oddzielnych połówkach. Innymi słowy, jest to identyczne z przechowywaniem liczby jako liczby całkowitej, ale po prostu zakłada stałą liczbę miejsc dziesiętnych.

Zwykle liczby zmiennoprzecinkowe są przechowywane przy użyciu wariacji binarnej w notacji naukowej, ponieważ zwykle ważne są cyfry znaczące. Istnieje jednak wiele innych metod. Liczby dziesiętne o stałym punkcie są powszechnie stosowane, na przykład do przechowywania wartości walutowych, gdzie dokładność jest krytyczna do określonej liczby miejsc po przecinku, ale liczba wymaganych cyfr po przecinku nigdy się nie zmienia.

tylerl
źródło
1

To by się nazywało BCD, myślę, że nadal możesz go używać, jeśli naprawdę chcesz. Jednak nie jest to warte tego, ponieważ:

  1. Bardzo rzadko występuje błąd zaokrąglania z 64-bitowym zmiennoprzecinkowym
  2. Sprawia, że ​​arytmatyka jest złożona i nieefektywna
  3. Marnuje 6 wartości co 4 bity
Odwrócona lama
źródło
Matematyka BCD była często stosowana we wczesnych 8-bitowych systemach mikroprocesorowych; w rzeczywistości na jednym popularnym mikroprocesorze (6502) dodawanie i odejmowanie z BCD jest tak samo szybkie na bajt, jak w przypadku binarnego. Gry wideo często wykorzystywały matematykę BCD do utrzymywania wyników. Nie ma specjalnej obsługi dla grupowania wyników na 1 000 000 punktów. Zamiast tego dodanie 1 do „99 99 99” daje „00 00 00” z przeniesieniem, które jest ignorowane. Dodatkowy narzut związany z dodawaniem wyników w BCD jest niewielki w porównaniu z kosztem konwersji wartości binarnej na format możliwy do wyświetlenia.
supercat
1

Krótka odpowiedź jest taka, że ​​zmiennoprzecinkowy został zaprojektowany do obliczeń naukowych. Może przechowywać liczbę z (do) określoną liczbą cyfr znaczących, co ściśle pasuje do sposobu pomiaru precyzji w większości obliczeń naukowych.

Jest to zwykle obsługiwane głównie przez sprzęt, ponieważ obliczenia naukowe zwykle były tymi, które najbardziej skorzystały na wsparciu sprzętowym. Na przykład, obliczenia finansowe są często wykonywane w innych formatach - ale oprogramowanie finansowe zwykle wykonuje mało rzeczywistych obliczeń, które mimo że niezbędne formaty są obsługiwane tylko w oprogramowaniu, wydajność pozostaje całkowicie wystarczająca dla większości programów finansowych.

Jerry Coffin
źródło