← SEARCHABLE ARCHIVE
Computer Science · 2026-08-08

Wie speichert IEEE 754 reelle Zahlen?

Binary32, binary64, Rundung, NaN und Summenfehler verstehen

INTERACTIVE READING MODE →

Wie speichert IEEE 754 reelle Zahlen?

Dieser Beitrag ist als Lehrmaterial gedacht. Hardware, Compileroptionen und Sprachimplementierungen können Details verändern. Wichtiges Verhalten sollte deshalb in der eigenen Umgebung geprüft werden.

Viele Einsteiger wundern sich, dass 0.1 + 0.2 nicht exakt 0.3 ergibt. Der Rechner hat das Addieren nicht verlernt. Er muss unendlich viele reelle Zahlen mit endlich vielen Bitmustern darstellen, weshalb die meisten Werte durch einen benachbarten Wert angenähert werden.

IEEE 754 standardisiert nicht nur die Bitaufteilung, sondern auch Operationen, Rundung, Unendlichkeit, NaN, schrittweisen Unterlauf, Umwandlungen und Ausnahmezustände. Dieser Artikel zerlegt binary32 und binary64 und leitet praktische Regeln für Vergleiche, Summen, Geldbeträge und Serialisierung ab.

Festkomma und Gleitkomma

Bei Festkomma ist die Position des Kommas vorgegeben. Bedeutet die Ganzzahl 12345 immer zwei Dezimalstellen, ist ihr Wert 123.45. Das eignet sich für feste Skalen, deckt aber sehr kleine und sehr große Werte nicht gleichzeitig gut ab.

Gleitkomma ähnelt der wissenschaftlichen Schreibweise.

6.25 = 1.5625 × 2²
0.15625 = 1.25 × 2⁻³

Signifikand und Exponent werden getrennt gespeichert. Der Wertebereich wird groß, aber der Abstand benachbarter Werte ist nicht überall gleich.

Umfang von IEEE 754

IEEE 754-2019 beschreibt binäre und dezimale Formate, Operationen, Umwandlungen, Rundung, Ausnahmebedingungen und ihre Standardbehandlung. Eine Implementierung kann Hardware, Software oder beides verwenden.

NameHäufiger SprachtypGesamtVorzeichenExponentgespeicherte FractionPräzision
binary32meist float32182324 Bit
binary64meist double641115253 Bit

„Meist“ ist entscheidend: Die C- und C++-Typnamen allein erzwingen diese Formate nicht überall. C++ stellt Eigenschaften wie std::numeric_limits<T>::is_iec559 bereit.

Die Felder von binary32

1 Vorzeichenbit | 8 Exponentenbits | 23 Fraction-Bits

Für einen normalen endlichen Wert gilt:

(-1)^s × (1.fraction)₂ × 2^(E - 127)

Der Exponent besitzt einen Bias von 127. Ein normalisierter binärer Signifikand beginnt immer mit 1, deshalb wird dieses führende Bit nicht gespeichert. 23 gespeicherte Bits ergeben somit 24 Bit Präzision. Bei binary64 sind Bias 1023 und Präzision 53 Bit.

6.25 von Hand codieren

6.25₁₀ = 110.01₂ = 1.1001₂ × 2²

Das Vorzeichen ist 0. Der gespeicherte Exponent ist 2 + 127 = 129, also 10000001₂. Nach Weglassen der führenden 1 lautet die Fraction 1001000...0.

0 10000001 10010000000000000000000

Hexadezimal ist das 0x40C80000.

float value = 6.25f;
std::uint32_t bits = std::bit_cast<std::uint32_t>(value);

Besondere Exponentenmuster

ExponentFractionBedeutung
nur 0nur 0vorzeichenbehaftete Null
nur 0nicht 0subnormale Zahl
1 bis 254beliebignormale endliche Zahl
nur 1nur 0positive oder negative Unendlichkeit
nur 1nicht 0NaN

Damit reichen normale binary32-Exponenten von -126 bis 127.

Positive und negative Null

Wenn Exponent und Fraction null sind, bleibt das Vorzeichen erhalten. Daher existieren +0 und -0. Sie vergleichen gleich, doch manche Operationen bewahren die Richtung.

double p = 0.0;
double n = -0.0;

std::cout << (p == n) << '\n';
std::cout << std::signbit(n) << '\n';
std::cout << 1.0 / p << '\n';
std::cout << 1.0 / n << '\n';

Bei üblichem IEEE-Verhalten liefern die letzten Ausdrücke positive und negative Unendlichkeit.

Subnormale Zahlen

Die implizite 1 würde nahe null eine Lücke zwischen null und der kleinsten normalen Zahl lassen. Ist der gespeicherte Exponent null und die Fraction nicht null, gilt stattdessen:

(-1)^s × (0.fraction)₂ × 2^-126

Subnormale Zahlen nähern sich null mit allmählich sinkender Präzision. Die kleinste positive normale binary32-Zahl ist ungefähr 1.17549435 × 10^-38, die kleinste positive subnormale Zahl ungefähr 1.40129846 × 10^-45.

Manche SIMD- oder GPU-Umgebungen behandeln subnormale Werte aus Leistungsgründen als null. Numerischer Code muss diesen Modus kennen.

Unendlichkeit und NaN

Ein Exponent aus Einsen und eine Fraction von null stellen Unendlichkeit dar. Überlauf oder die Division eines von null verschiedenen endlichen Wertes durch null können sie bei Standardbehandlung erzeugen.

Ist die Fraction nicht null, handelt es sich um NaN, Not a Number. 0.0 / 0.0 oder die reelle Wurzel einer negativen Zahl sind typische Quellen.

NaN ist ungeordnet und vergleicht selbst mit sich nicht gleich.

double nan = std::numeric_limits<double>::quiet_NaN();
std::cout << (nan == nan) << '\n';
std::cout << std::isnan(nan) << '\n';

Zur Erkennung dient std::isnan. Unterstützung für quiet NaN, signaling NaN und Payloads unterscheidet sich nach Sprache und Hardware.

Warum 0.1 nicht exakt ist

0.1 ist der Bruch 1 / 10. Ein vollständig gekürzter Bruch besitzt nur dann eine endliche Binärdarstellung, wenn sein Nenner ausschließlich den Primfaktor 2 enthält. Zehn enthält zusätzlich 5, daher wiederholen sich die Binärstellen.

0.1₁₀ = 0.000110011001100110011...₂

Binary64 behält 53 signifikante Bits und rundet den Rest.

std::cout << std::setprecision(20) << 0.1 << '\n';

Ein typisches Ergebnis ist 0.10000000000000000555. Die bekannte Abweichung bei 0.1 + 0.2 entsteht durch die Rundung der Eingaben und des Additionsergebnisses.

Der Abstand wächst mit dem Betrag

Gleitkommawerte sind nicht gleichmäßig verteilt. Binary32 besitzt 24 Bit Präzision und kann daher alle Ganzzahlen bis 16,777,216 exakt darstellen. Danach beträgt der Abstand benachbarter Werte 2 oder mehr.

float a = 16'777'216.0f;
float b = a + 1.0f;
std::cout << (a == b) << '\n';

Unter binary32 ist das normalerweise wahr, weil in diesem Bereich kein eigener Wert für a + 1 existiert.

Epsilon ist keine universelle Toleranz

std::numeric_limits<double>::epsilon() ist der Abstand zwischen 1 und dem nächsten darstellbaren double. Er eignet sich nicht unverändert für alle Größenordnungen. Oft werden eine absolute Toleranz nahe null und eine relative Toleranz kombiniert.

bool almostEqual(double a, double b,
                 double absoluteTolerance = 1e-12,
                 double relativeTolerance = 1e-9)
{
    double difference = std::abs(a - b);
    if (difference <= absoluteTolerance)
    {
        return true;
    }
    double scale = std::max(std::abs(a), std::abs(b));
    return difference <= scale * relativeTolerance;
}

Die Grenzwerte müssen aus Einheit und Genauigkeitsanforderung der jeweiligen Anwendung stammen.

Rundungsmodi und fünf Ausnahmezustände

Liegt ein exaktes Ergebnis zwischen zwei darstellbaren Werten, bestimmt die Rundungsrichtung das gespeicherte Ergebnis. Ein häufiger Standard ist die Rundung zum nächsten Wert, wobei ein exakter Gleichstand zur geraden letzten Stelle geht.

C und C++ bieten über <fenv.h> und <cfenv> Zugriff auf unterstützte Rundungsrichtungen und Statusflags.

Die fünf IEEE-Zustände sind invalid operation, division by zero, overflow, underflow und inexact. Meist handelt es sich nicht um ein C++-throw; ein Standardergebnis wird erzeugt und ein Flag gesetzt.

std::feclearexcept(FE_ALL_EXCEPT);
volatile double one = 1.0;
volatile double zero = 0.0;
volatile double result = one / zero;

if (std::fetestexcept(FE_DIVBYZERO))
{
    std::cout << "division by zero flag\n";
}

Reihenfolge und Summenfehler

Da jedes Zwischenergebnis gerundet wird, muss endliche Gleitkommaaddition nicht assoziativ sein.

double large = 1e16;
double small = 1.0;

double first = (large + small) - large;
double second = small + (large - large);

Im ersten Ausdruck kann small verschwinden. Für lange Summen kommen Kahan-Summation, paarweises Addieren ähnlich großer Werte oder ein breiterer Akkumulator infrage.

Geld und exakte Daten

Binäres Gleitkomma ist für Grafik, Koordinaten und viele wissenschaftliche Rechnungen geeignet, aber meistens nicht für Geld mit exakten Dezimalregeln.

  • Die kleinste Einheit, etwa Cent, als Ganzzahl speichern.
  • Einen Dezimal- oder Festkommatyp verwenden.
  • Zeitpunkt und Richtung der Rundung ausdrücklich festlegen.

Auch Kennungen und exakte Zähler gehören in Ganzzahltypen.

Serialisierung und Prüfung der Implementierung

An Datei- oder Netzwerkgrenzen sind binary32 oder binary64, Byte-Reihenfolge und der Umgang mit vorzeichenbehafteter Null sowie NaN-Payloads festzulegen. Für Text-Roundtrips liefert C++ max_digits10 eine ausreichende Stellenzahl.

Mit std::numeric_limits lassen sich is_iec559, radix, digits, min(), denorm_min() und max() prüfen. Bei Gleitkommatypen ist min() die kleinste positive normale Zahl; der negativste endliche Wert kommt von lowest().

Quellen

Zusammenfassung

IEEE 754 nutzt Vorzeichen, Signifikand und Exponent, um mit wenigen Bits einen großen Bereich abzudecken. Der Preis sind Näherungen, betragabhängige Abstände, Sonderwerte und Rundung in Zwischenschritten. Mit diesem Modell werden 0.1 + 0.2, NaN-Vergleiche, Zittern großer Koordinaten und Summenfehler zu verständlichen Folgen derselben Regeln.