Do arytmetyki zmiennoprzecinkowej
Przykład. Załóżmy, że wartości funkcji zostały obliczone na komputerze. Może się zdarzyć, że wykres funkcji ma mnóstwo różnych miejsc zerowych w okolicy , a sama funkcja nie jest gładka.
Tymczasem, ponieważ także , to funkcja ma dokładnie jedno miejsce zerowe (krotność pierwiastka – 4).
Wykonywanie realnych obliczeń na liczbach rzeczywistych w komputerze może być źródłem wielu innych zaskoczeń.
Przykład. W komputerze co można łatwo sprawdzić:
octave:7> 10 * (1.1 -1) - 1
ans = 8.8818e-16
Jaki wynik dostaniemy kalkulatorze?
Dlatego w praktyce numerycznej należy wystrzegać się testów w rodzaju
if (x == 1.0)
{
....
}
Źródło problemów leży w zbyt uproszczonym modelu obliczeniowym. Matematycznym modelem arytmetyki maszyny cyfrowej jest arytmetyka zmiennoprzecinkowa .
Niech będzie zadana liczba naturalna (jej znaczenie wyjaśni się dalej). Dowolną liczbę rzeczywistą można jednoznacznie przedstawić w postaci
,
gdzie jest znakiem, liczba całkowita cechą, a liczba rzeczywista mantysą liczby .
Zauważmy, że taki rozkład jest jednoznaczny i odpowiada przesuwaniu przecinka w rozwinięciu binarnym liczby do pierwszej cyfry znaczącej, tj. różnej od zera (stąd nazwa: reprezentacja zmiennoprzecinkowa (floating point)).
Mantysa ma w ogólności nieskończenie wiele cyfr binarnych w swoim rozwinięciu dwójkowym
.
W komputerach osobistych mamy do czynienia z reprezentacją liczb rzeczywistych, w której do zapisania liczby używa się ściśle określonej liczby bitów do zapisania mantysy i także określonej liczby bitów do zapisania cechy danej liczby niezerowej
Liczby zapisane przy użyciu powyższej sekwencji bitów nazywa się liczbami maszynowymi. Są to jedyne dokładnie zapisywalne w komputerze liczby rzeczywiste, pozostałe będą musiały zostać wyrażone z wykorzystaniem liczb maszynowych.
Reprezentacją zmiennoprzecinkową niezerowej liczby będziemy nazywać liczbę taką, że
gdzie jest liczbą dwójkową postaci , natomiast jest liczbą naturalną postaci .
Na znak liczby, , przeznaczony jest jeden bit. Wartości i dobiera się tak, żeby była tak bliska jak to możliwe. Stałą całkowitą dobiera się tak, by uzyskać zbalansowany zakres cechy (mniej więcej tyle samo wartości ujemnych i dodatnich), a zysk z korzystania z niej jest taki, że nie marnujemy dodatkowego bitu na przechowywanie znaku wykładnika potęgi dwójki .
Przy takim sposobie reprezentacji, jej błąd względny szacuje się przez
gdzie liczbę nazywa się precyzją arytmetyki. Precyzja arytmetyki zależy wyłącznie od liczby bitów przeznaczonych na reprezentację mantysy. Ostatnią nierówność wygodnie jest zapisać w równoważny sposób jako
Przykład. Rozważmy system, w którym zarówno na cechę, jak i mantysę, przeznaczone są jedynie po dwa bity, zatem jedna liczba maszynowa zajmuje 5 bitów. Ponieważ możliwy zakres wartości jest , to przyjmiemy korektę , dzięki czemu . Z kolei możliwe wartości mantysy to
Wobec tego, jedyne (dodatnie) liczby maszynowe naszej pięciobitowej arytmetyki zmiennopozycyjnej to
Liczby maszynowe: reprezentowane dokładnie w pięciobitowej arytmetyce o precyzji . (Przedstawiliśmy tylko liczby nieujemne)
Typ IEEE 754
Pojedynczej precyzji
Podwójnej precyzji
Nazwa typu w C
float
double
Liczba bitów cechy
8
11
Liczba bitów mantysy
23
52
Liczba bajtów dla typu w C
4
Bias (liczba powyżej)
127
1023
Orientacyjny zakres
Orientacyjna precyzja
Procesory Intel’a mają jedną z najlepszych implementacji standardu IEEE 754).
W Octave można łatwo podejrzeć reprezentację binarną liczby zmiennoprzecinkowej podwójnej precyzji (jest to domyślny typ numeryczny stosowany w MATLABie i Octave),
octave:9> format bit
octave:10> x = -2
x = 1100000000000000000000000000000000000000000000000000000000000000
octave:11> x = 1/4
x = 0011111111010000000000000000000000000000000000000000000000000000
octave:13> x = 0
x = 0000000000000000000000000000000000000000000000000000000000000000
octave:15> x = 0.1
x = 0011111110111001100110011001100110011001100110011001100110011010
(w MATLABie możemy zobaczyć tę samą liczbę w zapisie szestnastkowym).
Rozwinięcie dwójkowe liczby 0.1 jest nieskończone:
Ten banalny fakt jest bardzo często przeoczony przez programistów, a w 1991 roku doprowadził do awarii systemu antyrakietowego Patriot. Okazało się, że rakiety Patriot traciły skuteczność, gdy przez wiele godzin pozostawały w stanie gotowości. Jak zbadano, w celu pomiaru czasu, zliczano kolejne tyknięcia zegara rakiety, które następowały dokładnie co 0.1 sekundy. Następnie, w celu wyznaczenia prawdziwego czasu, mnożono liczbę tknięć zegara przez 0.1 (które właśnie było niedokładnie reprezentowane). Gdy cykli zegara było bardzo dużo, błąd bezwzględny wyznaczenia czasu stawał się na tyle poważny, że uniemożliwiał precyzyjne wyznaczenie parametrów toru lotu nieprzyjacielskiego obiektu.
Nie wszystkie maszyny liczące wykorzystują reprezentację dwójkową.
Arytmetyka IEEE 754 przyjmuje, że liczby dla których następuje overflow są reprezentowane przez specjalną wartość Inf (nieskończoność, ze znakiem), która propaguje się w obliczeniach zgodnie z powszechnie przyjętymi regułami, np. 1+Inf daje Inf, 1/Inf daje 0, Inf-Inf daje NaN, itd.
Np. wszystkie liczby większe od największej zapisywalnej liczby są reprezentowane przez Inf (na przykładzie 5-bitowej arytmetyki)
W dalszych rozważaniach zjawiska nadmiaru i niedomiaru będziemy dla uproszczenia zaniedbywać, jednak nie zawsze jest to uzasadnione, o czym niech świadczy poniższy przykład.
Przykład. Jedną z najczęściej wykonywanych operacji na wektorze jest obliczenie jego normy euklidesowej
Jak widać, możemy tu łatwo zetknąć się ze zjawiskiem zarówno niedomiaru, jak i nadmiaru, gdyż może się na przykład tak złożyć, że mimo iż jest reprezentowana, to już nie (np. w arytmetyce podwójnej precyzji i ). Łatwym wyjściem z tej sytuacji jest wstępna normalizacja danych tak, by wszystkie nie były większe od 1: niech i wtedy
i teraz suma pod pierwiastkiem jest zawsze pomiędzy 1 a .
Liczby denormalizowane. Wymaganie, że mantysa jest postaci , , powoduje, że wokół zera pojawia się coś w rodzaju próżni. Formalnie, liczby mniejsze niż powinny być reprezentowane przez 0, lecz zazwyczaj zamiast tego
octave:16> format bit
octave:17> x = 2^(-1022)
x = 0000000000010000000000000000000000000000000000000000000000000000
...
kinia04