Fliesskommazahlen

Tags:

Fliesskommazahlen. Die mit dem Punkt (oder Komma). Sie entstehen bei Brüchen. Oder? Naja, nicht ganz. Oder jedenfalls ist das nur die halbe Wahrheit. Die Fliesskommazahlen sind speziell, und sie werden auch speziell gespeichert. Deshalb schauen wir sie uns heute etwas genauer an.

Inhalt

Zahlenkunde

Damit die restlichen Ausführungen verständlich sind, müssen wir uns bewusst sein, was die reellen Zahlen (ℝ) sind. Zu den reellen Zahlen gehören:

Beispiele für reelle Zahlen sind: 12.387, 543564.02, 4/7, -0.333..., 786.6666667, usw.

Was sind Fliesskommazahlen?

Die Frage mag offensichtlich klingen, da wir doch täglich mit diesen Fliesskommazahlen (floating-point numbers), auch Gleitkommazahlen genannt, zu tun haben. Aber es gibt einen wichtigen Aspekt, der nicht sofort ersichtlich ist: Fliesskommazahlen dienen zur Darstellung einer Annäherung an eine reelle Zahl, und nicht der Zahl selbst. Da dies jetzt etwas kompliziert klingen mag, schauen wir uns den Unterschied zu Festkommazahlen an. Denn die gibt es auch, obwohl wir sie in der Programmierung eigentlich nie verwenden.

Festkommazahlen vs. Fliesskommazahlen

Festkommazahlen (fixed-point numbers) dienen ebenfalls der Darstellung von reellen Zahlen.

Der Unterschied zwischen Fest- und Fliesskommazahlen liegt darin, dass bei ersteren definiert wird, wie viele Stellen für die Ganzzahl und wie viele für die Fraktion (den Nachkommateil) genutzt werden. Bei Fliesskommazahlen hingegen ist die Position des Kommas egal.

Nehmen wir als Beispiel die Zahl 1.9854835542.

Single vs. Double

Bei Fliesskommazahlen ist wie gesagt entscheidend, wie genau wir die Zahlen speichern wollen. Dazu bieten Programmiersprachen spezielle Fliesskommadatentypen an. In PHP gibt nur einen Datentyp: float. Dieser entspricht dem IEEE-754-Standard für doppelte Präzision (double precision).

Die zwei relevanten Typen sind:

Name Grösse Total Grösse Vorzeichen Grösse Exponent Grösse Mantisse Bias
Single, Float (in C), Real (in Fortran) 4 Bytes / 32 bit 1 bit 8 bit 23 bit 127
Double, Float (in PHP) 8 Bytes / 64 bit 1 bit 11 bit 52 bit 1023
Quad 16 Bytes / 128 bit 1 bit 14 bit 112 bit 16383

Was Vorzeichen, Exponent, Bias und Mantisse sind, erfahrt ihr gleich.

Anmerken möchte ich noch, dass diese Datentypen jeweils zur Basis 2 hin arbeiten. Es gibt auch noch Datentypen für Fliesskommazahlen die im Dezimalsystem (Basis 10) arbeiten. Die sind für unser aber nicht interessant, bzw. nicht Gegenstand dieses Artikels.

Vorzeichen, Exponent und Mantisse

Bevor wir rechnen können, muss geklärt werden, wie so ein double Datentyp (PHP-float) im Arbeitsspeicher abgebildet wird. Das er aus 64 Bits besteht, wissen wir ja nun.

Die Fliesskommazahl teil sich wie folgt auf:

Wie werden Fliesskommazahlen gespeichert?

Jetzt sind wir startklar. Yippie! Nehmen wir als Beispiel die Zahl 10.1 und fangen direkt mit dem Ganzahlteil (10) an.

Ganzzahlteil

Diese Umrechnung entspricht der normalen, simpeln Umrechnung von Dezimalzahlen in andere Systeme: Die Zahl solange durch die Basis des gewünschten Zahlensystems (Binärsystem = Basis 2) teilen, bis das Result 0 ergibt. Den Rest dabei immer merken und am Schluss von unten nach oben zusammenführen.

10 / 2 = 5 R 0
 5 / 2 = 2 R 1
 2 / 2 = 1 R 0
 1 / 2 = 0 R 1
-----------------
           > 1010

Nachkommateil

Nun berechnen wir den Nachkommateil (0.1). Wir multiplizieren den Ausgangswert solange mit 2 bis wir genügend Werte berechnet haben, um unsere Präzisionsanforderungen zu befriedigen (float = 52 bit Mantisse = 52 Werte). Als neuen Ausgangswert nutzen wir stets den Nachkommateil der vorherigen Multiplikation. Da wir mit 2 multiplizieren, wird der Ganzzahlteil des Resultats nie die Zahl 2 erreichen. In Wahrheit wird er immer entweder 0 oder 1 sein. Diese Ganzzahlteile führen wir am Schluss von oben nach unten zusammen.

0.1 * 2 = 0.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4
0.4 * 2 = 0.8
0.8 * 2 = 1.6
        > 0001100110011001100110011001100110011001100110011001

Zusammen erhalten wir für 10.110 die Zahl 1010.00011001100110011001100110011001100110011001100110012.

Nur schieben wir das Komma solange nach links, bis wir nur noch eine 1 vor dem Komma haben.

Wir rechnen also wie folgt:

  2^0 * 1010.0001100110011001100110011001100110011001100110011001
= 2^1 * 101.00001100110011001100110011001100110011001100110011001
= 2^2 * 10.100001100110011001100110011001100110011001100110011001
= 2^3 * 1.0100001100110011001100110011001100110011001100110011001

Falls die ursprüngliche Zahl kleiner als 1 gewesen wäre, dann müssten wir den Punkt nach rechts verschieben und erhielten einen negativen Exponenten (siehe Beispiel am Ende des Artikels).

Da nach dem Normalisieren die einzige Zahl vor dem Punkt eine 1 sein muss, gibt es auch keinen Grund sie zu speichern; sie ist gegeben. Von Interesse ist also nur noch 0100001100110011001100110011001100110011001100110011001. Davon nehmen wir uns die ersten 52 Werte für die Mantisse.

Exponenten Bias

Ausserdem merken wir uns den Exponenten 3.

Da der Exponent auch negativ sein kann (z.B. wenn die Zahl sehr klein (< 1, Ganzzahlteil = 0) ist und wir den Punkt deshalb nach rechts verschieben mussten), addieren wir den sogenannten Bias dazu. Bei double precision beträgt dieser 1023 und sorgt dafür, dass der Exponent immer als positive Zahl da steht. Wir erhalten als Exponent also 3 + 1023 = 1026. Diese Zahl rechnen wir nun wieder ins Binärsystem um.

1026 / 2 =  513 R 0
 513 / 2 =  256 R 1
 256 / 2 =  128 R 0
 128 / 2 =   64 R 0
  64 / 2 =   32 R 0
  32 / 2 =   16 R 0
  16 / 2 =    8 R 0
   8 / 2 =    4 R 0
   4 / 2 =    2 R 0
   2 / 2 =    1 R 0
   1 / 2 =    0 R 1
-------------------
                  > 10000000010

Zusammenfassung

Nun haben wir für alle Teile der Fiesskommazahl eine binäre Zahl. Zusammengesetzt schaut die Darstellung dann wie folgt aus:

V = Vorzeichen
E = Exponent
M = Mantisse

V EEEEEEEEEEE MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM
0 10000000010 0100001100110011001100110011001100110011001100110011001

Die Dezimalzahl 10.1 wird also als Fliesskommazahl im Speicher mit der Binärfolge 0100000000100100001100110011001100110011001100110011001100110011001 gespeichert.

Rückrechnung

Wie ihr euch vorstellen könnt, ist die Rückrechnung in eine dezimale Repräsentation einfach der umgekehrte Ablauf. Hier im Schnelldurchgang.

Exponent ins Dezimalsystem umrechnen.

10000000010 = 1026

Bias abziehen um eigentlichen Exponenten zu erhalten.

1026 - 1023 = 3

Der Mantisse eine 1 vorsetzen.

1.0100001100110011001100110011001100110011001100110011001

Exponent auf Mantisse anwenden, bzw. Komma verschieben.

1010.0001100110011001100110011001100110011001100110011001

Ganzzahlteil ins Dezimalsystem umrechnen.

1010 = 10

Nachkommateil rückwärts bit für bit zurückrechnen.

1 / 2 = 0.5
0.5 / 2 = 0.25
0.25 / 2 = 0.125
1.125 / 2 = 0.5625
1.5625 / 2 = 0.78125
0.78125 / 2 = 0.390625
0.390625 / 2 = 0.1953125
1.1953125 / 2 = 0.59765625
1.59765625 / 2 = 0.798828125
0.798828125 / 2 = 0.3994140625
0.3994140625 / 2 = 0.19970703125
1.19970703125 / 2 = 0.599853515625
1.599853515625 / 2 = 0.7999267578125
0.7999267578125 / 2 = 0.39996337890625
0.39996337890625 / 2 = 0.199981689453125
1.199981689453125 / 2 = 0.5999908447265625
1.5999908447265625 / 2 = 0.79999542236328125
0.79999542236328125 / 2 = 0.399997711181640625
0.399997711181640625 / 2 = 0.1999988555908203125
1.1999988555908203125 / 2 = 0.59999942779541015625
1.59999942779541015625 / 2 = 0.799999713897705078125
0.799999713897705078125 / 2 = 0.3999998569488525390625
0.3999998569488525390625 / 2 = 0.19999992847442626953125
1.19999992847442626953125 / 2 = 0.599999964237213134765625
1.599999964237213134765625 / 2 = 0.7999999821186065673828125
0.7999999821186065673828125 / 2 = 0.39999999105930328369140625
0.39999999105930328369140625 / 2 = 0.199999995529651641845703125
1.199999995529651641845703125 / 2 = 0.5999999977648258209228515625
1.5999999977648258209228515625 / 2 = 0.79999999888241291046142578125
0.79999999888241291046142578125 / 2 = 0.399999999441206455230712890625
0.399999999441206455230712890625 / 2 = 0.1999999997206032276153564453125
1.1999999997206032276153564453125 / 2 = 0.59999999986030161380767822265625
1.59999999986030161380767822265625 / 2 = 0.799999999930150806903839111328125
0.799999999930150806903839111328125 / 2 = 0.3999999999650754034519195556640625
0.3999999999650754034519195556640625 / 2 = 0.19999999998253770172595977783203125
1.19999999998253770172595977783203125 / 2 = 0.599999999991268850862979888916015625
1.599999999991268850862979888916015625 / 2 = 0.7999999999956344254314899444580078125
0.7999999999956344254314899444580078125 / 2 = 0.39999999999781721271574497222900390625
0.39999999999781721271574497222900390625 / 2 = 0.199999999998908606357872486114501953125
1.199999999998908606357872486114501953125 / 2 = 0.5999999999994543031789362430572509765625
1.5999999999994543031789362430572509765625 / 2 = 0.79999999999972715158946812152862548828125
0.79999999999972715158946812152862548828125 / 2 = 0.399999999999863575794734060764312744140625
0.399999999999863575794734060764312744140625 / 2 = 0.1999999999999317878973670303821563720703125
1.1999999999999317878973670303821563720703125 / 2 = 0.59999999999996589394868351519107818603515625
1.59999999999996589394868351519107818603515625 / 2 = 0.799999999999982946974341757595539093017578125
0.799999999999982946974341757595539093017578125 / 2 = 0.3999999999999914734871708787977695465087890625
0.3999999999999914734871708787977695465087890625 / 2 = 0.19999999999999573674358543939888477325439453125
1.19999999999999573674358543939888477325439453125 / 2 = 0.599999999999997868371792719699442386627197265625
1.599999999999997868371792719699442386627197265625 / 2 = 0.7999999999999989341858963598497211933135986328125
0.7999999999999989341858963598497211933135986328125 / 2 = 0.39999999999999946709294817992486059665679931640625
0.39999999999999946709294817992486059665679931640625 / 2 = 0.199999999999999733546474089962430298328399658203125
0.199999999999999733546474089962430298328399658203125 / 2 = 0.0999999999999998667732370449812151491641998291015625

Ganz- und Nachkommazahl zusammenführen und - falls nötig - Vorzeichen setzen.

10.0999999999999998667732370449812151491641998291015625

Voilà! Und wir sehen jetzt auch, das nicht 10.1 gespeichert wurde, sondern nur eine sehr genau Annäherung daran. Diese wir üblicherweise dann von der Programmiersprache automatisch gerundet.

Wieso der ganze Aufwand?

Der Hauptgrund ist Flexibilität. Mit Fliesskommazahlen können wir extrem kleine, aber auch extrem grosse Zahlen speichern - in ein und dem selben Datentyp. Wir verlieren dabei zwar Präzision, dieser Verlust ist in den allermeisten Anwendungsfällen aber unbedeutend und verkraftbar.

Was gibt es sonst noch zu beachten?

Anhang

Beispiel mit kleinen, negativen Zahl als Single

Zum Abschluss noch ein weiteres Beispiel. Wir wählen die Zahl -0.0054 und gucken, wie ihre binäre Repräsentation als Single ist. Die Form von Single schaut folgendermassen aus:

V EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM

Ganzzahlteil umrechnen

0 / 2 = 0 R 0
-------------
          > 0

Fraktion umrechnen

0.0054 * 2 = 0.0108
0.0108 * 2 = 0.0216
0.0216 * 2 = 0.0432
0.0432 * 2 = 0.0864
0.0864 * 2 = 0.1728
0.1728 * 2 = 0.3456
0.3456 * 2 = 0.6912
0.6912 * 2 = 1.3824
0.3824 * 2 = 0.7648
0.7648 * 2 = 1.5296
0.5296 * 2 = 1.0592
0.0592 * 2 = 0.1184
0.1184 * 2 = 0.2368
0.2368 * 2 = 0.4736
0.4736 * 2 = 0.9472
0.9472 * 2 = 1.8944
0.8944 * 2 = 1.7888
0.7888 * 2 = 1.5776
0.5776 * 2 = 1.1552
0.1552 * 2 = 0.3104
0.3104 * 2 = 0.6208
0.6208 * 2 = 1.2416
0.2416 * 2 = 0.4832
0.4832 * 2 = 0.9664
0.9664 * 2 = 1.9328
0.9328 * 2 = 1.8656
0.8656 * 2 = 1.7312
0.7312 * 2 = 1.4624
0.4624 * 2 = 0.9248
0.9248 * 2 = 1.8496
0.8496 * 2 = 1.6992
-------------------
           > 0000000101100001111001001111011

Normalisieren

  2^0  * 0.0000000101100001111001001111011
= 2^-1 * 00.000000101100001111001001111011
= 2^-2 * 000.00000101100001111001001111011
= 2^-3 * 0000.0000101100001111001001111011
= 2^-4 * 00000.000101100001111001001111011
= 2^-5 * 000000.00101100001111001001111011
= 2^-6 * 0000000.0101100001111001001111011
= 2^-7 * 00000000.101100001111001001111011
= 2^-8 * 000000001.01100001111001001111011

Exponent: -8
Mantisse: 01100001111001001111011

Bias zu Exponenten hinzufügen

-8 + 127 = 119

Exponent umrechnen

119 / 2 = 59 R 1
 59 / 2 = 29 R 1
 29 / 2 = 14 R 1
 14 / 2 =  7 R 0
  7 / 2 =  3 R 1
  3 / 2 =  1 R 1
  1 / 2 =  0 R 1
----------------
             > 1110111

Resultat

V EEEEEEEE MMMMMMMMMMMMMMMMMMMMMMM
1 01110111 01100001111001001111011

Die binäre Repräsentation von -0.005410 als Single entspricht also 101110111011000011110010011110112.

Ähnliche Artikel

Kommentare