Ein wenig Theorie zu den Fließkommazahlen
in der Softwaretechnik mit C und C++

Es hat sich meiner Erfahrung nach herumgesprochen, dass die Verwendung von Fließkommazahlen in der Software nicht immer ganz problemfrei ist. Manch Programmierer scheut deshalb deren Verwendung wie der Teufel das Weihwasser. Andere Entwickler verwenden die ensprechenden Datentypen float und double etwas naiver und reagieren erst wenn die Software zu erkennen gibt, dass sie nicht so laufen möchte, wie es der Schreiber eigentlich wollte. Doch was ist nun eigentlich das Problem mit diesen Zahlen?

Das Problem

Einfache Vergleiche von errechneten Fließkommazahlen unter Verwendung der Vergleichsoperatoren in C und C++ führen oft nicht zum erwarteten Ergebnis. Das C-Beispiel in nebensehenden Kasten lässt sich zum Beispiel mit Standard C unter UNIX/Linux compilieren: cc bsp1.c -o bsp1
Der Aufruf ./bsp1 zeigt ungleich.

Das liegt daran, dass der Wert 1.2 wie die meisten Zahlen binär überhaupt nicht exakt darstellbar ist. Deshalb wird ein Wert codiert, der knapp neben dem zu erwarteten Ergebnis liegt. Generell kann gesagt werden, dass keine Darstellungsform von Realen Zahlen in der Lage ist, alle Zahlen abzubilden. Auch nicht die dezimale Form, die wir für gewöhnlich im Alltag verwenden. Zwischen zwei darstellbaren Zahlen liegen immer unendlich viele nicht darstellbare. Dieses grundsätzliche Problem ist also keine Spezialität der Binärdarstellung in Computersystemen. Man kann die meisten Zahlen bestenfalls als Näherung in ausreichender Genauigkeit darstellen und nur wenige exakt.

Die technische Darstellung von Fließkommazahlen in C und C++

Die in C und C++ gebräuchlichen Gleitkommatypen float und double sind im Standard IEEE754 definiert.

Darin ist geregelt, dass eine Zahl z nach dem Prinzip z = m · 2e dargestellt wird. Wobei m die Mantisse und e der Exponent ist. Der Datentyp float hat 32 Bit, die sich in eine Mantisse mit 23 Bit, einen Exponenten mit 8 Bit und ein Vorzeichenbit aufteilen.

VZ - E -------- M -----------------------

Bei double sieht die Aufteilung genauso aus. Nur die Breiten ändern sich gegenüber float. Die Mantisse hat 52 und der Exponent 11 Bit. Zusammen mit dem Vorzeichenbit kommen wir auf eine Größe von 64 Bit.

VZ - E ----------- M ----------------------------------------------------

Wie werden nun die Gleikommazahlen codiert?

Die Normalisierung von Fließkommawerten

Wenn man Werte so darstellt, dass sie vor dem Komma genau eine Stelle ungleich 0 haben, wird diese Darstellung normalisiert genannt. Das ist in der Binärdarstellung natürlich immer die 1. Der Wert 0.0 kann deshalb nicht normalisiert werden. Aber bei den meisten Zahlen, die in der Exponentendarstellung darstellbar sind, ist eine Normalisierbarkeit möglich. Man verschiebt dabei das Komma mit Hilfe des Exponenten so, dass nur eine Stelle davor übrig bleibt. Die normalisierten Zahlen bewegen sich für float im Bereich FLT_MIN bis FLT_MAX im positiven, wie auch im negativen Bereich. Um die 0.0 herum ist der Bereich der nicht normalisierten oder eben denormalisierten Werte. Für double gibt es die Konstanten DBL_MIN und DBL_MAX.

Durch das Zusammenwirken von Mantisse und Exponent wird ein Wert dargestellt, der durch das Vorzeichenbit nur noch mit einem Vorzeichen versehen wird. Der Exponent verschiebt einfach nur das Komma - Im binären Zahlensystem natürlich. Dabei gibt es noch eine Besonderheit bezüglich des Exponenten.

Die Verschiebedarstellung des Exponenten

Der Exponent wird in einer sogenannten Verschiebedarstellung repräsentiert (engl. Biased Representation). Dazu wird ein Verschiebewert - Bias oder Exzess - aufaddiert. Wenn man also den echten Exponenten eines Wertes erhalten möchte, muss man den entsprechenden Bias vom dargestellten Exponentenwert abziehen. Das Bias ist für float 127 und für double 1023.

Die Verschiebedaestellung führt dazu, dass sich Gließkommazahlen bei größer/kleiner-Vergleichen wie ganze Zahlen vergleichen lassen. Der Exponent wird damit in eine Form gebracht, in der sein direkter Betrag verglichen werden kann. Bei der Mantisse ist das sowieso schon der Fall. Da im Bitmuster der Exponent die Mantisse nach vorne erweitert, können beide zusammen genommen direkt verglichen werden. Die Zahl, bei der am weitesten vorne eine 1 steht, während die andere an der gleichen Position eine 0 hat, ist die größere. Das ist technisch als Hardwareoperation sehr einfach zu realisieren und deshalb auch sehr performant.

Sonderfälle

Die denormalisierten Zahlen

Wenn der Exponent nur aus Nullen besteht, spricht man von denormalisierten Zahlen (engl. subnormal). In diesem Fall wird keine 1 vor dem Komma angenommen. Außerdem wird mit dem Exponen -126 für float und -1022 für double gerechnet.

Die Null

Die 0 ist eine denormalisierte Zahl. Sie kann mit beiden Vorzeichen dargestellt werden.

+ 0 :    0000000000000000000000000000000000000000000000000000000000000000
− 0 :    0000000000000000000000000000000000000000000000000000000000000000


Not a Number - NaN

Wenn der Exponent nur aus Einsen besteht und die Mantisse mindestens eine Eins enthält so handelt es sich um eine Darstellung für NaN. Das Vorzeichenbit spielt für die Darstellung keine Rolle. Undefinierte Rechenoperationen können NaN als Resultat haben.

NaN :    ?111111111110101010101010101010101010101010101010101010101010101


Die Darstellung von Unendlich

Divisionen durch Null haben das Ergebnis Unendlich. Je nachdem ob eine positive oder negative Zahl durch Null geteilt wurde ergibt sich +/-Unendlich. In der beschriebenen Gleitkommadarstellung besteht der Exponent nur aus Einsen und die Mantisse nur aus Nullen.

 :    0111111111110000000000000000000000000000000000000000000000000000
−  :    1111111111110000000000000000000000000000000000000000000000000000


Einige Zahlen in Fließkommadarstellung...

Dez.   Vz.   Exp.                         Mantisse
-2 :   1100000000000000000000000000000000000000000000000000000000000000
-1 :   1011111111110000000000000000000000000000000000000000000000000000
 0 :   0000000000000000000000000000000000000000000000000000000000000000
 1 :   0011111111110000000000000000000000000000000000000000000000000000
 2 :   0100000000000000000000000000000000000000000000000000000000000000
 3 :   0100000000001000000000000000000000000000000000000000000000000000
 4 :   0100000000010000000000000000000000000000000000000000000000000000
 5 :   0100000000010100000000000000000000000000000000000000000000000000
 6 :   0100000000011000000000000000000000000000000000000000000000000000
 7 :   0100000000011100000000000000000000000000000000000000000000000000
 8 :   0100000000100000000000000000000000000000000000000000000000000000
 9 :   0100000000100010000000000000000000000000000000000000000000000000
10 :   0100000000100100000000000000000000000000000000000000000000000000
11 :   0100000000100110000000000000000000000000000000000000000000000000
12 :   0100000000101000000000000000000000000000000000000000000000000000
13 :   0100000000101010000000000000000000000000000000000000000000000000
14 :   0100000000101100000000000000000000000000000000000000000000000000
15 :   0100000000101110000000000000000000000000000000000000000000000000
16 :   0100000000110000000000000000000000000000000000000000000000000000



Links zum Thema Fließkommazahlen


Zuletzt geändert am 03.05.2021