Realzahl, Floating-Point

In der Mathematik bezeichnet die Menge der Reellen Zahlen alle Zahlen mit beliebigen Ziffern nach dem Komma. Da in einem Computer nur eine begrenzte Anzahl Bits zur Verfügung stehen, existieren spezielle Codierungen, welche einen genau definierten Teil des Reellen Zahlenbereiches abzubilden vermögen. All diese Codierungen werden grundsätzlich als Real-Zahl bezeichnet. In Prozessoren gibt es dementsprechend spezielle Befehle, welche mit ebendiesen Codierungen umgehen können. Während zu früheren Zeiten Codierungen wie Fixpunkt oder Binary coded decimals (BCD) verwendet wurden, hat sich seit einigen Jahrzehnten die vielseitig einsetzbare Floating-Point-Codierung durchgesetzt.

Floating-Point-Berechnungen werden in der sogenannten floating point unit (FPU) verarbeitet. Diese speichert Realzahlen mittels der Exponentialdarstellung, sprich, einer Aufspaltung der Zahl in Mantisse und Exponent. Durch die Wahl eines geeigneten Exponenten ist es möglich, das Trennzeichen (den Punkt, beziehungsweise das Komma) zwischen Ganzzahl-Teil und Bruch-Teil so zu verschieben, dass sowohl sehr grosse wie auch sehr kleine Zahlen mit derselben Codierung gespeichert werden können. Diese Verschiebung des Trennzeichens brachte der Codierung den Namen Floating-Point-Number oder auf Deutsch Fliesskomma-Zahl. Siehe dazu auch den Kommentar ganz unten auf der Seite.

Details

Die Fliesskomma-Codierung ist durch die IEEE 754 Norm festgelegt. Diese Codierung ist motiviert durch die Exponential-Darstellung von Dezimalzahlen. Die Zahl 0.00456 kann beispielsweise in der Exponentialdarstellung folgendermassen geschrieben werden: 4.56 * 10^-3, oder kurz 4.56e-3. Hierbei wird der Teil 4.56 als Mantisse und der Teil -3 als Exponent bezeichnet. Die dazugehörige Basis 10 ist implizit gegeben und wird einfach weggelassen. Durch diese Aufspaltung von Mantisse und Exponent können sowohl sehr grosse, wie auch sehr kleine Zahlen in kompakter Schreibweise angegeben werden. Die Fliesskomma-Codierung tut dies genauso.

Die Basis der Exponentialdarstellung wird auch Radix genannt. Während für Menschen die Radix-10-Darstellung üblich ist, rechnen die meisten FPUs mit Radix-2, basierend auf dem Binärsystem. Es sind auch Radix-10-FPUs im Umlauf, für die folgende Erklärung wird jedoch angenommen, dass eine FPU in Radix-2 rechnet.

Eine Fliesskommazahl besitzt stets 1 Vorzeichen-Bit, welches für positive Zahlen 0 und für negative Zahlen 1 ist. Die restlichen Bits werden auf die Mantisse und den Exponent aufgeteilt. In der IEEE 754 Norm werden insbesondere zwei Bit-Verteilungen als Standard definiert: Eine 32-Bit Fliesskommazahl mit 23 Bits Mantisse und 8 Bits Exponent für einfache Genauigkeit und eine 64-Bit Fliesskommazahl mit 52 Bits Mantisse und 11 Bits Exponent für doppelte Genauigkeit. Prozessorhersteller können grundsätzlich beliebige weitere Codierungen implementieren (und das tun sie auch). In der folgenden Erklärung werden die Bitgrössen für die einfache Genauigkeit verwendet (1 Bit Vorzeichen, 23 Bits Mantisse, 8 Bits Exponent):

Der Exponent wird als vorzeichenlose Ganzzahl mit einem sogenannten Bias (Verschiebung) codiert. Im Gegensatz zum Komplement entstehen negative Exponenten somit einfach durch Subtraktion des Bias von der vorzeichenlosen Ganzzahl. Wenn 8 Bits für den Exponenten reserviert sind und der Bias auf 127 gesetzt ist, so können die verschobenen Exponenten [0, 255], beziehungsweise die tatsächlichen Exponenten [-127, 128] codiert werden. Der jeweils kleinste und grösste Exponent ist jedoch für spezielle Zahlen reserviert (siehe unten). Somit können mit diesen Angaben schlussendlich die Exponenten [-126, 127] dargestellt werden.

Die Mantisse wird als vorzeichenlose Ganzzahl interpretiert. Für eine Mantisse mit 23 Bits ergibt sich somit der (Integer-) Wertebereich [0, 8388607]. Wenn dieser Wertebereich durch die Zahl 8388608 (=2^23) dividiert wird, so ergibt sich der (reelle) Wertebereich [0, 1). Da die Zahl 8388608 nur FAST alle Zahlen von 0 bis 9999999 (die grösste 7-Stellige Dezimalzahl) enthält, kann der reelle Wertebereich [0, 1) somit nur auf 6 Dezimalstellen (Nachkommastellen) genau abgebildet werden. Die kleinste unterscheidbare Einheit der Mantisse ist definiert als 1 / 8388608 = 1.192093e-7. Dieser Wert wird als das sogenannte Maschinen-Epsilon bezeichnet.

Bei der Codierung der Mantisse wird zusätzlich eine sogenannte Normalisierung vorgenommen, was folgendes bedeutet: Die Exponentialdarstellung ist nicht eindeutig. So ist beispielsweise die Zahl 4.56e-3 identisch mit der Zahl 45.6e-4. Diese Mehrdeutigkeit ist für eine Codierung ungeeignet. Um dem Abhilfe zu schaffen, wird definiert, dass in einer FPU nur diejenige Exponentialdarstellung gültig ist, welche vor dem Trennzeichen nur eine einzige Ziffer besitzt, welche zudem nicht Null sein darf. Bei einer Radix-2-FPU bedeutet dies somit: Das höchstwertige Bit muss 1 sein. Da dieses Bit also stets 1 sein muss, kann es auch einfach weggelassen und implizit bei Berechnungen angenommen werden, indem bei der Mantisse der Integer-Wert dieses Bits (2^23 = 8388608) hinzuaddiert wird. Gemäss obiger Rechnung kann somit bei der reell-wertigen Mantisse im Bereich von [0, 1) einfach 1 hinzuaddiert werden. Sollte es aufgrund der beschränkten Exponenten nicht möglich sein, die Mantisse einer Zahl zu normalisieren, so handelt es sich um eine denormalisierte Zahl (siehe unten).

Schlussendlich ergibt sich folgende (Pseudo-)Berechnung für eine Fliesskomma-codierte Radix-2-Zahl:

Dezimalzahl = Vorzeichen * (1 + Mantisse / 2^BitsProMantisse) * 2^(Exponent - Bias)

+-inf, denormalisierte Zahlen, +-Null, NaN

Da der Wertebereich von Fliesskommazahlen begrenzt ist, können Overflows und Underflows auftreten. Von einem Overflow wird geredet, wenn der Absolutwert einer Fliesskomma-Zahl zu gross wird, als dass sie mit dem Exponenten dargestellt werden könnte. Wenn dies passiert, wird der Exponent auf den maximalen Wert gesetzt und die Mantisse auf 0. Zusammen mit dem Vorzeichen wird diese Zahl interpretiert als +-infinity, also Plus/Minus Unendlich.

Wenn der Absolutwert einer Fliesskomma-Zahl zu klein wird, als dass sie mit dem Exponenten dargestellt werden könnte, wird von einem Underflow gesprochen. Hierbei wird der Exponent auf den minimalen Wert gesetzt, die Mantisse jedoch bleibt bestehen. Da diese Mantisse kein implizites 1 vor dem Trennzeichen besitzt, handelt es sich um eine denormalisierte Zahl (auf Englisch sub-normal). Die IEEE 754 Norm legt fest, dass denormalisierte Zahlen als gleichverteilte Zahlen hin zum Wert Null zu interpretieren sind. Diese Zahlen gelten jedoch als extrem ungenau und werden von FPUs nur aufgrund der Vollständigkeit halber unterstützt, oftmals jedoch mit starken Performance-Einbussen. Aus diesem Grund können manche FPUs so eingestellt werden, dass sie bei denormalisierte Zahlen einfach die Mantisse auf 0 setzen und die Zahl somit zu 0 runden.

Die denormalisierten Zahlen sind vergleichbar mit dem, was in manchen Wissenschaftsbereichen als Singularität bezeichnet wird: Jegliche Zahl, welche eine bestimmte Schwelle unterschreitet, kann nicht mehr exakt dargestellt werden.

Die Zahl Null muss bei der Fliesskomma-Codierung speziell behandelt werden. Da es keine normalisierte Darstellung der Zahl Null gibt, zählt sie ebenfalls zu den denormalisierten Zahlen. Da zudem jede Zahl, also auch eine denormalisierte, stets ein Vorzeichen besitzt, können die Zahlen +0 und -0 entstehen. Die Spezialbehandlung der Zahl Null besteht somit darin, dass +0 und -0 als dasselbe aufgefasst werden müssen.

Des weiteren definieren Fliesskommazahlen noch eine weitere Spezialzahl: NaN (Not a Number). Diese Zahl entsteht bei einem undefiniertem Rechenergebnis, beispielsweise, wenn die Wurzel aus einer negativen Zahl gezogen oder Null durch Null geteilt wird. Bei der NaN-Zahl wird der Exponent auf den maximal möglichen Wert gesetzt und die Mantisse auf einen Wert ungleich Null. Die genaue Spezifikation der Mantisse kann bei anderen Quellen nachgelesen werden.

Rechenfehler, numerische Genauigkeit

Für den Programmier-Alltag sind Fliesskommazahlen normalerweise unproblematisch. Sie vermögen den reellen Zahlenbereich genügend genau abzudecken. Dennoch sind sie aufgrund der limitierten Anzahl Bits nicht immer ganz exakt.

Eine genaue Ausführung, welche Probleme bei Fliesskommazahlen auftreten können, würde den Rahmen dieser Seite bei weitem sprengen. Es sei dem Leser überlassen, sich bei anderen Quellen dahingehend zu informieren, wieso 3 * 1 / 3 die Zahl 1 ergibt, 1 / 3 * 3 hingegen jedoch die Zahl .9999999

Fliesskommazahlen in C und C++

In manchen Programmiersprachen existiert ein Datentyp namens Real. In C und C++ hingegen werden die Typen float, double und long double verwendet. Sie werden als Fliesskomma-Zahl mit unterschiedlicher Genauigkeit codiert. Der Typ float entspricht der in der IEEE 754 Norm definierten einfachen Genauigkeit und der Typ double entspricht der doppelten Genauigkeit. Der Typ long double ist ein sogenannt erweiterter Typ mit ein paar Bits mehr Genauigkeit. Die genaue Definition dieses Typs ist jedoch ein Kapitel für sich, weswegen auf andere Quellen verwiesen wird.

In C und C++ wird der Typ double als Standardtyp angesehen. Wenn beispielsweise Typangaben zu einer Fliesskommazahl fehlen (siehe auch Typ-Promotion), wird automatisch der Typ double angenommen. Bei festen Werten im Programmcode wird ebenfalls standardmässig der Typ double angenommen, es sei denn, die Zahl wird mit einem Suffix versehen: f für float und l für long double.

Der Typ float kann von einer FPU normalerweise bedeutend schneller verarbeitet werden als double, ausserdem benötigt ein float nur 32 Bits, ein double hingegen deren 64. Die Genauigkeit des float-Typs reicht für viele alltägliche Anwendungen vollkommen aus. In der Wissenschaft jedoch stösst er schnell an seine Grenzen, weswegen dort vorwiegend mit dem Typ double programmiert wird.

Die Angaben zur Codierung der Typen sind in der float-Bibliothek festgelegt. Es ist zu beachten, dass die dort angegebene Anzahl Bits für die Mantisse das implizit hinzugefügte Bit mitzählt.

Fixpunkt-Arithmetik

Zu früheren Zeiten wurden Zahlen mit Nachkommastellen als sogenannte Fix-Point-Zahlen codiert, wobei einfach definiert wurde, wieviele Bits für die Zahlen vor dem Trennzeichen und wieviele nach dem Trennzeichen verwendet werden. Der Teil vor dem Trennzeichen (magnitude part oder auch integer part) wurde sodann als Ganzzahl (beispielsweise mittels des Zweierkomplements) interpretiert und der Teil nach dem Trennzeichen als Bruch (fractional part). Brüche werden mathematisch als rationale Zahlen bezeichnet, weswegen Bitgrössen-Angaben von Fixpunkt-Typen oftmals mit dem Zeichen Q angegeben werden.

Fixpunkt-Zahlen sind kaum standartisiert, können jedoch einfach definiert und auch einfach nachprogrammiert werden. So gibt es beispielsweise einen Fixpunkt-Typ mit 1 Vorzeichenbit, 15 Integer-Bits und 16 Fraction-Bits. Oder einen Fixpunkt-Typ mit 1 Vorzeichenbit und 31 Fraction-Bits. Theoretisch ist auch ein 8-Byte-Typ mit 1 Vorzeichen-Bit, 4 Integer-Bits und 3 Fraction-Bits denkbar. Beispielsweise für Schulnoten. Was das Vorzeichen bedeutet, sollte jedoch unbedingt vorher mit der Lehrperson abgesprochen werden.

Durch die Angabe der Bits sind der Wertebereich und damit auch die Handhabung der Arithmetik ähnlich wie bei Ganzzahlen zwar sehr exakt festgelegt, jedoch auch stark limitiert. Beispielsweise is es dadurch kaum möglich, Zahlen wie physikalische Konstanten genügend genau abzubilden, da sie oftmals entweder sehr gross oder sehr klein sind (Beispielsweise die Masse eines Atoms: 1.66 * 10^-27). Tatsächlich verwenden manche Technologien diese Fixpunkt-Arithmetik aber auch noch heute, für den alltäglichen Programmierbedarf hat sich jedoch die Fliesskomma-Zahl durchgesetzt.

Binary coded decimals (BCD)

Da Computer normalerweise mit Radix-2 arbeiten, haben sowohl Floating-Point-Zahlen als auch Fix-Point-Zahlen das Problem, dass sie Dezimalstellen (Zehntel, Hundertstel, Tausendstel, ...) nicht exakt abbilden können. In einigen Bereichen der Programmierung ist jedoch die exakte Handhabung von Dezimalstellen erfolgsentscheidend, beispielsweise im Finanzsektor.

Dementsprechend haben einige Prozessoren die BCD-Codierung unterstützt. Bei dieser Codierung wird eine Zahl in ihre Ziffern aufgespalten und jede Dezimal-Ziffer einzeln in 8 oder 4 Bits codiert:

0
1
2
3
4
5
6
7
8
9
00000000   0000
00000001   0001
00000010   0010
00000011   0011
00000100   0100
00000101   0101
00000110   0110
00000111   0111
00001000   1000
00001001   1001

Die restlichen Kombinationsmöglichkeiten der 4 oder 8 Bits werden schlicht verworfen. Bei 8 Bits ist dies eine gehörige Speicherplatzverschwendung. Die 4-Bit-Variante wurde sodann auch als gepackt, oder auf Englisch dense bezeichnet. Mit den entstandenen binären Zahlen konnte in den Prozessoren wie mit normalen Ganzzahlen gerechnet werden, allerdings musste vor oder nach jeder einzelnen Rechnung eine BCD-Korrektur angewendet werden. Diese Befehle hatten wohlklingende Namen wie Ascii adjust after addition.

Heutzutage sind BCDs vermutlich so gut wie ausgestorben, weswegen auf eine genauere Betrachtung der Korrekturbefehle verzichtet wird.

Bemerkung über die verwendeten Begriffe

Es ist zu beachten, dass das Wort Realzahl nicht gänzlich korrekt ist. Rein mathematisch müsste es reelle Zahl heissen, was jedoch im Englischen korrekt als Real number bezeichnet wird. Durch Eindeutschung hat sich daraus der Begriff Real-Zahl gebildet und eingebürgert. Er wird ganz vermischt manchmal Deutsch und manchmal Englisch ausgesprochen.

Des weiteren gibt es heftige Diskussionen darüber, welcher der Begriffe Fliesskomma, Gleitkomma, Fliesspunkt, Gleitpunkt, usw. korrekt ist. Alles sind mehr oder weniger unbedachte Übersetzungen vom englischen floating point. Korrekterweise müsste es heissen:

Exponentenmultiplikationsradixtrennzeichenpositionsverschiebungscodierung

Da dies etwas zu kompliziert ist, haben sich die obigen Begriffe eingebürgert. Je nach Situation, Hierarchie, Ort oder Institution kann das eine oder andere gelten. Auf dieser Seite wird aufgrund langjähriger Gewohnheit des Autors durchgehend die Fliesskomma-Variante verwendet.

Nächstes Kapitel: Integer-Arithmetik