Logo

Über den richtigen Umgang mit Fließkommazahlen

„Ein wahrhaft großer Mensch verliert nie die Einfachheit eines Kindes.“ - Konfuzius

Was jeder Entwickler über Float wissen sollte

In PHP stehen mit dem internen Typ Float Gleitkommazahlen zur Verfügung. Mit Float können also reelle Zahlen wie PI 3.14159265359... abgebildet werden. Auch wenn die PHP-Dokumentation von Float auf das Problem der "Genauigkeit von Gleitkommazahlen" eingeht, so erfolgt der Einsatz von Float in Programmen oft ohne genauere Kenntnis der dahinter liegenden Mathematik.

Ein einfaches Beispiel

Schauen wir uns das Problem an einem Beispiel genauer an. Wir wollen Zahlen in einem Float speichern und vergleichen. Im Beispiel haben wir die Zahlen $a=6.0 und $b=4.0.


$a = 6.0;
$b = 4.0;
var_dump($a+$b, $a+$b === 10.0); 
// double(10)
// bool(true)

    

Das Ergebnis der Addition ist wie erwartet 10.0 und das Ergebnis des Vergleichs ist true, also sind beide Zahlen gleich.

Kommen wir zum nächsten Beispiel: Addieren wir die Zahl $a=0.1 und $b=0.7 so erwarten wir als Ergebnis 0.8. Ein Vergleich von $a+$b === 0.8 sollte folglich true ergeben.


$a = 0.1;
$b = 0.7;
var_dump($a+$b, $a+$b === 0.8);
// double(0.8)
// bool(false)

    

Wie wir sehen ist das Ergebnis des Vergleichs jedoch false. Wer den mathematischen Grund für dieses Verhalten nachlesen möchte, sollte den Artikel What Every Computer Scientist Should Know About Floating-Point Arithmetic lesen. In diesem Artikel findet sich mathematisches Hintergrundwissen über die Fließkommadarstellung und über Rundungsfehler. Neben einer Diskussion über den IEEE-Fließkommastandards werden auch zahlreichen Beispiele besprochen. An dieser Stelle wollen wir uns damit beschäftigen wie wir mit diesem Verhalten umgehen.

Ein weiterer Artikel der sich mit Maschinengenauigkeit oder Maschinenepsilon auseinandersetzt ist auf Wikipedia zu finden.

Im wesentlichen hängt der Fehler mit der internen Darstellung der Zahlen und damit mit der Genauigkeit zusammen. Eine einfache Möglichkeit um mit diesem Umstand umzugehen, ist mit Genauigkeiten (Epsilon) zu arbeiten. So kann man schauen, ob die Differenz von zwei Zahlen einen gewissen Genauigkeit hat. Möchte man also prüfen ob $a und $b gleich sind, so kann man die Differenz bilden und prüfen ob das Ergebnis kleiner als ein Grenzwert ist (\abs($a-$b)<$precision).

Mit dieser Funktion erhalten wir jetzt das richtige Ergebnis:


function isEqual($a, $b, $precision=0.0001) {
   return (\abs($a-$b)<$precision);
}

$a = 0.1;
$b = 0.7;
var_dump($a+$b, isEqual($a+$b,0.8));
//double(0.8)
//bool(true)

    

Ein paar Worte zur Genauigkeit

Die von PHP gewählte Genauigkeit liegt in der Regel bei 14 Stellen. Die gewünschte Genauigkeit kann in PHP über die Konfigurationseinstellung precision in der php.ini definiert werden.

Wie sich die Genauigkeit in PHP auswirkt, sehen wir im folgenden Beispiel. Wir definieren eine Zahl und setzen die Konfigurationseinstellung precision einmal auf 14 und im Anschluss auf 21.


\ini_set('precision', 14);
$float = 24.6;
var_dump($float); 
// double(24.6)

\ini_set('precision', 20);
$float = 24.6;
var_dump($float); 
// double(24.600000000000001421)

    

Alvine Framework

In unserer täglichen Arbeit haben wir oft mit diesem Verhalten zu tun. Aus diesem Grund haben wir für uns ein Klasse Alvine\Types\FloatType erstellt. Die Klasse heißt FloatType, da Float ein reserviertes Schlüsselwort ist.

In dem Beispiel wird ein Objekt definiert und der Wert der Variable einmal als Float und einmal als Zeichenkette zurückgegeben.


// Float mit Wert 4.0 definieren.
$float=new \Alvine\Types\FloatType(4);

var_dump((string) $float);
// string(8) "4.000000"

var_dump($float->asFloat(2));
// double(4)

var_dump($float->asString(2));
// string(4) "4.00"

    

Die Klasse verfügt außerdem über Methoden um ein sicheres Rechnen mit Float-Werten abzubilden.


$float1=new \Alvine\Types\FloatType(0.7);
var_dump((string) $float1->add(0.1));
// string(8) "0.800000"

$float2=new \Alvine\Types\FloatType(0.7);
var_dump($float2->add(0.1)->asFloat(10));
// double(0.8)

$float3=new \Alvine\Types\FloatType(0.7);
var_dump($float3->add(0.1)->asString(10));
// string(12) "0.8000000000"

    

Oftmals ist es "sicherer" den Float-Wert als Zeichenkette abzubilden um somit eine definierte Genauigkeit zu erreichen. Aus diesem Grund gibt des die Methode Alvine\Types\FloatType::asString()

Die Dokumentation zum PHP-Framework befindet sich hier. Das Framework kann auf der Downloadseite heruntergeladen werden.

Im Framework finden sich weitere Klassen für unterschiedliche Typen wie zum Beispiel Map, Collection, Queue, oder Stack.

Quellen und weiterführende Artikel

Welchen Ansatz verfolgen Sie?
Zusätzliche Schlüsselwörter und Phrasen: Denormalisierte Zahl, Ausnahme, Fließkomma-Standard, Fließkomma, gradueller Unterlauf, Schutzziffer, NaN, Überlauf, Rundungsfehler, relativer Fehler, Rundungsmodus, ulp, Unterlauf