Dies ist eine alte Version des Dokuments!


Gleitkommazahlen

Bislang dachte ich, dass alles was können ist Rechnen und das obendrein noch richtig schnell aber nachdem ich jetzt angefangen habe eine Programmiersprache zu lernen (C) musste ich feststellen, dass das so nicht stimmt und war zugegebenermaßen völlig überrascht, wie oft sich ein Computer tatsächlich verrechnet. Ein Wunder, dass die Dinger nicht ständig abstürzen.

Das Problem über das ich gestolpert bin, sind Berechnungen mit Gleitkommazahlen, auch Float genannt. Fragt man nun einen Computer ob »0,1 + 0,1 + 0,1« gleich »0,3« oder »1,1 * 1,1« gleich »1,21« ist, sagt er Nein. Klingt komisch, ist aber so:

float_1.c

#include <stdio.h>
 
int main( void ) { 
  printf( "%d\n", 1.1 * 1.1       == 1.21 );
  printf( "%d\n", 0.1 + 0.1 + 0.1 == 0.3  );
}

Dieses Programm vergleicht mit dem Vergleichsoperator »==« ob die Gleichungen in der printf() Funktion wahr oder falsch sind, wobei eine »1« als Rückgabe die Gleichung als »wahr«, bzw. eine »0« die Gleichung als »falsch« identifiziert:

$ ./float_1 
0
0

Da hat mein Weltbild schon angefangen zu wanken aber was ist dabei schlief gelaufen? Schauen wir uns mal an, was dann die og Rechnungen für ein Ergebnis liefern:

float_2.c

#include <stdio.h>
 
float f = 0.1 + 0.1 + 0.1;
float e = 1.1 * 1.1;
int main( void ) { 
  printf( "%.9f\n", f );
  printf( "%.9f\n", e );
}

Dieser Code gibt die Summen in der Variablendeklaration mit neun Nachkommastellen aus:

$ ./float_2 
0.300000012
1.210000038

Das erklärt zumindest mal das überraschende Ergebnis der Vergleichsoperation im ersten Programm aber befriedigend ist erst mal nicht. Das Ganze geht sogar noch einen Schritt weiter, lassen wir den Rechner mal nicht rechnen, sondern uns nur die interne Darstellung der beiden Zahlen »0,1« und »1,1« ausgeben:

float_3.c

#include <stdio.h>
 
float f = 0.1;
float e = 1.1;
int main( void ) {
  printf( "%.9f\n", f );
  printf( "%.9f\n", e );
}

$ ./float_3 
0.100000001
1.100000024

Ich fand, dass schreit nach einer Erklärung und ich habe dieses Thema in der Newsgroup »de.comp.lang.c« zur Diskussion gestellt. Also warum ist das so? Die kurze Antwort ist: Das liegt an der Art, wie intern Dezimalzahlen in Gleitkommazahlen dargestellt werden. Die lange Antwort aber ist gar nicht so leicht zu verstehen und beim ersten Überfliegen der Erklärung dachte ich, dass schon ein paar Semester Mathematik nötig wären, um das in letzter Konsequenz zu verstehen.

Kurz gefasst kann man festhalten, dass DEzimalzahlen intern als Ganzzahl dargestellt werden. Ergibt die Umrechnung der Dezimalzahl keine Ganzzahl, ist sie nicht exakt als Binärzahl darstellbar und es kommt zu oben gezeigten Umrechnungsfehlern. Nehmen wir unser og Beispiel »0,1« und rechnen das mal in eine Ganzzahl um. Es wird zuerst die Anzahl der Nachkommastellen als Exponent zur Basis 2 errechnet. »0,1« hat eine Nachkommastelle → »21 = 2«. Nun wird die Dezimalzahl mit diesem ermittelten Wert multipliziert und wenn das Ergebnis dieser Multiplikation keine ganze Zahl ergibt, kommt es zu dem oben gezeigten Effekt → »0,1 * 2 = 0,2«.

Schauen wir mal wie es mit »1,1« ausschaut: »21 * 1,1 = 1,21«, was auch keine ganze zahl ergibt. Schauen wir uns mal eine Dezimalzahl an, bei der das aber funktioniert: »1.998046875« hat neun Nachkommastellen, was »29 = 512« ergibt, multipliziert man nun »1.998046875« mit »512« hat man das Ergebnis »1023«. Das ist eine ganze Zahl und wird dementsprechend korrekt dargestellt:

float_4.c

#include <stdio.h>
 
float f =1.998046875;
int main( void ) {
  printf( "%.70f\n", f );
}

$ ./float_4 
1.9980468750000000000000000000000000000000000000000000000000000000000000

Ein weiterer Grund warum eine Dezimalzahl uU nicht exakt dargestellt werden kann liegt daran, dass die binäre Darstellung nicht mehr vollständig in den dafür vorgesehenen Platz abgebildet werden kann und hier fängt es an kompliziert zu werden. Eine binäre Gleitkommazahl wird in einem 32 Bit (single precision) bzw. 64 Bit (double precision) Format definiert. Die 32 Bit der single precision teilt sich auf in 1 Bit für das Vorzeichen, 8 Bit für den Exponenten und 23 Bit für die Mantisse. Die Anordnung der Bits hängt dabei von der jeweiligen Bytereihenfolge (little/big endian) ab.

Erläutern wir dieses System mal anhand der Dezimalzahl »18,4« an:

1) Errechnung des Bias

»Bias = (2(r-1))-1« wobei »r« die Anzahl der Bits im Exponenten der Gleitkommazahl (in unserem Fall sind 8 Bit für den Exponenten vorgesehen) darstellt. Was folgendes ergibt:

Bias = (2(8-1))-1 = (27)-1 = 128-1 = 127

2) Umrechnung der Dezimalzahl in eine duale Festkommazahl ohne Vorzeichen:

18,4

 18/2 = 9 Rest 0 (least significant Bit)
  9/2 = 4 Rest 1
  4/2 = 2 Rest 0
  2/2 = 1 Rest 0
  1/2 = 0 Rest 1 (most significant bit)
      = 10010

0,4*2 = 0,8 -0   (most significant bit)
0,8*2 = 1,6 -1
0,6*2 = 1,2 -1
0,2*2 = 0,4 -0
0,4*2 = 0,8 -0
0,8*2 = 1,6 -1   (least significant bit)
      *
      *
      *
      = 0,011001100110011...
     
 18,4 = 10010,011001100110011...

it/float.1322409706.txt.gz (17850 views) · Zuletzt geändert: 2011/11/27 17:01 von wikisysop
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0