Liczba zmiennoprzecinkowa

Z Wikipedii, wolnej encyklopedii
Skocz do: nawigacja, szukaj

Liczba zmiennoprzecinkowa – reprezentacja liczby rzeczywistej zapisanej za pomocą notacji naukowej. Ze względu na wygodę operowania na takich liczbach, przyjmuje się ograniczony zakres na mantysę i cechę – nazwy te mają w matematyce znaczenie podane w artykule podłoga i sufit, a w niniejszym artykule inne, powszechne w informatyce. Powoduje to, że reprezentacja liczby rzeczywistej jest tylko przybliżona, a jedna liczba zmiennoprzecinkowa może reprezentować różne liczby rzeczywiste z pewnego zakresu.

Podstawa matematyczna[edytuj | edytuj kod]

Wartość liczby zmiennoprzecinkowej jest obliczana według wzoru:

x = S \cdot M \cdot B^E

gdzie:

Mantysa jest znormalizowana, tj. należy do przedziału [1, B) (przedział prawostronnie otwarty!). Jeżeli M jest stałe, a E zmienia się, wówczas przesunięciu ulega przecinek – stąd właśnie pochodzi nazwa tej reprezentacji.

Zarówno dla mantysy jak i wykładnika liczba cyfr jest z góry ustalona. Zatem dana liczba jest reprezentowana z pewną skończoną dokładnością i należy do skończonego zbioru wartości.

Załóżmy, że m to liczba cyfr przeznaczonych na mantysę, natomiast n+1 to liczba cyfr przeznaczonych na wykładnik (n cyfr dla wartości i 1 dla znaku wykładnika). Uznaje się również, że jedna dodatkowa pozycja (najstarsza) zarezerwowana jest dla zapisu znaku całej liczby. Wówczas wartości maksymalne i minimalne dla M i E określone są następująco:

  • Wykładnik E:
    • E_{\min} = -B^{n}+1
    • E_{\max} = B^{n}-1
  • Mantysa M:
    • M_{\min} = 1
    • M_{\max} = B-B^{-(m-1)}

Stąd najmniejsza i największa liczba dodatnia możliwa do zapisania w takiej reprezentacji to:

  • x_{\min} = M_{\min} \cdot B^{E_{\min}} = 1 \cdot B^{E_{min}}
  • x_{\max} = M_{\max} \cdot B^{E_{\max}} = \left( B-B^{-(m-1)} \right) \cdot B^{E_{max}} < B^{E_{max}+1}.

Zakres liczb, które mogą być reprezentowane w danym zapisie wynosi:

[-x_{\max}, -x_{\min}] \cup \{0\} \cup [x_{\min}, x_{\max}]

Zero jest wartością specjalną, która nie może zostać bezpośrednio reprezentowana w tym formacie.

Błąd względny reprezentacji wynosi \frac{1}{B^{m-1}} (inaczej: waga najmniej znaczącej cyfry mantysy). Błędów bezwzględnych na ogół się nie podaje.

Jeżeli komputer wygeneruje liczbę |x|<B^{E_{\min}}, to traktowana jest jako niedomiar (underflow).

W przypadku, gdy otrzymana liczba |x|>M_{\max} \cdot B^{E_{\max}}, to traktowana jest jako nadmiar wykładniczy (overflow).

Przykład reprezentacji[edytuj | edytuj kod]

Przyjmijmy, że B=10, liczba cyfr dziesiętnych przeznaczonych na mantysę wynosi 4, natomiast na wykładnik 2. Chcemy zapisać wartość 60,89523.

Liczba 60,89523 odpowiada M=60,89523, E=0.

Normalizacja mantysy:

Mantysa nie należy do przedziału [1,10), zatem należy przesuwać przecinek w lewo aż będzie doń należała. Przesuwanie przecinka w lewo wiąże się ze zwiększaniem wykładnika.
M=6,089523, E=1.

Odcięcie i zaokrąglenie mantysy:

po odcięciu: 6,089,
po zaokrągleniu: 6,090.

Wynik:

6,090 \cdot 10^1=6,09E1


Przykład dla liczby mniejszej od 1: 0,0000125.

M=0,0000125, E=0.

Po normalizacji:

M=1,25,  E=-5.

Liczba cyfr znaczących jest mniejsza od dostępnej, więc nie jest potrzebne zaokrąglanie.

Wynik:

1,250 \cdot 10^-5 = 1,25E-5

Operacje na liczbach zmiennoprzecinkowych[edytuj | edytuj kod]

Własności arytmetyki zmiennoprzecinkowej[edytuj | edytuj kod]

Arytmetyka zmiennoprzecinkowa nie jest łączna. To znaczy, że dla x, y i z mogą zachodzić różności:

  •  (x + y) + z \neq x + (y + z)
  •  (x \cdot y) \cdot z \neq x \cdot (y \cdot z) ,

Nie jest też rozdzielna, czyli może zachodzić różność:

  •  x \cdot (y + z) \neq (x \cdot y) + (x \cdot z)

Innymi słowy, kolejność wykonywania operacji wpływa na końcowy wynik.

Przy obliczeniach zmiennoprzecinkowych występują też:

  • zaokrąglenia
  • nieprawidłowe operacje
  • przepełnienie
  • niedomiar

Dodawanie i odejmowanie[edytuj | edytuj kod]

Załóżmy że chcemy dodać lub odjąć dwie dodatnie liczby zmiennoprzecinkowe: x_1 = M_1 \cdot B^{E_1} oraz x_2 = M_2 \cdot B^{E_2}, przy czym x_1 \ge x_2. Założenia te można spełnić dla dowolnych liczb zmiennoprzecinkowych manipulując ich kolejnością, znakiem wyniku oraz rodzajem wykonywanej operacji, według poniższego schematu:

\begin{matrix}
(+x_1)+(+x_2), & (+x_1)-(-x_2), & (+x_2)+(+x_1), & (+x_2)-(-x_1) & \rightarrow & +(x_1+x_2) \\
(+x_1)+(-x_2), & (+x_1)-(+x_2), & (-x_2)+(+x_1), & (-x_2)-(-x_1) & \rightarrow & +(x_1-x_2) \\
(-x_1)+(-x_2), & (-x_1)-(+x_2), & (-x_2)+(-x_1), & (-x_2)-(+x_1) & \rightarrow & -(x_1+x_2) \\
(-x_1)+(+x_2), & (-x_1)-(-x_2), & (+x_2)+(-x_1), & (+x_2)-(+x_1) & \rightarrow & -(x_1-x_2) \\
\end{matrix}

Wówczas:

x_1 \pm x_2 = (M_1 \pm M_2 \cdot B^{E_2-E_1}) \cdot B^{E_1}

Jeśli liczby mają różne wykładniki, to podczas dodawania mantysa liczby o mniejszym wykładniku musi zostać zdenormalizowana – we wzorze jest to przemnożenie M_2 przez czynnik B^{E_2-E_1}. W szczególnym przypadku, jeśli E_1-E_2 jest większe niż m (liczba cyfr mantysy), to po denormalizacji mantysa będzie miała wartość 0, a liczba o mniejszym wykładniku nie wpłynie na wynik dodawania bądź odejmowania.

Odejmowanie liczb zmiennoprzecinkowych o takim samym wykładniku E i niewiele różniącej się mantysie powoduje, że wynikowa mantysa jest znacznie zdenormalizowana. Renormalizacja w takim wypadku wiąże się z wprowadzeniem sporej liczby nieznaczących zer na końcu mantysy. Jest to szczególnie niekorzystne, dlatego zwykle tak projektuje się obliczenia, by do tego nie dopuścić.

Mnożenie i dzielenie[edytuj | edytuj kod]

Mając dane liczby zmiennoprzecinkowe x_1 = S_1 \cdot M_1 \cdot B^{E_1} i x_2 = S_2 \cdot M_2 \cdot B^{E_2}:

x_1 \cdot x_2 = (S_1 \cdot S_2) \cdot (M_1 \cdot M_2) \cdot B^{E_1+E_2}
x_1/x_2 = (S_1 \cdot S_2) \cdot (M_1 / M_2) \cdot B^{E_1-E_2}

Błędy operacji elementarnych[edytuj | edytuj kod]

Wygodnie jest przedstawić liczbę zmiennoprzecinkową jako wartość dokładną zaburzoną pewnym błędem reprezentacji \varepsilon:

\bar{x} = x(1 + \varepsilon_x)

Wówczas błąd względny poszczególnych operacji elementarnych wykonywanych na liczbach \bar{a}=a(1+\varepsilon_a) oraz \bar{b}=b(1+\varepsilon_b) można oszacować następująco:

  • dodawanie/odejmowanie: \varepsilon_{a \pm b} = \frac{a \varepsilon_a \pm b \varepsilon_b}{a \pm b} + \varepsilon_{\pm}
  • mnożenie: \varepsilon_{a \cdot b} = \varepsilon_a + \varepsilon_b + \varepsilon_{\cdot}
  • dzielenie: \varepsilon_{a / b} = \varepsilon_a - \varepsilon_b + \varepsilon_{/}

gdzie \varepsilon_{\pm}, \varepsilon_{\cdot} i \varepsilon_{/} to błędy wprowadzane przez dane operacje arytmetyczne.

Rozbijając każde wyrażenie arytmetyczne na operacje elementarne można za pomocą tych zależności oszacować powstałe błędy. Istnieją jednak lepsze i szybsze metody modelowania błędów.

Implementacje sprzętowe[edytuj | edytuj kod]

Reprezentacja zmiennoprzecinkowa IEEE-754 single

W implementacjach sprzętowych liczby zmiennoprzecinkowe wyraża się liczbami dwójkowymi (B = 2). Ma to następujące zalety:

  1. Mantysa należy do przedziału [1,2), jest więc postaci 1.xxxxx.... (gdzie x – bit o dowolnej wartości). Ponieważ część całkowita jest znana i równa zawsze 1, przeto nie jest zapamiętywana, co daje dodatkowy bit na część ułamkową.
  2. Ponieważ znak liczby jest zapamiętywany na jednym bicie, przeto otrzymanie modułu lub wartości przeciwnej wymaga, odpowiednio, wyzerowania tego bitu (logiczna operacja AND), lub zmiany na wartość przeciwną (logiczna operacja XOR).

W celu ujednolicenia zasad operacji na liczbach zmiennoprzecinkowych na różnych platformach sprzętowych, opracowano standard IEEE 754, w oparciu o który realizuje się obecnie wszystkie implementacje sprzętowe liczb zmiennoprzecinkowych. Definiuje on dwie klasy liczb:

  • pojedynczej precyzji (ang. single, single precision)
  • podwójnej precyzji (ang. double, double precision)

Są również inne sposoby zapisu, różniące się jedynie liczbą bitów przeznaczoną na poszczególne pola. Np. koprocesor w procesorach x86, oprócz typów standardowych, wspiera sprzętowo liczby 10-bajtowe, natomiast kompilator Pascala – w sposób programowy liczby 6-bajtowe[1]. Liczby zgodne ze standardem IEEE 754 mają dokładnie określoną semantykę, jak na przykład: dokładność operacji elementarnych, kierunki zaokrągleń czy obsługa sytuacji wyjątkowych – są to cechy bardzo pożądane w zastosowaniach naukowych i inżynieryjnych, a ponadto ułatwiają przenoszenie kodu programu na inną platformę sprzętową.

Format Znak [bity] Wykładnik [bity] Mantysa [bity] Szerokość słowa [bity] Typy w językach programowania
IEEE-754 single 1 8 23 32 float (C), single (Pascal), real*4 (Fortran)
IEEE-754 double 1 11 52 64 double (C), real lub double (Pascal), real*8 (Fortran)
koprocesor x87 1 15 64 80 long double (C99), extended (Pascal)
Turbo Pascal 1 8 39 48 real
SSE5, OpenGL 3.0[2] 1 5 10 16 w OpenGL nazywana half-float

Przesunięcie wykładnika[edytuj | edytuj kod]

Wykładnik będący liczbą całkowitą jest zapisywany w kodzie spolaryzowanym, co można interpretować jako wartość przesuniętą o pewną stałą (ang. biased exponent). Właściwą wartość wykładnika uzyskuje się odejmując od zakodowanego wykładnika wartość przesunięcia (ang. bias). Wartość liczby zmiennoprzecinkowej oblicza się ze wzoru:

x = (-1)^S \cdot M \cdot 2^{E - \textrm{bias}}

gdzie S to bit znaku; liczba jest ujemna, gdy bit znaku jest równy 1, w przeciwnej sytuacji ma on wartość 0.

Typowe wartości przesunięcia dla koprocesora x87 (występującego w procesorach x86) wynoszą:

  • 127 (7Fh) w formacie 32-bitowym,
  • 1023 (3FFh) w formacie 64-bitowym,
  • 16383 (3FFFh) w formacie 80-bitowym.

Wartości specjalne[edytuj | edytuj kod]

Oprócz zwykłych liczb zdefiniowano następujące wartości specjalne:

  • NaN – nie-liczba (ang. Not-a-Number), to symbol, który nie reprezentuje wartości liczbowej, powstały zazwyczaj w wyniku niedozwolonej operacji (np. pierwiastkowanie liczby ujemnej)
    • sNaN – sygnalizujące NaN (ang. signalling NaN) – rozróżnienie wprowadzone w procesorach z rodziny x86; w większości przypadków wykonanie działania liczba operacja sNaN spowoduje zgłoszenie wyjątku.
    • qNaN – ciche NaN (ang. quiet NaN) – przekazanie tej wartości jako argumentu operacji nie powoduje zgłoszenia wyjątku; w operacjach SSE można ustalić, że liczba operacja qNaN → 0.
  • Zero – rozróżnia się +0,0 i -0,0.
  • Nieskończoność – jest wynikiem operacji w przypadku wystąpienia nadmiaru (przepełnienia), przy dzieleniu przez 0, itp.; może być dodatnia lub ujemna.
  • Liczba nieznormalizowana – pojawia się, gdy występuje niedomiar (ang. underflow), ale wynik operacji jeszcze można zapisać denormalizując mantysę (w takim przypadku mantysa reprezentuje liczbę w postaci 0,xxx...xxx, a nie 1,xxx...xxxx).
Wartość specjalna Bit znaku Bity wykładnika Bity mantysy Uwagi
NaN x 111..111 xxxx...xxx wszystkie bity wykładnika są równe 1, natomiast mantysa ma niezerową wartość
qNaN x 111..111 1xxx...xxx uwagi jak dla NaN, ale pierwszy bit mantysy zawsze równy 1
sNaN x 111..111 0xxx...xxx uwagi jak dla NaN, ale pierwszy bit mantysy zawsze równy 0
±zero x 000..000 0000...000 wszystkie bity mantysy i wykładnika równe 0, bit znaku decyduje o znaku wartości
±nieskończoność x 111..111 0000...000 wszystkie bity mantysy równe 0, wszystkie bity wykładnika równe 1, bit znaku decyduje o znaku wartości
nieznormalizowana x 000..000 xxxx...xxx mantysa różna od 0, wszystkie bity wykładnika równe 0, bit znaku decyduje o znaku wartości

Typy zmiennoprzecinkowe w językach programowania[edytuj | edytuj kod]

C, C++[edytuj | edytuj kod]

Rozmiar typów zmiennoprzecinkowych zależy od konkretnych implementacji. Standardowo, typ float zajmuje co najmniej 4 bajty, double 8 bajtów, a long double zazwyczaj 8-16 bajtów. W przypadku kompilatora GCC w wersji 4.6.2, długości typów wynoszą odpowiednio 4, 8 i 16 bajtów, a w Visual C++ – 4, 8 i 8 bajtów.

Pascal[edytuj | edytuj kod]

W standardzie języka Pascal ISO/IEC 7185 :1990 jest wymagany typ real obejmujący podzbiór liczb rzeczywistych. Turbo Pascal wykorzystuje cztery typy zmiennoprzecinkowe (typ, liczba bajtów, liczba cyfr znaczących, zakres wartości):

  • single, 4 B, 7-8 cyfr, 1.5·10-45..3.4·1038
  • real, 6 B, 11-12 cyfr, 2.9·10-39..1.7·1038
  • double, 8 B, 15-16 cyfr, 5.0·10-324..1.7·10308
  • extended, 10 B, 19-20 cyfr, 3.4·10-4932..1.1·104932

Delphi obsługuje te same typy, przy czym w domyślnych ustawieniach opcji kompilatora typ real jest równoważny typowi double, a sześciobajtowy nazwano real48. We free Pascalu typ real jest zastępowany przez single lub double.

Fortran[edytuj | edytuj kod]

W oryginalnej specyfikacji języka Fortran typ real miał dwie możliwe długości REAL i DOUBLE PRECISION. Zakres i precyzja obydwu typów nie były wyspecyfikowane, lecz zależały od architektury konkretnego komputera, z wymaganiem aby DOUBLE PRECISION miał wyższą precyzję i co najmniej taki sam zakres co REAL.

Z czasem rynek został całkowicie zdominowany przez komputery o architekturze opartej na 8-bitowych bajtach, przyjęło się, że podstawowy typ REAL zajmuje 4 bajty. Współczesne kompilatory Fortranu dopuszczają deklaracje:

  • REAL*4 lub po prostu REAL – 32 bity, odpowiednik typu float w języku C
  • REAL*8 lub DOUBLE PRECISION – 64 bity, odpowiednik typu double w języku C

Niektóre implementacje dopuszczają także typ:

  • REAL*16 – 128 bitów.

Nowsze specyfikacje języka Fortran, poczynając od Fortran90, umożliwiają programiście deklarowanie wymaganej precyzji i zakresu liczb zmiennoprzecinkowych w oderwaniu od konkretnej implementacji. Wbudowana funkcja SELECTED_REAL_KIND(p,r) zwraca najkrótszą, dla konkretnego procesora, reprezentację o co najmniej wskazanej dokładności i zakresie. Na przykład SELECTED_REAL_KIND(10,80) zwróci dla danego procesora typ liczby zmiennoprzecinkowej o dokładności co najmniej 10 cyfr znaczących i zakresie co najmniej do 1080. Istnieją też wbudowane funkcje PRECISION i RANGE pozwalające sprawdzić jaki rzeczywisty zakres i dokładność mają liczby zmiennoprzecinkowe podanego typu w danym procesorze[3].

Kalkulator[edytuj | edytuj kod]

Sposoby wyświetlania liczb zmiennoprzecinkowych:

  • FLO (Floating Notation) – notacja dziesiętna – tryb domyślny. Jeżeli jest to możliwe wyświetla liczbę z wykładnikiem równym 0 pomijając jego wyświetlanie
  • SCE (Scientific Notation) – notacja naukowa – zawsze wyświetla liczbę z wykładnikiem
  • ENG (Engineering Notation) – notacja inżynierska – zawsze wyświetla liczbę z wykładnikiem podzielnym przez 3.

Historia[edytuj | edytuj kod]

Binarne liczby zmiennoprzecinkowe po raz pierwszy zastosował Konrad Zuse w mechanicznym komputerze Z1.

Zobacz też[edytuj | edytuj kod]

Przypisy

  1. Firma Borland w kompilatorach języka Pascal począwszy od wersji Delphi 3.0 przyjęła za standard liczb zmiennoprzecinkowych liczby typu double, a stary typ został nazwany real48 i jest obsługiwany w celu zachowania zgodności z poprzednimi wersjami, ale ma status przestarzałego elementu języka, który w pewnym momencie może przestać być obsługiwany.
  2. Specyfikacja OpenGL 3.0, wersja z 11.08.2008, sekcja 2.1.2 "16-Bit Floating Point Number"
  3. Michael Metcalf, John Ker Reid, Malcolm Cohen: Fortran 95/2003 explained. Oxford: Oxford University Press, 2004, s. 16. ISBN 978-0-19-852693-3.