Strona odniesienia isnormal () mówi:
Określa, czy podana liczba zmiennoprzecinkowa arg jest normalna, tj. Nie jest równa zeru, podnormalna, nieskończona ani NaN.
Liczba równa zero, nieskończona lub NaN jest jasne, co to znaczy. Ale mówi też, że jest to nienormalne. Kiedy liczba jest niższa od normy?
c++
c++11
floating-point
ieee-754
BЈовић
źródło
źródło
Odpowiedzi:
W standardzie IEEE754 liczby zmiennoprzecinkowe są przedstawiane jako binarna notacja naukowa, x = M × 2 e . Tutaj M jest mantysa i e jest wykładnikiem . Matematycznie zawsze możesz tak dobrać wykładnik, że 1 ≤ M <2. * Jednakże, ponieważ w reprezentacji komputerowej wykładnik może mieć tylko skończony zakres, istnieją liczby większe od zera, ale mniejsze niż 1,0 × 2 e min . Te liczby są pod normalnymi lub denormalnymi .
Praktycznie mantysa jest przechowywana bez wiodącej 1, ponieważ zawsze występuje wiodąca 1, z wyjątkiem liczb podnormalnych (i zera). Dlatego interpretacja jest taka, że jeśli wykładnik jest inny niż minimalny, istnieje niejawne wiodące 1, a jeśli wykładnik jest minimalny, nie ma go, a liczba jest podnormalna.
*) Bardziej ogólnie, 1 ≤ M < B dla dowolnej notacji naukowej o podstawie B.
źródło
isnomal
totrue
, czy są wszystkie 8 bitów zera ifalse
inaczej?001010
i interpretowany jako1.001010
.Podstawy IEEE 754
Najpierw przyjrzyjmy się podstawom uporządkowania numerów IEEE 754.
Skoncentrujemy się na pojedynczej precyzji (32-bitowej), ale wszystko można natychmiast uogólnić na inne dokładności.
Format to:
Lub jeśli lubisz zdjęcia:
Źródło .
Znak jest prosty: 0 jest pozytywne, a 1 jest negatywne, koniec historii.
Wykładnik ma długość 8 bitów, a więc zawiera się w zakresie od 0 do 255.
Wykładnik nazywany jest obciążeniem, ponieważ ma przesunięcie
-127
, np .:0 == special case: zero or subnormal, explained below 1 == 2 ^ -126 ... 125 == 2 ^ -2 126 == 2 ^ -1 127 == 2 ^ 0 128 == 2 ^ 1 129 == 2 ^ 2 ... 254 == 2 ^ 127 255 == special case: infinity and NaN
Wiodąca konwencja bitów
(Poniżej przedstawiono fikcyjną, hipotetyczną narrację, nieopartą na żadnych faktycznych badaniach historycznych).
Podczas projektowania IEEE 754 inżynierowie zauważyli, że wszystkie liczby, z wyjątkiem
0.0
, mają1
jedynkę w systemie dwójkowym jako pierwszą cyfrę. Na przykład:25.0 == (binary) 11001 == 1.1001 * 2^4 0.625 == (binary) 0.101 == 1.01 * 2^-1
oba zaczynają się od tego irytującego
1.
części.Dlatego marnotrawstwem byłoby pozwolić, aby ta cyfra zajmowała jeden precyzyjny bit prawie każdej liczby.
Z tego powodu stworzyli „wiodącą konwencję bitową”:
Ale jak sobie z tym radzić
0.0
? Cóż, postanowili stworzyć wyjątek:0.0
tak, że bajty
00 00 00 00
również reprezentują0.0
, co wygląda dobrze.Gdybyśmy tylko rozważyli te reguły, najmniejsza niezerowa liczba, którą można przedstawić, to:
który wygląda mniej więcej tak w ułamku szesnastkowym z powodu wiodącej konwencji bitów:
1.000002 * 2 ^ (-127)
gdzie
.000002
jest 22 zera i1
na końcu.Nie możemy wziąć
fraction = 0
, inaczej byłaby ta liczba0.0
.Ale wtedy inżynierowie, którzy również mieli wyostrzony zmysł estetyczny, pomyśleli: czy to nie jest brzydkie? Że skaczemy od razu
0.0
do czegoś, co nie ma nawet właściwej potęgi 2? Czy nie moglibyśmy w jakiś sposób przedstawić nawet mniejszych liczb? (OK, to było trochę bardziej niepokojące niż „brzydkie”: w rzeczywistości ludzie otrzymywali złe wyniki w swoich obliczeniach, zobacz „Jak wartości podrzędne poprawiają obliczenia” poniżej).Liczby podnormalne
Inżynierowie podrapali się przez chwilę po głowach i jak zwykle wrócili z kolejnym dobrym pomysłem. Co jeśli utworzymy nową regułę:
Ta reguła natychmiast oznacza, że liczba taka, że:
jest nadal
0.0
, co jest dość eleganckie, ponieważ oznacza jedną regułę mniej do śledzenia.Tak więc
0.0
jest to liczba podnormalna zgodnie z naszą definicją!Dzięki tej nowej regule najmniejsza liczba nienormalna to:
które reprezentuje:
1.0 * 2 ^ (-126)
Wtedy największa liczba anormalna to:
co jest równe:
0.FFFFFE * 2 ^ (-126)
gdzie
.FFFFFE
jest ponownie 23 bity, jeden na prawo od kropki.Jest to bardzo blisko najmniejszej wartości nienormalnej, co brzmi rozsądnie.
Najmniejsza niezerowa liczba podnormalna to:
co jest równe:
0.000002 * 2 ^ (-126)
który również wygląda bardzo blisko
0.0
!Nie mogąc znaleźć żadnego rozsądnego sposobu na przedstawienie liczb mniejszych od tego, inżynierowie byli szczęśliwi i wrócili do oglądania zdjęć kotów w Internecie lub czegokolwiek innego, co robili w latach 70.
Jak widać, liczby podnormalne stanowią kompromis między dokładnością a długością reprezentacji.
Jako najbardziej ekstremalny przykład, najmniejsza niezerowa wartość podnormalna:
0.000002 * 2 ^ (-126)
ma zasadniczo precyzję pojedynczego bitu zamiast 32 bitów. Na przykład, jeśli podzielimy to przez dwa:
0.000002 * 2 ^ (-126) / 2
faktycznie osiągamy
0.0
dokładnie!Wyobrażanie sobie
Zawsze dobrze jest mieć geometryczną intuicję dotyczącą tego, czego się uczymy, więc proszę bardzo.
Jeśli narysujemy liczby zmiennoprzecinkowe IEEE 754 w linii dla każdego podanego wykładnika, wygląda to mniej więcej tak:
+---+-------+---------------+-------------------------------+ exponent |126| 127 | 128 | 129 | +---+-------+---------------+-------------------------------+ | | | | | v v v v v ------------------------------------------------------------- floats ***** * * * * * * * * * * * * ------------------------------------------------------------- ^ ^ ^ ^ ^ | | | | | 0.5 1.0 2.0 4.0 8.0
Z tego widać, że:
*
)Teraz sprowadzimy to do wykładnika 0.
Bez wartości podnormalnych hipotetycznie wyglądałoby to tak:
+---+---+-------+---------------+-------------------------------+ exponent | ? | 0 | 1 | 2 | 3 | +---+---+-------+---------------+-------------------------------+ | | | | | | v v v v v v ----------------------------------------------------------------- floats * **** * * * * * * * * * * * * ----------------------------------------------------------------- ^ ^ ^ ^ ^ ^ | | | | | | 0 | 2^-126 2^-125 2^-124 2^-123 | 2^-127
W przypadku podnormalnych wygląda to tak:
+-------+-------+---------------+-------------------------------+ exponent | 0 | 1 | 2 | 3 | +-------+-------+---------------+-------------------------------+ | | | | | v v v v v ----------------------------------------------------------------- floats * * * * * * * * * * * * * * * * * ----------------------------------------------------------------- ^ ^ ^ ^ ^ ^ | | | | | | 0 | 2^-126 2^-125 2^-124 2^-123 | 2^-127
Porównując te dwa wykresy, widzimy, że:
podnormalne podwajają długość zakresu wykładnika
0
, od[2^-127, 2^-126)
do[0, 2^-126)
Przestrzeń między elementami zmiennoprzecinkowymi w zakresie poniżej normalnego jest taka sama jak dla
[0, 2^-126)
.zakres
[2^-127, 2^-126)
ma połowę liczby punktów, które miałby bez wartości podrzędnych.Połowa z tych punktów jest przeznaczona na drugą połowę zakresu.
zakres
[0, 2^-127)
ma kilka punktów z wartościami podrzędnymi, ale żadnych bez.Ten brak punktów
[0, 2^-127)
nie jest zbyt elegancki i jest głównym powodem istnienia subnormalności!ponieważ punkty są równomiernie rozmieszczone:
[2^-128, 2^-127)
ma połowę punktów niż[2^-127, 2^-126)
-[2^-129, 2^-128)
ma połowę punktów niż[2^-128, 2^-127)
To właśnie mamy na myśli, mówiąc, że wartości podnormalne są kompromisem między rozmiarem a precyzją.
Przykład Runnable C.
Zagrajmy teraz z prawdziwym kodem, aby zweryfikować naszą teorię.
W prawie wszystkich obecnych i stacjonarnych komputerach, C
float
reprezentuje liczby zmiennoprzecinkowe pojedynczej precyzji IEEE 754.Dotyczy to w szczególności mojego laptopa Ubuntu 18.04 amd64 Lenovo P51.
Przy takim założeniu wszystkie stwierdzenia przekazują następujący program:
subnormal.c
#if __STDC_VERSION__ < 201112L #error C11 required #endif #ifndef __STDC_IEC_559__ #error IEEE 754 not implemented #endif #include <assert.h> #include <float.h> /* FLT_HAS_SUBNORM */ #include <inttypes.h> #include <math.h> /* isnormal */ #include <stdlib.h> #include <stdio.h> #if FLT_HAS_SUBNORM != 1 #error float does not have subnormal numbers #endif typedef struct { uint32_t sign, exponent, fraction; } Float32; Float32 float32_from_float(float f) { uint32_t bytes; Float32 float32; bytes = *(uint32_t*)&f; float32.fraction = bytes & 0x007FFFFF; bytes >>= 23; float32.exponent = bytes & 0x000000FF; bytes >>= 8; float32.sign = bytes & 0x000000001; bytes >>= 1; return float32; } float float_from_bytes( uint32_t sign, uint32_t exponent, uint32_t fraction ) { uint32_t bytes; bytes = 0; bytes |= sign; bytes <<= 8; bytes |= exponent; bytes <<= 23; bytes |= fraction; return *(float*)&bytes; } int float32_equal( float f, uint32_t sign, uint32_t exponent, uint32_t fraction ) { Float32 float32; float32 = float32_from_float(f); return (float32.sign == sign) && (float32.exponent == exponent) && (float32.fraction == fraction) ; } void float32_print(float f) { Float32 float32 = float32_from_float(f); printf( "%" PRIu32 " %" PRIu32 " %" PRIu32 "\n", float32.sign, float32.exponent, float32.fraction ); } int main(void) { /* Basic examples. */ assert(float32_equal(0.5f, 0, 126, 0)); assert(float32_equal(1.0f, 0, 127, 0)); assert(float32_equal(2.0f, 0, 128, 0)); assert(isnormal(0.5f)); assert(isnormal(1.0f)); assert(isnormal(2.0f)); /* Quick review of C hex floating point literals. */ assert(0.5f == 0x1.0p-1f); assert(1.0f == 0x1.0p0f); assert(2.0f == 0x1.0p1f); /* Sign bit. */ assert(float32_equal(-0.5f, 1, 126, 0)); assert(float32_equal(-1.0f, 1, 127, 0)); assert(float32_equal(-2.0f, 1, 128, 0)); assert(isnormal(-0.5f)); assert(isnormal(-1.0f)); assert(isnormal(-2.0f)); /* The special case of 0.0 and -0.0. */ assert(float32_equal( 0.0f, 0, 0, 0)); assert(float32_equal(-0.0f, 1, 0, 0)); assert(!isnormal( 0.0f)); assert(!isnormal(-0.0f)); assert(0.0f == -0.0f); /* ANSI C defines FLT_MIN as the smallest non-subnormal number. */ assert(FLT_MIN == 0x1.0p-126f); assert(float32_equal(FLT_MIN, 0, 1, 0)); assert(isnormal(FLT_MIN)); /* The largest subnormal number. */ float largest_subnormal = float_from_bytes(0, 0, 0x7FFFFF); assert(largest_subnormal == 0x0.FFFFFEp-126f); assert(largest_subnormal < FLT_MIN); assert(!isnormal(largest_subnormal)); /* The smallest non-zero subnormal number. */ float smallest_subnormal = float_from_bytes(0, 0, 1); assert(smallest_subnormal == 0x0.000002p-126f); assert(0.0f < smallest_subnormal); assert(!isnormal(smallest_subnormal)); return EXIT_SUCCESS; }
GitHub upstream .
Skompiluj i uruchom z:
gcc -ggdb3 -O0 -std=c11 -Wall -Wextra -Wpedantic -Werror -o subnormal.out subnormal.c ./subnormal.out
C ++
Oprócz ujawniania wszystkich interfejsów API języka C, C ++ udostępnia również dodatkowe funkcje związane z podnormalizacją, które nie są tak łatwo dostępne w C w
<limits>
, np .:denorm_min
: Zwraca minimalną dodatnią wartość podnormalną typu TW C ++ całe API jest oparte na szablonach dla każdego typu zmiennoprzecinkowego i jest znacznie ładniejsze.
Wdrożenia
x86_64 i ARMv8 implementują IEEE 754 bezpośrednio na sprzęcie, na który tłumaczy się kod C.
W niektórych implementacjach elementy podnormalne wydają się działać wolniej niż normalne: Dlaczego zmiana z 0,1f na 0 spowalnia wydajność 10-krotnie? Jest to wspomniane w podręczniku ARM, zobacz sekcję „Szczegóły ARMv8” w tej odpowiedzi.
Szczegóły ARMv8
Podręcznik architektury ARM ARMv8 DDI 0487C.a instrukcja A1.5.4 „Zrównanie do zera” opisuje konfigurowalny tryb, w którym wartości podrzędne są zaokrąglane do zera w celu poprawy wydajności:
A1.5.2 „Standardy zmiennoprzecinkowe i terminologia” Tabela A1-3 „Terminologia zmiennoprzecinkowa” potwierdza, że wartości podnormalne i denormalne są synonimami:
C5.2.7 „FPCR, zmiennoprzecinkowy rejestr sterujący” opisuje, w jaki sposób ARMv8 może opcjonalnie zgłaszać wyjątki lub ustawiać bity flagi, gdy dane wejściowe operacji zmiennoprzecinkowej są nieprawidłowe:
D12.2.88 "MVFR1_EL1, AArch32 Media and VFP Feature Register 1" pokazuje, że obsługa denormalna jest w rzeczywistości całkowicie opcjonalna i oferuje trochę do wykrycia, czy jest obsługiwana:
Sugeruje to, że jeśli podnormalne nie są zaimplementowane, implementacje po prostu powracają do zerowania.
Nieskończoność i NaN
Ciekawy? Napisałem kilka rzeczy na:
Jak wartości podnormalne poprawiają obliczenia
DO ZROBIENIA: dokładniejsze zrozumienie, w jaki sposób ten skok pogarsza wyniki obliczeń / jak wartości podnormalne poprawiają wyniki obliczeń.
Aktualna historia
The Interview with the Old Man of Floating-Point autorstwa Charlesa Severance'a (1998) to krótki przegląd historii świata w formie wywiadu z Williamem Kahanem, zasugerowany przez Johna Colemana w komentarzach.
źródło
Z http://blogs.oracle.com/d/entry/subnormal_numbers :
źródło