1. Einleitung
Floats zu Vergleichen ist keine triviale Aufgabe. Im Gegensatz zu Integern kann man für zwei Floats $a und $b nicht einfach ein schlichtes $a === $b verwenden, um herauszufinden, ob beide den selben Wert abbilden. Das liegt an der Ungenauigkeit von Floats, die dazu führen kann, dass zwei Floats, die eigentlich gleich sein sollten, trotzdem minimale Abweichung auf der x-ten Nachkommastelle haben. Diese würden dann bei einem Vergleich über $a === $b nicht als gleich angesehen werden, da sie nicht exakt gleich sind, sondern nur nahezu gleich. Das Beispiel stellt dieses Problem dar, 0,1 + 0,7 ist in PHP nicht das gleiche wie 0,8:
<?php // 1.0 + 7.0 ist gleich 8.0 $a = 1.0; $b = 7.0; var_dump($a+$b, $a+$b === 8.0); // 8 und true // 0.1 + 0.7 ist ungleich 0.8 $a = 0.1; $b = 0.7; var_dump($a+$b, $a+$b === 0.8); // 0.8 und false // 0.1 + 0.3 ist gleich 0.4 $a = 0.1; $b = 0.3; var_dump($a+$b, $a+$b === 0.4); // 0.4 und true ?>
float(8) bool(true) float(0.8) bool(false) float(0.4) bool(true)
2. Vergleich anhand von Differenz (Epsilon)
Ein Weg zum Vergleich von Floats ist die Berechnung der Differenz und das anschließende Prüfen, ob diese Differenz unterhalb eines gewissen Schwellwerts (Epsilon) liegt. Dies wird im nächsten Beispiel von der Funktion isFloatEqual($a, $b, $epsilon) durchgeführt. $a und $b sind Float-Werte, $epsilon der Schwellwert. Es wird true zurückgegeben, falls der Betrag von $a minus $b kleiner als der Schwellwert ist. Mathematisch ausgedrückt muss also gelten: |$a-$b| < $epsilon.
<?php function isFloatEqual($a, $b, $epsilon=0.0001) { return (abs($a - $b) < $epsilon); } var_dump(isFloatEqual(8.0, 1.0+7.0)); // true var_dump(isFloatEqual(0.8, 0.1+0.7)); // true var_dump(isFloatEqual(0.4, 0.1+0.3)); // true var_dump(isFloatEqual(1.0, 0.5+0.501)); // false var_dump(isFloatEqual(1.0, 0.5+0.50001)); // true var_dump(isFloatEqual(1.0, 0.5+0.50001, 0.000001)); // false var_dump(isFloatEqual(13.7, 27.3)); // false ?>
bool(true) bool(true) bool(true) bool(false) bool(true) bool(false) bool(false)
3. Vergleich durch Runden
Der zweite mögliche Weg erfolgt über das Runden beider Zahlen, um alle Abweichungen ab einer bestimmten Nachkommastelle zu ignorieren. Im nachfolgenden Beispiel rundet die Funktion isFloatEqual($a, $b, $decPlaces) die Float-Werte $a und $b auf $decPlaces Stellen nach dem Komma. Falls diese gerundeten Werte exakt gleich sind, wird true zurückgegeben. Sonst false. $decPlaces sollte nicht zu groß gewählt werden, um zu vermeiden, dass die gerundeten Zahlen ebenfalls unerwartete Ungenauigkeiten aufweisen. (Im Allgemeinen ist die vorherige Methode mit Epsilon vorzuziehen, da bei dieser weniger schief gehen kann.)
<?php function isFloatEqual($a, $b, $decPlaces=3) { return (round($a, $decPlaces) === round($b, $decPlaces)); } var_dump(isFloatEqual(8.0, 1.0+7.0)); // true var_dump(isFloatEqual(0.8, 0.1+0.7)); // true var_dump(isFloatEqual(0.4, 0.1+0.3)); // true var_dump(isFloatEqual(1.0, 0.5+0.501)); // false var_dump(isFloatEqual(1.0, 0.5+0.50001)); // true var_dump(isFloatEqual(1.0, 0.5+0.50001, 12)); // false var_dump(isFloatEqual(13.7, 27.3)); // false ?>
bool(true) bool(true) bool(true) bool(false) bool(true) bool(false) bool(false)