Was ist eine subnormale Gleitkommazahl?

82

Die Referenzseite isnormal () sagt:

Bestimmt, ob die angegebene Gleitkommazahl arg normal ist, dh weder Null, subnormal, unendlich noch NaN ist.

Eine Zahl, die Null, unendlich oder NaN ist, ist klar, was es bedeutet. Es heißt aber auch subnormal. Wann ist eine Zahl nicht normal?

BЈовић
quelle
2
Das erste Google-Ergebnis zeigt, dass es nur ein Synonym für ein Denormal ist: en.wikipedia.org/wiki/Denormal_number
Tenfour
9
Und doch ist diese Frage selbst der zweite Treffer bei Google (Suche nach „subnormalem Gleitkomma“) .
Slipp D. Thompson
In dieser Frage finden Sie eine eingehende Diskussion über Denormale und deren Behandlung: stackoverflow.com/questions/9314534/…
Abb.

Antworten:

77

Im IEEE754-Standard werden Gleitkommazahlen als binäre wissenschaftliche Notation dargestellt, x  =  M  × 2 e . Hier ist M die Mantisse und e der Exponent . Mathematisch können Sie den Exponenten immer so wählen, dass 1 ≤  M  <2. * Da der Exponent in der Computerdarstellung jedoch nur einen endlichen Bereich haben kann, gibt es einige Zahlen, die größer als Null, aber kleiner als 1,0 × 2 e sind min . Diese Zahlen sind die Subnormen oder Denormalen .

Praktisch wird die Mantisse ohne die führende 1 gespeichert, da es immer eine führende 1 gibt, mit Ausnahme von subnormalen Zahlen (und Null). Die Interpretation lautet also: Wenn der Exponent nicht minimal ist, gibt es eine implizite führende 1, und wenn der Exponent minimal ist, gibt es keine und die Zahl ist subnormal.

*) Im Allgemeinen ist 1 ≤  M  <  B   für jede wissenschaftliche Notation der Basis B.

Kerrek SB
quelle
Wollen Sie damit sagen isnomalist , truewenn die 8 Bits alle Null sind und falsesonst?
Pacerier
'gespeichert' oder interpretiert?
Pacerier
@Pacerier: "gespeichert": Es wird ohne die führende 1 gespeichert, z. B. als 001010, und als interpretiert1.001010 .
Kerrek SB
Ist es offensichtlich, in welchem ​​Emin erwähnt wird: `` `e <sub> min </ sub>? `` `(Ich hoffe mein Formatierungsversuch funktioniert) ..
Razzle
80

IEEE 754-Grundlagen

Lassen Sie uns zunächst die Grundlagen der Organisation von IEEE 754-Nummern überprüfen.

Wir werden uns auf die einfache Genauigkeit (32-Bit) konzentrieren, aber alles kann sofort auf andere Genauigkeiten verallgemeinert werden.

Das Format ist:

  • 1 Bit: Vorzeichen
  • 8 Bits: Exponent
  • 23 Bit: Bruch

Oder wenn Sie Bilder mögen:

Geben Sie hier die Bildbeschreibung ein

Quelle .

Das Vorzeichen ist einfach: 0 ist positiv und 1 ist negativ, Ende der Geschichte.

Der Exponent ist 8 Bit lang und reicht daher von 0 bis 255.

Der Exponent wird als vorgespannt bezeichnet, weil er einen Versatz von -127z.

  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

Die führende Bitkonvention

Beim Entwurf von IEEE 754 stellten die Ingenieure fest, dass alle Zahlen außer 0.0einer 1binären Eins als erste Ziffer haben. Z.B:

25.0   == (binary) 11001 == 1.1001 * 2^4
 0.625 == (binary) 0.101 == 1.01   * 2^-1

beide beginnen mit diesem nervigen 1.Teil.

Daher wäre es verschwenderisch, diese Ziffer fast jede einzelne Zahl mit einem Präzisionsbit belegen zu lassen.

Aus diesem Grund haben sie die "Leitbitkonvention" erstellt:

Nehmen Sie immer an, dass die Nummer mit eins beginnt

Aber wie geht man dann damit um 0.0? Nun, sie haben beschlossen, eine Ausnahme zu erstellen:

  • wenn der Exponent 0 ist
  • und der Bruch ist 0
  • dann steht die Zahl für Plus oder Minus 0.0

damit stellen die bytes 00 00 00 00auch dar 0.0, was gut aussieht.

Wenn wir nur diese Regeln berücksichtigen würden, wäre die kleinste Zahl ungleich Null, die dargestellt werden kann:

  • Exponent: 0
  • Fraktion: 1

was aufgrund der führenden Bitkonvention in einem Hex-Bruch ungefähr so ​​aussieht:

1.000002 * 2 ^ (-127)

wo .000002ist 22 Nullen mit einem 1am Ende.

Wir können nicht nehmen fraction = 0, sonst wäre diese Nummer 0.0.

Aber dann dachten die Ingenieure, die auch einen ausgeprägten ästhetischen Sinn hatten: Ist das nicht hässlich? Dass wir von direkt 0.0zu etwas springen , das nicht einmal eine richtige Potenz von 2 ist? Könnten wir nicht irgendwie noch kleinere Zahlen darstellen?

Subnormale Zahlen

Die Ingenieure kratzten sich eine Weile am Kopf und kamen wie üblich mit einer weiteren guten Idee zurück. Was ist, wenn wir eine neue Regel erstellen:

Wenn der Exponent 0 ist, dann:

  • Das führende Bit wird 0
  • Der Exponent ist auf -126 festgelegt (nicht -127, als ob wir diese Ausnahme nicht hätten).

Solche Zahlen werden als subnormale Zahlen (oder Denormalzahlen, was synonym ist) bezeichnet.

Diese Regel impliziert sofort, dass die Nummer so ist, dass:

  • Exponent: 0
  • Bruchteil: 0

ist immer noch 0.0, was irgendwie elegant ist, da es eine Regel weniger bedeutet, den Überblick zu behalten.

Also 0.0ist eigentlich eine subnormale Zahl nach unserer Definition!

Mit dieser neuen Regel lautet die kleinste nicht subnormale Zahl:

  • Exponent: 1 (0 wäre subnormal)
  • Bruchteil: 0

welches darstellt:

1.0 * 2 ^ (-126)

Dann ist die größte subnormale Zahl:

  • Exponent: 0
  • Bruch: 0x7FFFFF (23 Bit 1)

was gleich ist:

0.FFFFFE * 2 ^ (-126)

wo .FFFFFEist wieder 23 Bits eins rechts vom Punkt.

Dies ist ziemlich nahe an der kleinsten nicht-subnormalen Zahl, was vernünftig klingt.

Und die kleinste subnormale Zahl ungleich Null ist:

  • Exponent: 0
  • Fraktion: 1

was gleich ist:

0.000002 * 2 ^ (-126)

das sieht auch ziemlich nah aus 0.0!

Die Ingenieure waren nicht in der Lage, einen vernünftigen Weg zu finden, um kleinere Zahlen darzustellen. Sie waren glücklich und schauten sich wieder Katzenbilder online an oder was auch immer sie in den 70er Jahren taten.

Wie Sie sehen können, machen subnormale Zahlen einen Kompromiss zwischen Genauigkeit und Darstellungslänge.

Als extremstes Beispiel das kleinste Subnormal ungleich Null:

0.000002 * 2 ^ (-126)

hat im Wesentlichen eine Genauigkeit von einem einzelnen Bit anstelle von 32 Bit. Wenn wir es zum Beispiel durch zwei teilen:

0.000002 * 2 ^ (-126) / 2

wir erreichen tatsächlich 0.0genau!

Visualisierung

Es ist immer eine gute Idee, eine geometrische Intuition über das zu haben, was wir lernen.

Wenn wir für jeden Exponenten IEEE 754-Gleitkommazahlen auf einer Linie darstellen, sieht dies ungefähr so ​​aus:

          +---+-------+---------------+-------------------------------+
exponent  |126|  127  |      128      |              129              |
          +---+-------+---------------+-------------------------------+
          |   |       |               |                               |
          v   v       v               v                               v
          -------------------------------------------------------------
floats    ***** * * * *   *   *   *   *       *       *       *       *
          -------------------------------------------------------------
          ^   ^       ^               ^                               ^
          |   |       |               |                               |
          0.5 1.0     2.0             4.0                             8.0

Daraus können wir das ersehen:

  • Für jeden Exponenten gibt es keine Überlappung zwischen den dargestellten Zahlen
  • Für jeden Exponenten haben wir die gleiche Zahl 2 ^ 32 von Zahlen (hier dargestellt durch 4 *).
  • Innerhalb jedes Exponenten sind die Punkte gleich beabstandet
  • Größere Exponenten decken größere Bereiche ab, wobei die Punkte jedoch weiter verteilt sind

Lassen Sie uns das jetzt bis zum Exponenten 0 reduzieren.

Ohne Subnormen würde es hypothetisch so aussehen:

          +---+---+-------+---------------+-------------------------------+
exponent  | ? | 0 |   1   |       2       |               3               |
          +---+---+-------+---------------+-------------------------------+
          |   |   |       |               |                               |
          v   v   v       v               v                               v
          -----------------------------------------------------------------
floats    *    **** * * * *   *   *   *   *       *       *       *       *
          -----------------------------------------------------------------
          ^   ^   ^       ^               ^                               ^
          |   |   |       |               |                               |
          0   |   2^-126  2^-125          2^-124                          2^-123
              |
              2^-127

Bei Subnormen sieht es so aus:

          +-------+-------+---------------+-------------------------------+
exponent  |   0   |   1   |       2       |               3               |
          +-------+-------+---------------+-------------------------------+
          |       |       |               |                               |
          v       v       v               v                               v
          -----------------------------------------------------------------
floats    * * * * * * * * *   *   *   *   *       *       *       *       *
          -----------------------------------------------------------------
          ^   ^   ^       ^               ^                               ^
          |   |   |       |               |                               |
          0   |   2^-126  2^-125          2^-124                          2^-123
              |
              2^-127

Beim Vergleich der beiden Diagramme sehen wir Folgendes:

  • Subnormen verdoppeln die Länge des Exponentenbereichs 0von [2^-127, 2^-126)bis[0, 2^-126)

    Der Abstand zwischen den Floats im subnormalen Bereich ist der gleiche wie für [0, 2^-126).

  • Der Bereich [2^-127, 2^-126)hat die Hälfte der Punkte, die er ohne Subnormen hätte.

    Die Hälfte dieser Punkte füllt die andere Hälfte des Bereichs.

  • Der Bereich [0, 2^-127)hat einige Punkte mit Subnormen, aber keine ohne.

    Dieser Mangel an Punkten [0, 2^-127)ist nicht sehr elegant und der Hauptgrund für die Existenz von Subnormen!

  • da die Punkte gleich beabstandet sind:

    • Der Bereich [2^-128, 2^-127)hat die Hälfte der Punkte als [2^-127, 2^-126) - [2^-129, 2^-128)hat die Hälfte der Punkte als[2^-128, 2^-127)
    • und so weiter

    Dies ist es, was wir meinen, wenn wir sagen, dass Subnormen ein Kompromiss zwischen Größe und Präzision sind.

Runnable C Beispiel

Lassen Sie uns nun mit einem tatsächlichen Code spielen, um unsere Theorie zu verifizieren.

In fast allen aktuellen und Desktop-Computern steht C floatfür IEEE 754-Gleitkommazahlen mit einfacher Genauigkeit.

Dies gilt insbesondere für meinen Ubuntu 18.04 amd64 Lenovo P51 Laptop.

Mit dieser Annahme geben alle Behauptungen das folgende Programm weiter:

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 stromaufwärts .

Kompilieren und ausführen mit:

gcc -ggdb3 -O0 -std=c11 -Wall -Wextra -Wpedantic -Werror -o subnormal.out subnormal.c
./subnormal.out

C ++

C ++ stellt nicht nur alle APIs von C zur Verfügung, sondern stellt auch einige zusätzliche subnormale Funktionen zur Verfügung, die in C nicht so leicht verfügbar sind <limits>, z.

  • denorm_min: Gibt den minimalen positiven Subnormalwert vom Typ T zurück

In C ++ wird die gesamte API für jeden Gleitkommatyp als Vorlage verwendet und ist viel besser.

Implementierungen

x86_64 und ARMv8 implementieren IEEE 754 direkt auf der Hardware, in die der C-Code übersetzt wird.

Subnormen scheinen in bestimmten Implementierungen weniger schnell als normal zu sein : Warum verlangsamt das Ändern von 0,1f auf 0 die Leistung um das 10-fache? Dies wird im ARM-Handbuch erwähnt, siehe Abschnitt "ARMv8-Details" dieser Antwort.

ARMv8-Details

ARM-Architektur Referenzhandbuch ARMv8 DDI 0487C.a Handbuch A1.5.4 "Auf Null spülen" beschreibt einen konfigurierbaren Modus, in dem Subnormen zur Verbesserung der Leistung auf Null gerundet werden:

Die Leistung der Gleitkommaverarbeitung kann reduziert werden, wenn Berechnungen mit denormalisierten Zahlen und Unterlaufausnahmen durchgeführt werden. In vielen Algorithmen kann diese Leistung wiederhergestellt werden, ohne die Genauigkeit des Endergebnisses wesentlich zu beeinträchtigen, indem die denormalisierten Operanden und Zwischenergebnisse durch Nullen ersetzt werden. Um diese Optimierung zu ermöglichen, ermöglichen ARM-Gleitkommaimplementierungen die Verwendung eines Flush-to-Zero-Modus für verschiedene Gleitkommaformate wie folgt:

  • Für AArch64:

    • Wenn dies der FPCR.FZ==1Fall ist, wird der Flush-to-Zero-Modus für alle Ein- und Ausgänge aller Anweisungen mit einfacher und doppelter Präzision verwendet.

    • Wenn dies der FPCR.FZ16==1Fall ist, wird der Flush-to-Zero-Modus für alle Ein- und Ausgänge von Gleitkommaanweisungen mit halber Präzision verwendet, außer: - Konvertierungen zwischen Halbpräzisions- und Einfachpräzisionszahlen. - Konvertierungen zwischen Halbpräzision und Doppelpräzision Zahlen.

A1.5.2 "Gleitkomma-Standards und Terminologie" Tabelle A1-3 "Gleitkomma-Terminologie" bestätigt, dass Subnormen und Denormale Synonyme sind:

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

C5.2.7 "FPCR, Gleitkomma-Steuerregister" beschreibt, wie ARMv8 optional Ausnahmen auslösen oder Flag-Bits setzen kann, wenn die Eingabe einer Gleitkommaoperation nicht normal ist:

FPCR.IDE, Bit [15] Denormal-Gleitkomma-Ausnahmefalle aktivieren. Mögliche Werte sind:

  • 0b0 Nicht eingeschlossene Ausnahmebehandlung ausgewählt. Wenn die Gleitkomma-Ausnahme auftritt, wird das FPSR.IDC-Bit auf 1 gesetzt.

  • 0b1 Trapped Exception Handling ausgewählt. Wenn die Gleitkomma-Ausnahme auftritt, aktualisiert das PE das FPSR.IDC-Bit nicht. Die Trap-Handling-Software kann entscheiden, ob das FPSR.IDC-Bit auf 1 gesetzt werden soll.

D12.2.88 "MVFR1_EL1-, AArch32-Medien- und VFP-Funktionsregister 1" zeigt, dass die denormale Unterstützung tatsächlich völlig optional ist, und bietet eine Möglichkeit, festzustellen, ob Unterstützung vorhanden ist:

FPFtZ, Bits [3: 0]

In den Nullmodus spülen. Gibt an, ob die Gleitkommaimplementierung nur den Betriebsmodus "Auf Null spülen" unterstützt. Definierte Werte sind:

  • 0b0000 Nicht implementiert oder Hardware unterstützt nur den Flush-to-Zero-Betriebsmodus.

  • 0b0001 Hardware unterstützt eine vollständig denormalisierte Zahlenarithmetik.

Alle anderen Werte sind reserviert.

In ARMv8-A sind die zulässigen Werte 0b0000 und 0b0001.

Dies deutet darauf hin, dass Implementierungen, wenn keine Subnormen implementiert sind, einfach auf Null zurückgesetzt werden.

Unendlichkeit und NaN

Neugierig? Ich habe einige Dinge geschrieben unter:

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
1
Zitat für 'Beim Entwerfen von IEEE 754 ..'? Oder besser, um den Satz mit "Angeblich" zu beginnen
Pacerier
@ Pacerier Ich glaube nicht, dass diese Tatsache falsch sein kann :-) Welche andere Begründung könnte es dafür geben? Wahrscheinlich war das schon vorher bekannt, aber das ist OK, denke ich.
Ciro Santilli 28 冠状 病 六四 事件 28
29

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

Es gibt möglicherweise mehrere Möglichkeiten, dieselbe Zahl am Beispiel einer Dezimalzahl darzustellen. Die Zahl 0,1 könnte als 1 * 10 -1 oder 0,1 * 10 0 oder sogar 0,01 * 10 dargestellt werden. Der Standard schreibt vor, dass die Zahlen immer mit gespeichert werden das erste bisschen als eins. In Dezimalzahl entspricht dies dem Beispiel 1 * 10-1.

Angenommen, der niedrigste Exponent, der dargestellt werden kann, ist -100. Die kleinste Zahl, die in normaler Form dargestellt werden kann, ist also 1 * 10 -100 . Wenn wir jedoch die Einschränkung lockern, dass das führende Bit eine Eins ist, können wir tatsächlich kleinere Zahlen im selben Raum darstellen. Anhand eines Dezimalbeispiels könnten wir 0,1 * 10 -100 darstellen . Dies wird als subnormale Zahl bezeichnet. Der Zweck von subnormalen Zahlen besteht darin, die Lücke zwischen der kleinsten normalen Zahl und Null zu glätten.

Es ist sehr wichtig zu wissen, dass subnormale Zahlen weniger genau dargestellt werden als normale Zahlen. Tatsächlich handeln sie mit reduzierter Präzision gegen ihre kleinere Größe. Daher haben Berechnungen, die subnormale Zahlen verwenden, nicht die gleiche Genauigkeit wie Berechnungen für normale Zahlen. Eine Anwendung, die signifikante Berechnungen für subnormale Zahlen durchführt, ist wahrscheinlich eine Untersuchung wert, um festzustellen, ob eine Neuskalierung (dh Multiplikation der Zahlen mit einem Skalierungsfaktor) weniger Subnormen und genauere Ergebnisse ergeben würde.

allwyn.menezes
quelle