Login lub e-mail Hasło   

Liczby zmiennoprzecinkowe

Odnośnik do oryginalnej publikacji: http://www.i-lo.tarnow.pl/edu/inf/alg/nu(...)ex.html
Zagadnienie liczb zmiennoprzecinkowych (ang. floating point numbers - FP) opisaliśmy szczegółowo w poprzednich rozdziałach . Tutaj zajmiemy się ich konkretną realizacją...
Wyświetlenia: 22.731 Zamieszczono 02/11/2006

Zagadnienie liczb zmiennoprzecinkowych (ang. floating point numbers - FP) opisaliśmy szczegółowo w poprzednich rozdziałach. Tutaj zajmiemy się ich konkretną realizacją w systemie binarnym. Przypomnijmy, wartość liczby zmiennoprzecinkowej obliczamy według wzoru:

L(FP) = m x pc

gdzie:

L(FP) - wartość liczby zmiennoprzecinkowej
m - mantysa
p - podstawa
c - cecha

W systemie dwójkowym wszystkie trzy elementy m, p i c będą zapisane dwójkowo za pomocą odpowiednio dobranego systemu kodowania liczb. Podstawa p zawsze będzie równa 2, zatem wzór obliczeniowy przyjmie postać:

L(FP) = m x 2c

Jak widać, do określenia formatu zmiennoprzecinkowego pozostaje nam podanie sposobu kodowania mantysy i cechy, ponieważ podstawa p=2 będzie zawsze znana.

Na potrzeby tego artykułu zdefiniujemy bardzo prosty dwójkowy system zmiennoprzecinkowy. Pozwoli nam on w zrozumiały sposób przedstawić wszystkie podstawowe właściwości rzeczywistych systemów zmiennoprzecinkowych, które dla zaawansowanych czytelników opisujemy szczegółowo w następnym rozdziale.

Słowo kodowe FP będzie zbudowane z 8 bitów ponumerowanych od 0 do 7, które podzielimy na dwie części:

Słowo kodowe FP
b7 b6 b5 b4 b3 b2 b1 b0
cecha mantysa

Cecha zawarta będzie w bitach od b4 do b7. Ustalmy, iż cecha jest liczbą całkowitą ze znakiem w kodzie U2 (Uwaga - ten sposób kodowania przyjęliśmy dla prostoty - rzeczywiste systemy zmiennoprzecinkowe stosują tutaj kodowanie z nadmiarem).  Wartość cechy obliczamy wg wzoru:

c = b7(-23) + b622 + b521 + b420 = (-8)b7 + 4b6 + 2b5 + b4

 

W słówku kodowym 10111101(FP) cecha ma wartość 1011(U2) = -8 + 2 + 1 = -5(10).

Mantysa zawarta jest w bitach od b0 do b3. Ustalamy, iż mantysa jest 4-bitową liczbą stałoprzecinkową w kodzie U2 (rzeczywiste systemy stosują kodowanie mantysy w stałoprzecinkowym kodzie ZM) Pozycję przecinka umieszczamy pomiędzy bitami b1 i b2 (jest to tylko taka nasza umowa, w rzeczywistości przecinek nie jest umieszczany w zapisie liczby, ale my wiemy, gdzie on powinien być i o to właśnie chodzi!). Zatem wartość mantysy obliczamy według wzoru:

m = b3b2 , b1b0(U2)b3(-21) + b220 + b12-1 + b02-2 = -2b3 + b2 + 1/2b1 + 1/4b2

 

W słówku kodowym 10111101(FP) mantysa ma wartość 11,01(U2) = -2 + 1 + 1/4 = -3/4(10).

Znając sposób kodowania cechy i mantysy możemy już obliczyć wartość dowolnej liczby zapisanej w tym systemie zmiennoprzecinkowym. W tym celu należy ze słowa kodu wydobyć bity cechy i mantysy. Na podstawie podanych definicji z wydobytych bitów uzyskujemy wartości cechy i mantysy, a następnie obliczamy wartość liczby zmiennoprzecinkowej zgodnie ze wzorem

L(FP) = m x 2c

 

Obliczyć wartość liczby zmiennoprzecinkowej 00010100(FP).

c = 0001(U2)
0001(U2) = 1
(10)
    m = 01,00(U2)
01,00(U2) = 1
(10)

L(FP) = m x 2c = 1 x 21 = 1 x 2 = 2
00010100(FP) = 2

 

Obliczyć wartość liczby zmiennoprzecinkowej 11010111(FP).

c = 1101(U2)
1101(U2) = -8 + 4 + 1 = -3
    m = 01,11(U2)
01,11(U2) = 1 + 1/2 + 1/4 = 13/4

L(FP) = m x 2c = 13/4 x 2-3 = 7/4 x 1/8 = 7/32
11010111(FP) = 7/32

 

Obliczyć wartość liczby zmiennoprzecinkowej 11111001(FP).

c = 1111(U2)
1111(U2) = -8 + 4 + 2 + 1 = -1
(10)
    m = 10,01(U2)
10,01(U2) = -2 + 1/4 = -13/4

L(FP) = m x 2c = -13/4 x 2-1 = -7/4 x 1/2 = -7/8
11111001(FP) = -7/8

Z podanych powyżej przykładów widzimy jasno, iż nasz system pozwala kodować liczby ułamkowe zarówno dodatnie jak i ujemne.


DLA
GENIUSZA

Wzór L(FP) = m x 2c przyjmuje wartość maksymalną dla maksymalnej cechy i maksymalnej mantysy. Cecha przyjmie wartość maksymalną dla kodu:

0111(U2) = 7(10)

Mantysa największą wartość przyjmie dla kodu:

01,11(U2) = 13/4 = 7/4

Zatem max(FP) = 7/4 x 27 = 7/4 x 128 = 7 x 32 = 224

Wartość najmniejszą uzyskamy dla maksymalnej cechy i minimalnej mantysy. Cechę maksymalną policzyliśmy już powyżej. Minimalną mantysę reprezentuje kod:

10,00(U2) = -2

Zatem min(FP) = -2 x 27 = -2 x 128 = -256

Stąd wszystkie liczby reprezentowane przez nasz kod zawierają się w przedziale:

Z(FP) = -256 ... 224


DLA
GENIUSZA

W poniższej tabeli zebraliśmy wszystkie możliwe wartości, które może reprezentować nasz kod zmiennoprzecinkowy. Po lewej stronie mamy wszystkie możliwe cechy. U góry mamy wszystkie możliwe mantysy. Wartość liczby dla danej cechy i mantysy otrzymujemy na przecięciu wiersza cechy z kolumną mantysy.



mantysa


0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111
c
e
c
h
a
0000 0 1/4 1/2 3/4 1 11/4 11/2 13/4 -2 -13/4 -11/2 -11/4 -1 -3/4 -1/2 -1/4
0001 0 1/2 1 11/2 2 21/2 3 31/2 -4 -31/2 -3 -21/2 -2 -11/2 -1 -1/2
0010 0 1 2 3 4 5 6 7 -8 -7 -6 -5 -4 -3 -2 -1
0011 0 2 4 6 8 10 12 14 -16 -14 -12 -10 -8 -6 -4 -2
0100 0 4 8 12 16 20 24 28 -32 -28 -24 -20 -16 -12 -8 -4
0101 0 8 16 24 32 40 48 56 -64 -56 -48 -40 -32 -24 -16 -8
0110 0 16 32 48 64 80 96 112 -128 -112 -96 -80 -64 -48 -32 -16
0111 0 32 64 96 128 160 192 224 -256 -224 -196 -180 -128 -96 -64 -32
1000 0 1/1024 1/512 3/1024 1/256 5/1024 3/512 7/1024 -1/128 -7/1024 -3/512 -5/1024 -1/256 -3/1024 -1/512 -1/1024
1001 0 1/512 1/256 3/512 1/128 5/512 3/256 7/512 -1/64 -7/512 -3/256 -5/512 -1/128 -3/512 -1/256 -1/512
1010 0 1/256 1/128 3/256 1/64 5/256 3/128 7/256 -1/32 -7/256 -3/128 -5/256 -1/64 -3/256 -1/128 -1/256
1011 0 1/128 1/64 3/128 1/32 5/128 3/64 7/128 -1/16 -7/128 -3/64 -5/128 -1/32 -3/128 -1/64 -1/128
1100 0 1/64 1/32 3/64 1/16 5/64 3/32 7/64 -1/8 -7/64 -3/32 -5/64 -1/16 -3/64 -1/32 -1/64
1101 0 1/32 1/16 3/32 1/8 5/32 3/16 7/32 -1/4 -7/32 -3/16 -5/32 -1/8 -3/32 -1/16 -1/32
1110 0 1/16 1/8 3/16 1/4 5/16 3/8 7/16 -1/2 -7/16 -3/8 -5/16 -1/4 -3/16 -1/8 -1/16
1111 0 1/8 1/4 3/8 1/2 5/8 3/4 7/8 -1 -7/8 -3/4 -5/8 -1/2 -3/8 -1/4 -1/8

Zwróć uwagę, iż pewne liczby występują wielokrotnie (0, 1, 2, 4), a pewnych liczb tutaj nie ma (9, 11, 13, 15). A przecież leżą one w wyliczonym wcześniej zakresie od -256 do 224. Dlaczego więc ich brakuje?

Aby to zrozumieć, spróbujmy zakodować liczbę 9 w naszym systemie zmiennoprzecinkowym. W tym celu musimy wyznaczyć cechę i mantysę. Cecha musi się zawierać w zakresie od -8 do 7, a mantysa w zakresie od -2 do 13/4. Początkowo przyjmiemy mantysę równą 9, a cechę równą 0. Następnie mantysę sprowadzimy do pożądanego zakresu dzieląc ją przez 2. Aby liczba zachowała po takim podziale swoją wartość, cecha musi być zwiększona o 1:

9 = 9 x 20 = 41/2 x 21 = 21/4 x 22 = 11/8 x 23

Otrzymaliśmy c = 3 oraz m = 11/8. Przeliczamy otrzymane wartości na kod U2:

3(10) = 0011(U2)

11/8 = 01,001(U2).

Zwróć uwagę, iż mantysa jest o 1 bit za długa - ma 5 bitów, a w naszej liczbie zmiennoprzecinkowej możemy zapamiętać tylko 4 bity mantysy. Życie jest okrutne, skoro nasz format wymaga 4 bitów, musimy odrzucić ostatni bit mantysy.

Zatem do kodu powędruje wartość 01,00(U2) zamiast wyliczonej 01,001(U2). Otrzymamy kod 00110100(FP). Szukamy w naszej tabelce i znajdujemy:

00110100(FP) = 8

Zatem z wartości 9 zrobiło się nam 8. Dlaczego? Po prostu wystąpiło zjawisko zwane utratą precyzji lub błędem zaokrąglenia. Liczba 9 wymaga większej precyzji niż oferuje nasz system i nie może w nim zostać przedstawiona dokładnie - zostanie zaokrąglona do 8.

   
   
   

Jeśli myślisz, że jest to tylko cecha naszego niedoskonałego systemu zmiennoprzecinkowego, to jesteś w grubym błędzie. Zjawisko utraty precyzji występuje w nawet najdroższym procesorze PENTIUM IV i na razie nie można nic na to poradzić. Pewnych liczb po prostu nie da się przedstawić dokładnie. Oczywiście w rzeczywistym systemie zmiennoprzecinkowym mantysa zbudowana jest z dużo większej ilości bitów, zatem jej rozdzielczość jest o wiele większa od naszej.

 
       
program precyzja;

var
x : single; // 32-bitowa zmienna fp

begin
x := 100000001; // tego zmienna nie zapamięta dokładnie!
writeln(x:0:0); // ups, utrata precyzji !!!
readln;
end.

Sprawdź wyniki pracy tego prostego programu. Czy teraz cię to dziwi?

   
   
   

Liczba da się przedstawić dokładnie w danym systemie zmiennoprzecinkowym, jeśli leży w zakresie dozwolonych wartości oraz liczba jej bitów znaczących jest mniejsza lub równa ilości bitów mantysy bez bitu znakowego.

 
       

Dla liczb dodatnich ilość bitów znaczących uzyskamy usuwając wszystkie początkowe i końcowe zera. To, co pozostanie, jest właśnie bitami znaczącymi.

Kod 00101100 ma 4 bity znaczące 1011.

Kod 00010100 ma 3 bity znaczące 101.

Dla liczb ujemnych usuwamy początkowe jedynki i końcowe zera.

Kod 11101000 ma 2 bity znaczące 01.

Kod 11110010 ma 3 bity znaczące 001.

Po tych ustaleniach już łatwo możemy określić, czy liczba da się zapisać w danym systemie zmiennoprzecinkowym. W tym celu wystarczy przeliczyć ją na system dwójkowy i obliczyć ilość bitów znaczących. Jeśli mantysa może przechować te bity, to liczba będzie przedstawiona dokładnie. Jeśli nie, mamy utratę precyzji zapisu - liczba będzie zapamiętana z zaokrągleniem.

   
   
   

Liczby zmiennoprzecinkowe przechowują wartości przybliżone - pewnych liczb nie da się zapisać w systemie zmiennoprzecinkowym. Typowym przykładem są ułamki dziesiętne typu 1/10, 2/10,3/10. Takie ułamki posiadają nieskończone rozwinięcia w systemie dwójkowym, zatem wymagają mantys o nieskończonej ilości bitów, co nie jest możliwe do zrealizowania.

Ilość bitów mantysy określa precyzję zapisu. Im jest większa, tym dokładniej można przedstawiać liczby rzeczywiste - błędy zaokrągleń są mniejsze.

Ilość bitów cechy określa dopuszczalny zakres reprezentowanych liczb.

 
       


DLA
GENIUSZA

Dodawanie i odejmowanie

Wbrew pozorom zasady arytmetyki zmiennoprzecinkowej nie są wcale trudne - jak zwykle diabeł tkwi w szczegółach. Mamy dane dwie liczby zmiennoprzecinkowe:

gdzie

L1, L2 - wartości liczb
m1, m2 - mantysy
c1, c2 - cechy

Suma lub różnica tych dwóch liczb wynosi:

Z wyliczeń tych wynika, iż mantysa sumy (lub różnicy) jest sumą (lub różnicą) mantys liczb wyjściowych po sprowadzeniu ich do wspólnej cechy (operacja ta nosi nazwę wyrównania cech liczb zmiennoprzecinkowych). Cecha sumy (lub różnicy) jest równa sumie cech dodawanych (lub odejmowanych) liczb. Po wykonaniu operacji arytmetycznej mantysa wyniku jest sprowadzana do postaci znormalizowanej i zapamiętywana w kodzie liczby zmiennoprzecinkowej.

W systemie dwójkowym operacja dzielenia przez 2 jest równoważna przesunięciu wszystkich bitów zapisu liczby o jedną pozycję w prawo (tak jak w systemie dziesiętnym podział przez 10). Z kolei mnożenie przez 2 odpowiada przesunięciu wszystkich cyfr o jedną pozycję w lewo.

Dzielenie lub mnożenie przez potęgi liczby 2 jest zatem przesuwaniem bitów o odpowiednią ilość pozycji (równą wykładnikowi potęgi liczby 2) w prawo (dzielenie) lub w lewo (mnożenie). Obie operacje są bardzo proste i nie wymagają wykonywania żadnych działań arytmetycznych (w procesorze realizują je układy zwane rejestrami przesuwnymi - ang. shift registers).

 

Obliczyć 11110100(FP) + 11100110(FP).

Z zapisu zmiennoprzecinkowego wydobywamy cechy i mantysy obu liczb:

c1 = 1111(U2) = -1; m1 = 01,00(U2)
c2 = 1110(U2) = -2; m2 = 01,10(U2)

Pierwszą mantysę musimy podzielić przez 2-2, co odpowiada mnożeniu przez 22. Zatem wszystkie jej bity przesuwamy o 2 pozycje w lewo:

m1 = 0100,00(U2)

Drugą mantysę musimy podzielić przez 2-1, co odpowiada mnożeniu przez 21. Wszystkie jej bity przesuwamy o 1 pozycję w lewo:

m2 = 011,00(U2)

Obliczamy mantysę sumy:

  0100,00
+  0011,00
  0111,00

m1 + m2 = 0111,00

Obliczamy cechę sumy:

  1111
+  1110
  1101

Mantysę sumy sprowadzamy do postaci znormalizowanej:

c = 1101(U2) ; m = 0111,00(U2) - za duża, przesuwamy o 1 bit w prawo i zwiększamy cechę o 1
c = 1110(U2) ; m = 0011,10(U2) - za duża, powtarzamy przesuw i zwiększanie cechy
c = 1111(U2) ; m = 0001,11(U2) - mantysa w zakresie, koniec operacji

Otrzymaną cechę i mantysą łączymy w jeden kod i otrzymujemy wynik operacji dodawania:

11110100(FP) + 11100110(FP) = 11110111(FP).

Sprawdźmy, czy wynik jest prawidłowy. W tym celu posługując się tabelką wyznaczamy wartości poszczególnych liczb zmiennoprzecinkowych:

11110100(FP) = 1/2
11100110(FP) = 3/8
11110111(FP) = 7/
8

1/2 + 3/8 = 4/8 + 3/8 = 7/8 - wynik prawidłowy.

 

A teraz spróbujmy zsumować liczbę dużą i małą: 01000110(FP) + 00010110(FP)  (24 + 3):

Wydobywamy cechy i mantysy:

c1 = 0100(U2) = 4; m1 = 01,10(U2)
c2 = 0001(U2) = 1; m2 = 01,10(U2)

Pierwszą mantysę przesuwamy o 1 bit w prawo, a drugą o 4 bity w prawo:

m1 = 00,11000(U2)
m2 = 00,00011(U2)

Sumujemy cechy i mantysy:

m1 + m2 = 00,11011(U2) ; c1 + c2  = 0101(U2)

Normalizujemy mantysę wyniku:

c = 0101(U2) ; m = 00,11011(U2)
c = 0100(U2) ; m = 01,10110(U2)

Łączymy otrzymaną cechę i mantysę w jeden kod zmiennoprzecinkowy otrzymując wynik dodawania:

01000110(FP) + 00010110(FP) = 01000110(FP) = 24(10).

Zwróć uwagę, iż suma jest równa pierwszej z sumowanych liczb. Zatem dodanie drugiej liczby nie wpłynęło na wynik sumowania. Nastąpiła utrata precyzji. Wynika stąd bardzo ważny wniosek:

   
   
   

W systemie zmiennoprzecinkowym sumowanie liczby dużej z liczbą małą może być niedokładne z uwagi na utratę precyzji. Dlatego sumując ciąg liczb zawsze rozpoczynajmy sumowanie od wartości najmniejszych do największych.

 
       

Aby się przekonać, iż kolejność sumowania jest bardzo istotna, uruchom poniższy program i sprawdź jego wynik - w obu przypadkach sumuje on te same liczby, jednak raz od najmniejszej do największej, a za drugim razem od największej do najmniejszej. Czy otrzymujemy ten sam wynik sumowania?

program precyzja;

var
s : single;
i : integer;
begin
s := 0;
for i := 1 to 100000000 do s := s + i/10000;
writeln(s:0:0);
s := 0;
for i := 100000000 downto 1 do s := s + i/10000;
writeln(s:0:0);
readln;
end.

Dzielenie i mnożenie

Teraz wyprowadzimy reguły mnożenia i dzielenia liczb zmiennoprzecinkowych. Mamy dane dwie liczby:

gdzie

L1, L2 - wartości liczb
m1, m2 - mantysy
c1, c2 - cechy

Wykonując proste przekształcenia algebraiczne otrzymujemy wzory na iloczyn i iloraz tych dwóch liczb:

Wynika z nich, iż mantysa iloczynu jest iloczynem mantys, cecha iloczynu jest sumą cech. Mantysa ilorazu jest ilorazem mantys, a cecha ilorazu jest różnicą cech. Po wykonaniu operacji arytmetycznej mantysę wynikową normalizujemy i łączymy z cechą otrzymując gotowy kod zmiennoprzecinkowy. Co ciekawe, operacje mnożenia i dzielenia są koncepcyjnie prostsze w systemie zmiennoprzecinkowym od operacji dodawania i odejmowania.

Sprawdzenie poprawności tych wzorów pozostawiam ambitnym czytelnikom jako zadanie domowe.

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

Podobne artykuły


16
komentarze: 5 | wyświetlenia: 9005
9
komentarze: 0 | wyświetlenia: 2783
49
komentarze: 18 | wyświetlenia: 64976
37
komentarze: 9 | wyświetlenia: 28519
17
komentarze: 4 | wyświetlenia: 14175
15
komentarze: 5 | wyświetlenia: 32760
13
komentarze: 2 | wyświetlenia: 22960
12
komentarze: 3 | wyświetlenia: 29779
12
komentarze: 2 | wyświetlenia: 18504
11
komentarze: 2 | wyświetlenia: 33151
11
komentarze: 1 | wyświetlenia: 86404
11
komentarze: 1 | wyświetlenia: 10470
10
komentarze: 1 | wyświetlenia: 34969
10
komentarze: 5 | wyświetlenia: 20414
 
Autor
Artykuł

Powiązane tematy






Brak wiadomości


Dodaj swoją opinię
W trosce o jakość komentarzy wymagamy od użytkowników, aby zalogowali się przed dodaniem komentarza. Jeżeli nie posiadasz jeszcze swojego konta, zarejestruj się. To tylko chwila, a uzyskasz dostęp do dodatkowych możliwości!
 

© 2005-2018 grupa EIOBA. Wrocław, Polska