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.

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.

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
− 0

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

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.

+ 
 

Die Darstellung von Unendlich

Einige Zahlen in Fließkommadarstellung...

Dez.    Vz.   Exp.                       Mantisse
-2  ——  1 10000000000 0000000000000000000000000000000000000000000000000000
-1  ——  1 01111111111 0000000000000000000000000000000000000000000000000000
 0  ——  0 00000000000 0000000000000000000000000000000000000000000000000000
 1  ——  0 01111111111 0000000000000000000000000000000000000000000000000000
 2  ——  0 10000000000 0000000000000000000000000000000000000000000000000000
 3  ——  0 10000000000 1000000000000000000000000000000000000000000000000000
 4  ——  0 10000000001 0000000000000000000000000000000000000000000000000000
 5  ——  0 10000000001 0100000000000000000000000000000000000000000000000000
 6  ——  0 10000000001 1000000000000000000000000000000000000000000000000000
 7  ——  0 10000000001 1100000000000000000000000000000000000000000000000000
 8  ——  0 10000000010 0000000000000000000000000000000000000000000000000000
 9  ——  0 10000000010 0010000000000000000000000000000000000000000000000000
10  ——  0 10000000010 0100000000000000000000000000000000000000000000000000
11  ——  0 10000000010 0110000000000000000000000000000000000000000000000000
12  ——  0 10000000010 1000000000000000000000000000000000000000000000000000
13  ——  0 10000000010 1010000000000000000000000000000000000000000000000000
14  ——  0 10000000010 1100000000000000000000000000000000000000000000000000
15  ——  0 10000000010 1110000000000000000000000000000000000000000000000000
16  ——  0 10000000011 0000000000000000000000000000000000000000000000000000



Links zum Thema Fließkommazahlen


Zuletzt geändert am 08.10.2020