PHP Float Werte vergleichen; auf Gleichheit prüfen | Rechenoperationen mit Float
2014-02-19

PHP,Float,DE

Will man in PHP Rechenoperationen mit Floats durchführen, gilt es einiges zu beachten. Dieser Artikel zeigt damit verbundene Probleme auf und stellt Lösungsansätze und Workarounds vor.

Aufgrund der endlichen Mantisse in der Fließkommadarstellung lassen sich solche Zahlen auf einem Computer nicht beliebig genau darstellen. Es handelt sich bei Fließkommazahlen (engl.: "Floats") um Näherungswerte, deren Genauigkeit abhängig ist von der voreingestellten "Precision" - die Anzahl der Nachkommastellen, die zur Betrachtung des Float Wertes herangezogen werden. Standardwert für PHP ist 14.

Übersicht

1. Vergleich von Float-Werten

Ein if ($fA !== $fB){..} funktioniert nicht, falls $fA und $fB Float Werte sind.

Daher niemals float Werte miteinander vergleichen. So auch die offizielle Empfehlung.

Orginal Zitat PHP:

"[…] So never trust floating number results to the last digit, and do not compare floating point numbers directly for equality. If higher precision is necessary, the arbitrary precision math functions and gmp functions are available. […]"

Quelle: php.net "language.types.float"

1.1. Maschinengenauigkeit "machine epsilon"

Der offizelle Workaround bzgl. eines Vergleichs von float Werten mittels Berücksichtigung des sog. machine epsilon funktioniert nicht gut genug''', wie Tests zeigten. Hier wird im Prinzip eine Abweichung der beiden Werte um einen Toleranz-Wert (der machine epsilon) geprüft; s.: php.net "language.types.float Comparison"

$fA = 1.23456789;
$fB = 1.23456780;
$fEpsilon = 0.00001;
if (abs($fA - $fB)  1.2. Vergleich von 2 Floats als Strings

Ein "Vergleich" von Floats kann erfolgen, indem man die Zahlen als Strings betrachtet. **Hierzu kann man die PHP Funktion strcmp (string compare) heranziehen**:
~~~php
strcmp ("$fA", "$fB");

Die Funktion liefert einen Integer Wert zurück:

 0: beide Zahlen (Strings) sind gleich
>0: $fA ist größer > als $fB
 2. Precision

Es muss immer eine ini Precision von 14 gelten. Gemeint ist die Genauigkeit von 14 Nachkommastellen, wobei 14 auch Standard / Default Einstellung ist (Stand 2014-02-12, PHP 5.3.10).

Wird eine höhere Precision angegeben, erhöhen sich schnell die Anzahl der Nachkommastellen bei Floatwerten.

Aus float(40) wird dann bspw. float(39.99999999995)

So kann die Precision zur Laufzeit gesetzt werden:

ini_set('precision', '14');

3. Cast

3.1. cast float -> zu -> integer

Einen float Wert niemals direkt nach integer casten. Das funktioniert nicht:

Fail:

// gibt 60342 - also falsch (das Ergebnis der Multiplikation ist ebenfalls ein float)
(int) (603.43 * 100); 

Stattdessen den Umweg über eine Stringbetrachtung gehen

  1. Zahl erst als String ausdrücken: "$fValue", und diesen Ausdruck sicher nach float casten: ((float) "$fValue")
  2. Dann diesen Float wiederum als String ausdrücken (durch anhängen eines Leerstrings ."")
  3. Den String dann mit "intval" zu integer casten

Beispiel:

$fValue = 603.43;
$iInteger = intval((((float) "$fValue") * 100 ) . '') 
var_dump($iInteger); // liefert int(60343) - korrekt

Beispiel ohne eine Rechnung:

$fValue = 603.43;
$iInteger = intval (((float) "$fValue") . ''); 
var_dump($iInteger); // liefert int(603) - korrekt

4. float in einer for - Schleife

Funktioniert nicht beständig, da hier eine Rechenoperation mit 2 Float-Werten vorgenommen wird - nämlich $fA und 0.1 -> also $fA+= 0.1. Die Schleife liefert schnell unerwartete Werte.

for ($fA = 0; $fA  5. Rechenoperationen mit Float Werten im Allgemeinen

Es zeigt sich, dass Rechenoperationen mit 2 oder mehreren Float Werten problematisch sind und nicht belastbare Ergebnisse liefern können. Ein weiteres Beispiel soll dies noch einmal erläutern:
~~~php
$fA = 0.1;
$fB = 0.7;

$fSum = ($fA + $fB);
var_dump($fSum); // OK: liefert float(0.8)

$fMulti = ($fSum * 10); // OK: liefert float(8)
var_dump($fMulti);

var_dump( floor($fMulti) ); // VORSICHT ! liefert float(7)

Erklärung: Gegeben sind 2 Float-Werte $fA und $fB. Die erste Rechenoperation (Addition) ($fA + $fB) liefert wie erwartet den Float 0.8 - Bei einer Standard-Precision von 14 wohlgemerkt. Wird jedoch bspw. eine Precision von 16 gesetzt (also eine höhere Genauigkeit, da nun 16 Nachkommastellen berücksichtigt werden), so ändert sich das Ergebnis auf: float(0.7999999999999999). Siehe dazu auch weiter oben den Abschnitt "Precision".

Die zweite Rechenoperation (Multiplikation) liefert ebenfalls wie erwartet float(8).

Die Ausgabe mit floor() nun jedoch liefert nicht wie erwartet den Wert float(8), sondern float(7)! Dies ist so, da die interne Repräsentation des Float-Wertes (womit letztendlich gerechnet wird) eine andere ist.

5.1. Workaround

Als Workaround kann man Float Werte als Strings ausdrücken und dann diese zur Berechnung heranziehen.

Das komplette obere Beispiel mit als String ausgedrückten Floats:

$fA = 0.1;
$fB = 0.7;

$fSum = ("$fA" + "$fB");
var_dump($fSum); // OK: liefert float(0.8)

$fMulti = ("$fSum" * 10); // OK: liefert float(8)
var_dump($fMulti);

var_dump( floor("$fMulti") ); // OK: liefert float(8)

Hier werden nun erwartete Ergebnisse geliefert, auch der Typ ist der richtige.


Links

Related


Comment++

E-Mail Adresse wird nicht veröffentlicht.
E-mail address will not be published.