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?
c++
c++11
floating-point
ieee-754
BЈовић
quelle
quelle
Antworten:
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.
quelle
isnomal
ist ,true
wenn die 8 Bits alle Null sind undfalse
sonst?001010
, und als interpretiert1.001010
.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:
Oder wenn Sie Bilder mögen:
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
-127
z.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.0
einer1
binä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:
Aber wie geht man dann damit um
0.0
? Nun, sie haben beschlossen, eine Ausnahme zu erstellen:0.0
damit stellen die bytes
00 00 00 00
auch dar0.0
, was gut aussieht.Wenn wir nur diese Regeln berücksichtigen würden, wäre die kleinste Zahl ungleich Null, die dargestellt werden kann:
was aufgrund der führenden Bitkonvention in einem Hex-Bruch ungefähr so aussieht:
1.000002 * 2 ^ (-127)
wo
.000002
ist 22 Nullen mit einem1
am Ende.Wir können nicht nehmen
fraction = 0
, sonst wäre diese Nummer0.0
.Aber dann dachten die Ingenieure, die auch einen ausgeprägten ästhetischen Sinn hatten: Ist das nicht hässlich? Dass wir von direkt
0.0
zu 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:
Diese Regel impliziert sofort, dass die Nummer so ist, dass:
ist immer noch
0.0
, was irgendwie elegant ist, da es eine Regel weniger bedeutet, den Überblick zu behalten.Also
0.0
ist eigentlich eine subnormale Zahl nach unserer Definition!Mit dieser neuen Regel lautet die kleinste nicht subnormale Zahl:
welches darstellt:
1.0 * 2 ^ (-126)
Dann ist die größte subnormale Zahl:
was gleich ist:
0.FFFFFE * 2 ^ (-126)
wo
.FFFFFE
ist 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:
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.0
genau!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:
*
).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
0
von[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:
[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)
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
float
fü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ückIn 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:
A1.5.2 "Gleitkomma-Standards und Terminologie" Tabelle A1-3 "Gleitkomma-Terminologie" bestätigt, dass Subnormen und Denormale Synonyme sind:
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:
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:
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:
quelle
Von http://blogs.oracle.com/d/entry/subnormal_numbers :
quelle