Co to jest podnormalna liczba zmiennoprzecinkowa?

82

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?

BЈовић
źródło
2
Pierwszy wynik Google pokazuje, że to tylko synonim denormalnego: en.wikipedia.org/wiki/Denormal_number
tenfour
10
A jednak, teraz drugim trafieniem w Google (szukanie „subnormal zmiennoprzecinkowego”) jest samo pytanie.
Slipp D. Thompson,
Zobacz to pytanie, aby uzyskać dogłębną dyskusję na temat denormali i radzenia sobie z nimi: stackoverflow.com/questions/9314534/ ...
fig

Odpowiedzi:

79

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.

Kerrek SB
źródło
Mówisz isnomalto true, czy są wszystkie 8 bitów zera i falseinaczej?
Pacerier
„przechowywane” czy interpretowane?
Pacerier
@Pacerier: "zapisany": jest przechowywany bez wiodącej cyfry 1, np. As 001010i interpretowany jako 1.001010.
Kerrek SB
Czy jest oczywiste, co to jest wspomniane emin w: `` e <sub> min </sub>? `` (Mam nadzieję, że moja próba formatowania zadziała) ...
Razzle
85

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:

  • 1 bit: znak
  • 8 bitów: wykładnik
  • 23 bity: ułamek

Lub jeśli lubisz zdjęcia:

wprowadź opis obrazu tutaj

Ź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ą 1jedynkę 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ą”:

zawsze zakładaj, że liczba zaczyna się od jedynki

Ale jak sobie z tym radzić 0.0? Cóż, postanowili stworzyć wyjątek:

  • jeśli wykładnik wynosi 0
  • a ułamek to 0
  • to liczba oznacza plus lub minus 0.0

tak, że bajty 00 00 00 00ró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:

  • wykładnik: 0
  • frakcja: 1

który wygląda mniej więcej tak w ułamku szesnastkowym z powodu wiodącej konwencji bitów:

1.000002 * 2 ^ (-127)

gdzie .000002jest 22 zera i 1na końcu.

Nie możemy wziąć fraction = 0, inaczej byłaby ta liczba 0.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.0do 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łę:

Jeśli wykładnik wynosi 0, to:

  • wiodący bit staje się 0
  • wykładnik jest ustalony na -126 (nie -127, jakbyśmy nie mieli tego wyjątku)

Takie liczby nazywane są liczbami podnormalnymi (lub liczbami denormalnymi, które są synonimami).

Ta reguła natychmiast oznacza, że ​​liczba taka, że:

  • wykładnik: 0
  • frakcja: 0

jest nadal 0.0, co jest dość eleganckie, ponieważ oznacza jedną regułę mniej do śledzenia.

Tak więc 0.0jest to liczba podnormalna zgodnie z naszą definicją!

Dzięki tej nowej regule najmniejsza liczba nienormalna to:

  • wykładnik: 1 (0 byłoby podnormalne)
  • frakcja: 0

które reprezentuje:

1.0 * 2 ^ (-126)

Wtedy największa liczba anormalna to:

  • wykładnik: 0
  • ułamek: 0x7FFFFF (23 bity 1)

co jest równe:

0.FFFFFE * 2 ^ (-126)

gdzie .FFFFFEjest 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:

  • wykładnik: 0
  • frakcja: 1

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.0dokł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:

  • dla każdego wykładnika nie ma nakładania się między przedstawionymi liczbami
  • dla każdego wykładnika mamy tę samą liczbę 2 ^ 32 liczb (tutaj reprezentowaną przez 4 * )
  • w każdym wykładniku punkty są równo rozmieszczone
  • większe wykładniki obejmują większe zakresy, ale punkty są bardziej rozłożone

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:

    • zakres [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)
    • i tak dalej

    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 floatreprezentuje 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 T

W 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:

Wydajność przetwarzania zmiennoprzecinkowego można zmniejszyć podczas wykonywania obliczeń obejmujących zdenormalizowane liczby i wyjątki niedomiaru. W wielu algorytmach wydajność tę można odzyskać bez znaczącego wpływu na dokładność wyniku końcowego, zastępując zdenormalizowane operandy i wyniki pośrednie zerami. Aby umożliwić tę optymalizację, implementacje zmiennoprzecinkowe ARM pozwalają na użycie trybu zerowego dla różnych formatów zmiennoprzecinkowych w następujący sposób:

  • Dla AArch64:

    • Jeśli FPCR.FZ==1, to tryb Flush-to-Zero jest używany dla wszystkich wejść i wyjść Single-Precision i Double-Precision dla wszystkich instrukcji.

    • Jeśli FPCR.FZ16==1, to tryb równorzędny do zera jest używany do wszystkich wejść i wyjść instrukcji zmiennoprzecinkowych z połowiczną precyzją, innych niż: —Konwersje między liczbami o połowie dokładności i pojedynczej precyzji. —Konwersje między dokładnością połowiczną a podwójną dokładnością liczby.

A1.5.2 „Standardy zmiennoprzecinkowe i terminologia” Tabela A1-3 „Terminologia zmiennoprzecinkowa” potwierdza, że ​​wartości podnormalne i denormalne są synonimami:

This manual                 IEEE 754-2008
-------------------------   -------------
[...]
Denormal, or denormalized   Subnormal

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:

FPCR.IDE, bit [15] Włącz pułapkę wyjątków zmiennoprzecinkowych wejściowych Denormal. Możliwe wartości to:

  • 0b0 Wybrano nieopakowaną obsługę wyjątków. Jeśli wystąpi wyjątek zmiennoprzecinkowy, bit FPSR.IDC jest ustawiany na 1.

  • 0b1 Wybrano obsługę uwięzionych wyjątków. Jeśli wystąpi wyjątek zmiennoprzecinkowy, PE nie aktualizuje bitu FPSR.IDC. Oprogramowanie do obsługi pułapek może zdecydować, czy ustawić bit FPSR.IDC na 1.

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:

FPFtZ, bity [3: 0]

Tryb spłukiwania do zera. Wskazuje, czy implementacja zmiennoprzecinkowa zapewnia obsługę tylko dla trybu działania Flush-to-Zero. Zdefiniowane wartości to:

  • 0b0000 Nie zaimplementowano lub sprzęt obsługuje tylko tryb działania ze stanu zerowego.

  • 0b0001 Sprzęt obsługuje arytmetykę pełnych zdenormalizowanych liczb.

Wszystkie inne wartości są zastrzeżone.

W ARMv8-A dozwolone wartości to 0b0000 i 0b0001.

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.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
1
Odniesienie do „Podczas projektowania IEEE 754 ..”? Albo lepiej zacząć zdanie od „Rzekomo”
Pacerier
@Pacerier Nie sądzę, żeby to było złe :-) Jakie inne uzasadnienie mogłoby to mieć znaczenie? Prawdopodobnie było to znane wcześniej, ale myślę, że to w porządku.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Świetna odpowiedź. Przygotowuję się do poprowadzenia zajęć z analizy numerycznej na wiosnę i skieruję do tego moich uczniów (nasz tekst zawiera krótką dyskusję, ale pomija szczegóły). Jeśli chodzi o uzasadnienie niektórych decyzji, wydało mi się to pouczające: Wywiad ze Starym Człowiekiem z Floating-Point .
John Coleman
@JohnColeman dzięki za ten link! Dodano do odpowiedzi cytującej Cię. Byłoby niesamowite, gdyby ktoś mógł dodać, być może w innej odpowiedzi, najkrótszy możliwy znaczący przykład, w którym podnormalne wyniki obliczeń są lepsze (i może jeden sztuczny przykład, w którym wyniki się pogarszają)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
29

Z http://blogs.oracle.com/d/entry/subnormal_numbers :

Potencjalnie istnieje wiele sposobów przedstawienia tej samej liczby, używając jako przykładu liczby dziesiętnej, liczbę 0,1 można przedstawić jako 1 * 10 -1 lub 0,1 * 10 0 lub nawet 0,01 * 10. Standard nakazuje, aby liczby były zawsze przechowywane z pierwszy kawałek jako jeden. W systemie dziesiętnym, który odpowiada przykładowi 1 * 10-1.

Załóżmy teraz, że najniższy wykładnik, który można przedstawić, to -100. Zatem najmniejsza liczba, którą można przedstawić w postaci normalnej, to 1 * 10-100 . Jeśli jednak złagodzimy ograniczenie, że wiodący bit jest jedynką, możemy w rzeczywistości reprezentować mniejsze liczby w tej samej przestrzeni. Na przykładzie dziesiętnym moglibyśmy przedstawić 0,1 * 10-100 . Nazywa się to liczbą subnormalną. Celem posiadania liczb podnormalnych jest wyrównanie różnicy między najmniejszą liczbą normalną a zerem.

Bardzo ważne jest, aby zdać sobie sprawę, że liczby nienormalne są przedstawiane z mniejszą dokładnością niż liczby normalne. W rzeczywistości sprzedają mniejszą precyzję za mniejszy rozmiar. W związku z tym obliczenia wykorzystujące liczby podnormalne nie będą miały takiej samej precyzji, jak obliczenia na liczbach normalnych. Zatem aplikacja, która wykonuje znaczące obliczenia na liczbach podnormalnych, jest prawdopodobnie warta zbadania, czy przeskalowanie (tj. Pomnożenie liczb przez jakiś współczynnik skalowania) przyniosłoby mniej wartości podnormalnych i dokładniejsze wyniki.

allwyn.menezes
źródło