Frage

Da der float-Datentyp in PHP ist ungenau, und ein Schwimmer in MySQL braucht mehr Platz als ein INT (und ist ungenau), habe ich immer Ladenpreise als INTs, multipling um 100 vor dem Speicher uns genau 2 Nachkommastellen, um sicherzustellen, haben Orte der Präzision. Aber ich glaube, PHP ist misbehaving. Beispielcode:

echo "<pre>";

$price = "1.15";
echo "Price = ";
var_dump($price);

$price_corrected = $price*100;
echo "Corrected price = ";
var_dump($price_corrected);


$price_int = intval(floor($price_corrected));
echo "Integer price = ";
var_dump($price_int);

echo "</pre>";

Produziert Ausgabe:

Price = string(4) "1.15"
Corrected price = float(115)
Integer price = int(114)

Ich war überrascht. Wenn das Endergebnis als um 1 niedriger ist erwartet, ich war die Ausgabe von meinem Test erwarten zu sehen eher aus wie:

Price = string(4) "1.15"
Corrected price = float(114.999999999)
Integer price = int(114)

das würde die Ungenauigkeit der Schwimmers Art zeigen. Aber warum ist Boden (115) zurückkehr 114 ??

War es hilfreich?

Lösung

Versuchen Sie dies als eine schnelle Lösung:

$price_int = intval(floor($price_corrected + 0.5));

Das Problem, das Sie erleben werden, ist nicht PHP-Fehler, alle Programmiersprachen mit reellen Zahlen mit Fließkommaarithmetik haben ähnliche Probleme.

Die Faustregel für das Geld Berechnungen ist es, nie verwenden Schwimmer (weder in der Datenbank noch in Ihrem Skript). Sie können alle Arten von Problemen vermeiden, indem sie immer den Cent statt in Dollar zu speichern. Der Cent ganze Zahlen sind, und Sie können sie zusammen frei addieren und multiplizieren Sie mit anderen Zahlen. Jedes Mal, wenn Sie die Nummer angezeigt wird, stellen Sie sicher, dass Sie einen Punkt vor den letzten beiden Ziffern eingefügt werden.

Der Grund, warum Sie 114 statt 115 erhalten ist, dass floor abrundet, auf die nächste ganze Zahl, so Boden (114,999999999) wird 114. Die interessantere Frage ist, warum 1.15 * 100 114,999999999 ist statt 115. Der Grund für das heißt, dass 1,15 ist nicht gerade 115/100, aber es ist ein wenig kleiner, wenn Sie also mit 100 multiplizieren, erhalten Sie eine Nummer ein klein wenig kleiner als 115.

Hier ist eine ausführlichere Erklärung was echo 1.15 * 100; tut:

  • Es analysiert 1,15 auf eine binäre Gleitpunktzahl. Dies beinhaltet Runden, es geschieht ein wenig abzurunden am nächsten 1.15 die binäre Gleitpunktzahl zu erhalten. Der Grund, warum Sie nicht eine genaue Zahl bekommen (ohne Rundungsfehler) ist, dass 1,15 unendliche Anzahl von Ziffern in der Basis hat 2.
  • Es analysiert 100 in eine binäre Gleitkommazahl. Dies beinhaltet Runden, aber da 100 ist eine kleine ganze Zahl, der Rundungsfehler Null ist.
  • Sie berechnet das Produkt der beiden vorangegangenen Zahlen. Dazu gehört auch eine kleine Rundung, den nächst binär Gleitpunktzahl zu finden. Der Rundungsfehler geschieht Null in diesem Betrieb sein.
  • Er wandelt die binären Gleitkommazahl in eine Basis 10 Dezimalzahl mit einem Punkt, und druckt diese Darstellung. Dazu gehört auch eine kleine Rundung.

Der Grund, warum PHP den überraschenden Corrected price = float(115) druckt (statt 114,999 ...) ist, dass var_dump nicht die genaue Zahl nicht gedruckt werden (!), Aber er druckt die Zahl auf n - 2 gerundet (oder n - 1) Ziffern, wobei n Ziffern ist die Genauigkeit der Berechnung. Sie können dies leicht überprüfen:

echo 1.15 * 100;  # this prints 115
printf("%.30f", 1.15 * 100);  # you 114.999....
echo 1.15 * 100 == 115.0 ? "same" : "different";  # this prints `different'
echo 1.15 * 100 < 115.0 ? "less" : "not-less";    # this prints `less'

Wenn Sie schwimmt drucken, denken Sie daran:. Sie nicht immer alle Stellen sehen, wenn Sie den Schwimmer drucken

Siehe auch die große Warnung am Anfang der PHP float docs.

scroll top