Arytmetyka zmiennoprzecinkowa

Witamy w kolejnej odsłonie Under The Hood . Ta kolumna ma na celu dać programistom Java wgląd w piękno ukryte pod ich uruchomionymi programami Java. Kolumna z tego miesiąca stanowi kontynuację rozpoczętej w zeszłym miesiącu dyskusji na temat zestawu instrukcji kodu bajtowego wirtualnej maszyny języka Java (JVM). W tym artykule przyjrzymy się arytmetyce zmiennoprzecinkowej w JVM i omówiono kody bajtowe, które wykonują operacje arytmetyczne zmiennoprzecinkowe. W kolejnych artykułach omówimy innych członków rodziny kodów bajtowych.

Główne punkty zmiennoprzecinkowe

Obsługa zmiennoprzecinkowych maszyn JVM jest zgodna ze standardem zmiennoprzecinkowym IEEE-754 1985. Ten standard definiuje format 32-bitowych i 64-bitowych liczb zmiennoprzecinkowych oraz definiuje operacje na tych liczbach. W JVM arytmetyka zmiennoprzecinkowa jest wykonywana na 32-bitowych liczbach zmiennoprzecinkowych i 64-bitowych podwójnych liczbach. Dla każdego kodu bajtowego, który wykonuje operacje arytmetyczne na liczbach zmiennoprzecinkowych, istnieje odpowiedni kod bajtowy, który wykonuje tę samą operację na liczbach podwójnych.

Liczba zmiennoprzecinkowa składa się z czterech części - znaku, mantysy, podstawy i wykładnika. Znak to 1 lub -1. Mantysa, zawsze liczba dodatnia, zawiera znaczące cyfry liczby zmiennoprzecinkowej. Wykładnik wskazuje dodatnią lub ujemną moc podstawy, przez którą należy pomnożyć mantysę i znak. Cztery składniki są łączone w następujący sposób, aby uzyskać wartość zmiennoprzecinkową:

znak * mantysa * wykładnik radix

Liczby zmiennoprzecinkowe mają wiele reprezentacji, ponieważ zawsze można pomnożyć mantysę dowolnej liczby zmiennoprzecinkowej przez jakąś potęgę podstawy i zmienić wykładnik, aby uzyskać pierwotną liczbę. Na przykład liczba -5 może być reprezentowana równorzędnie przez dowolną z następujących form w podstawie 10:

Formy -5
Znak Mantysa Wykładnik Radix
-1 50 10 -1
-1 5 10 0
-1 0.5 10 1
-1 0,05 10 2

Dla każdej liczby zmiennoprzecinkowej istnieje jedna reprezentacja, o której mówi się, że jest znormalizowana. Liczba zmiennoprzecinkowa jest normalizowana, jeśli jej mantysa mieści się w zakresie określonym następującą zależnością:

1 / radix <= mantysa <

Znormalizowana liczba zmiennoprzecinkowa o podstawie 10 ma swój punkt dziesiętny tuż po lewej stronie pierwszej niezerowej cyfry mantysy. Znormalizowana reprezentacja zmiennoprzecinkowa -5 to -1 * 0,5 * 10 1. Innymi słowy, mantysa znormalizowanej liczby zmiennoprzecinkowej nie ma niezerowych cyfr po lewej stronie przecinka dziesiętnego i niezerowej cyfry tylko do z prawej strony przecinka dziesiętnego. Każda liczba zmiennoprzecinkowa, która nie pasuje do tej kategorii, jest określana jako zdenormalizowana . Zwróć uwagę, że liczba zero nie ma znormalizowanej reprezentacji, ponieważ nie ma niezerowej cyfry, którą można umieścić po prawej stronie przecinka dziesiętnego. „Dlaczego należy się znormalizować?” to powszechny wykrzyknik wśród zer.

Liczby zmiennoprzecinkowe w JVM używają podstawy równej dwa. Dlatego liczby zmiennoprzecinkowe w JVM mają następującą postać:

znak * mantysa * 2 wykładnik

Mantysa liczby zmiennoprzecinkowej w JVM jest wyrażana jako liczba binarna. Znormalizowana mantysa ma swój punkt binarny (dwójkowy odpowiednik przecinka dziesiętnego) tuż po lewej stronie najbardziej znaczącej cyfry niezerowej. Ponieważ system liczb binarnych ma tylko dwie cyfry - zero i jedną - najbardziej znaczącą cyfrą znormalizowanej mantysy jest zawsze jeden.

Najbardziej znaczącym bitem typu float lub double jest bit znaku. Mantysa zajmuje 23 najmniej znaczące bity liczby zmiennoprzecinkowej i 52 najmniej znaczące bity liczby podwójnej. Wykładnik, 8 bitów liczby zmiennoprzecinkowej i 11 bitów liczby podwójnej, znajduje się między znakiem a mantysą. Format pływaka pokazano poniżej. Bit znaku jest wyświetlany jako „s”, bity wykładnika są wyświetlane jako „e”, a bity mantysy są wyświetlane jako „m”:

Układ bitowy pływaka Java
s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm

Bit znaku zero oznacza liczbę dodatnią, a bit znaku jeden oznacza liczbę ujemną. Mantysa jest zawsze interpretowana jako dodatnia liczba o podstawie dwa. Nie jest to liczba uzupełniająca się do dwóch. Jeśli bit znaku wynosi jeden, wartość zmiennoprzecinkowa jest ujemna, ale mantysa jest nadal interpretowana jako liczba dodatnia, którą należy pomnożyć przez -1.

Pole wykładnika jest interpretowane na jeden z trzech sposobów. Wykładnik wszystkich jedynek wskazuje, że liczba zmiennoprzecinkowa ma jedną ze specjalnych wartości plus lub minus nieskończoność lub „nie jest liczbą” (NaN). NaN jest wynikiem pewnych operacji, takich jak dzielenie zera przez zero. Wykładnik wszystkich zer wskazuje zdenormalizowaną liczbę zmiennoprzecinkową. Każdy inny wykładnik wskazuje znormalizowaną liczbę zmiennoprzecinkową.

Mantysa zawiera jeden dodatkowy element precyzji poza tymi, które pojawiają się w bitach mantysy. Mantysa pływaka, która zajmuje tylko 23 bity, ma 24 bity precyzji. Mantysa sobowtóra, która zajmuje 52 bity, ma 53 bity precyzji. Najbardziej znaczący bit mantysy jest przewidywalny i dlatego nie jest uwzględniany, ponieważ wykładnik liczb zmiennoprzecinkowych w JVM wskazuje, czy liczba jest znormalizowana, czy nie. Jeśli wykładnikiem są same zera, liczba zmiennoprzecinkowa jest zdenormalizowana i wiadomo, że najbardziej znaczący bit mantysy jest zerem. W przeciwnym razie liczba zmiennoprzecinkowa jest znormalizowana i wiadomo, że najbardziej znaczący bit mantysy jest jeden.

JVM nie zgłasza żadnych wyjątków w wyniku jakichkolwiek operacji zmiennoprzecinkowych. Wartości specjalne, takie jak dodatnia i ujemna nieskończoność lub NaN, są zwracane w wyniku podejrzanych operacji, takich jak dzielenie przez zero. Wykładnik wszystkich jedynek wskazuje specjalną wartość zmiennoprzecinkową. Wykładnik wszystkich jedynek z mantysą, której wszystkie bity są równe zero, wskazuje na nieskończoność. Znak nieskończoności jest wskazywany przez bit znaku. Wykładnik wszystkich jedynek z jakąkolwiek inną mantysą jest interpretowany jako „nie liczba” (NaN). JVM zawsze wytwarza tę samą mantysę dla NaN, która składa się z samych zer z wyjątkiem najbardziej znaczącego bitu mantysy, który pojawia się w liczbie. Poniższe wartości przedstawiają zmiennoprzecinkowe:

Specjalne wartości zmiennoprzecinkowe
Wartość Bity pływające (mantysa znak wykładnika)
+ Nieskończoność 0 11111111 00000000000000000000000
-Nieskończoność 1 11111111 00000000000000000000000
NaN 1 11111111 10000000000000000000000

Potęgi, które nie są ani same, ani same zera, wskazują potęgę dwóch, przez którą można pomnożyć znormalizowaną mantysę. Potęgę dwójki można określić, interpretując bity wykładnika jako liczbę dodatnią, a następnie odejmując odchylenie od liczby dodatniej. W przypadku liczby zmiennoprzecinkowej odchylenie wynosi 126. W przypadku wartości podwójnej odchylenie wynosi 1023. Na przykład pole wykładnika w zmiennej zmiennoprzecinkowej o wartości 00000001 daje potęgę dwóch przez odjęcie odchylenia (126) od pola wykładnika interpretowanego jako dodatnia liczba całkowita (1). Zatem potęga dwójki wynosi 1 - 126, czyli -125. Jest to najmniejsza możliwa potęga dwóch dla pływaka. Z drugiej strony, pole wykładnika 11111110 daje potęgę dwóch (254 - 126) lub 128. Liczba 128 jest największą potęgą dwóch dostępną dla liczby zmiennoprzecinkowej. W poniższej tabeli przedstawiono kilka przykładów znormalizowanych pływaków:

Znormalizowane wartości zmiennoprzecinkowe
Wartość Bity pływające (mantysa znak wykładnika) Bezstronny wykładnik
Największy dodatni (skończony) pływak 0 11111110 11111111111111111111111 128
Największy ujemny (skończony) float 1 11111110 11111111111111111111111 128
Najmniejszy znormalizowany pływak 1 00000001 00000000000000000000000 -125
Liczba Pi 0 10000000 10010010000111111011011 2

Wykładnik wszystkich zer wskazuje, że mantysa jest zdenormalizowana, co oznacza, że ​​nieokreślony bit wiodący to zero zamiast jedynki. Potęga dwóch w tym przypadku jest taka sama, jak najniższa potęga dwóch dostępnych dla znormalizowanej mantysy. Dla pływaka jest to -125. Oznacza to, że znormalizowane mantysy pomnożone przez dwa podniesione do potęgi -125 mają pole wykładnika równe 00000001, podczas gdy zdenormalizowane mantysy pomnożone przez dwa podniesione do potęgi -125 mają pole wykładnika równe 00000000. Dodatek dla denormalizowanych liczb na dole koniec zakresu wykładników obsługuje stopniowy niedomiar. Gdyby zamiast tego użyto najniższego wykładnika do przedstawienia znormalizowanej liczby, dla większych liczb wystąpiłby niedomiar do zera. Innymi słowy, pozostawienie najniższego wykładnika dla zdenormalizowanych liczb umożliwia przedstawienie mniejszych liczb.Mniejsze zdenormalizowane liczby mają mniej bitów precyzji niż liczby znormalizowane, ale jest to lepsze niż niedomiar do zera, gdy tylko wykładnik osiągnie swoją minimalną znormalizowaną wartość.

Zdenormalizowane wartości zmiennoprzecinkowe
Wartość Bity pływające (mantysa znak wykładnika)
Najmniejsza dodatnia (niezerowa) liczba zmiennoprzecinkowa 0 00000000 00000000000000000000001
Najmniejsza ujemna (niezerowa) liczba zmiennoprzecinkowa 1 00000000 00000000000000000000001
Największy zdenormalizowany pływak 1 00000000 11111111111111111111111
Zero dodatnie 0 00000000 00000000000000000000000
Ujemne zero 1 00000000 00000000000000000000000

Odsłonięty pływak

Java float ujawnia swoją wewnętrzną naturę Poniższy aplet umożliwia zabawę z formatem zmiennoprzecinkowym. Wartość liczby zmiennoprzecinkowej jest wyświetlana w kilku formatach. Format notacji naukowej podstawa dwa przedstawia mantysę i wykładnik o podstawie dziesięć. Przed wyświetleniem rzeczywista mantysa jest mnożona przez 2 24, co daje liczbę całkowitą, a nieobciążony wykładnik jest dekrementowany przez 24. Zarówno całkowa mantysa, jak i wykładnik są następnie łatwo przekształcane do dziesiętnej podstawy i wyświetlane.