PHP7.1 json_encode () Float-Problem

92

Dies ist keine Frage, da es eher bewusst ist. Ich habe eine Anwendung aktualisiert, die json_encode()PHP7.1.1 verwendet , und es wurde ein Problem festgestellt, bei dem Floats so geändert wurden, dass sie manchmal 17 Stellen umfassen. Laut Dokumentation wurde PHP 7.1.x serialize_precisionbeim Codieren von Doppelwerten anstelle der Genauigkeit verwendet. Ich vermute, dies hat einen Beispielwert von verursacht

472,185

werden

472.18500000000006

nachdem dieser Wert durchgegangen ist json_encode(). Seit meiner Entdeckung bin ich zu PHP 7.0.16 zurückgekehrt und habe kein Problem mehr mit json_encode(). Ich habe auch versucht, auf PHP 7.1.2 zu aktualisieren, bevor ich wieder auf PHP 7.0.16 zurückkam.

Die Gründe für diese Frage stammen aus PHP - Floating Number Precision . Der Grund dafür liegt jedoch in der Änderung von Precision zu serialize_precision in json_encode().

Wenn jemand eine Lösung für dieses Problem kennt, würde ich gerne die Argumentation / Lösung anhören.

Auszug aus einem mehrdimensionalen Array (vorher):

[staticYaxisInfo] => Array
                    (
                        [17] => stdClass Object
                            (
                                [variable_id] => 17
                                [static] => 1
                                [min] => 0
                                [max] => 472.185
                                [locked_static] => 1
                            )

                    )

und nach dem Durchgehen json_encode()...

"staticYaxisInfo":
            {
                "17":
                {
                    "variable_id": "17",
                    "static": "1",
                    "min": 0,
                    "max": 472.18500000000006,
                    "locked_static": "1"
                }
            },
Gwi7d31
quelle
6
ini_set('serialize_precision', 14); ini_set('precision', 14);würde es wahrscheinlich wie früher serialisieren lassen, aber wenn Sie sich wirklich auf eine bestimmte Präzision Ihrer Schwimmer verlassen, machen Sie etwas falsch.
Apokryfos
1
"Wenn jemand eine Lösung für dieses Problem kennt" - welches Problem? Ich kann hier kein Problem sehen. Wenn Sie den JSON mit PHP dekodieren, erhalten Sie den von Ihnen codierten Wert zurück. Und wenn Sie es in einer anderen Sprache dekodieren, erhalten Sie höchstwahrscheinlich den gleichen Wert. In beiden Fällen erhalten Sie den ursprünglichen ("korrekten") Wert zurück, wenn Sie den Wert mit 12 Ziffern drucken. Benötigen Sie eine Genauigkeit von mehr als 12 Dezimalstellen für die von Ihrer Anwendung verwendeten Floats?
Axiac
12
@axiac 472.185! = 472.18500000000006. Es gibt einen klaren Vorher-Nachher-Unterschied. Dies ist Teil einer AJAX-Anforderung an einen Browser, und der Wert muss im ursprünglichen Zustand bleiben.
Gwi7d31
4
Ich versuche, die Verwendung einer Zeichenfolgenkonvertierung zu vermeiden, da das Endprodukt Highcharts ist und keine Zeichenfolgen akzeptiert. Ich denke, ich würde es als sehr ineffizient und schlampig betrachten, wenn Sie einen Float-Wert nehmen, ihn als String umwandeln, ihn wegschicken und dann Javascript den String mit parseFloat () zurück in einen Float interpretieren lassen. Nicht wahr?
Gwi7d31
1
@axiac Ich stelle fest, dass Sie PHP sind. json_decode () bringt den ursprünglichen Float-Wert zurück. Wenn Javascript die JSON-Zeichenfolge jedoch wieder in ein Objekt umwandelt, wird der Wert nicht zurück in 472.185 konvertiert, wie Sie es möglicherweise unterstellt haben ... daher das Problem. Ich werde bei dem bleiben, was ich vorhabe.
Gwi7d31

Antworten:

97

Das hat mich ein bisschen verrückt gemacht, bis ich endlich diesen Fehler gefunden habe, der Sie auf diesen RFC hinweist, der besagt

Derzeit json_encode()wird EG (Genauigkeit) verwendet, die auf 14 eingestellt ist. Dies bedeutet, dass höchstens 14 Ziffern zum Anzeigen (Drucken) der Nummer verwendet werden. IEEE 754 double unterstützt eine höhere Genauigkeit und serialize()/ oder var_export()verwendet PG (serialize_precision), das standardmäßig auf 17 gesetzt ist. Da json_encode()EG (Präzision) verwendet wird, werden json_encode()niedrigere Ziffern von Bruchteilen entfernt und der ursprüngliche Wert zerstört, selbst wenn der Float von PHP einen genaueren Float-Wert enthalten könnte.

Und (Hervorhebung von mir)

Dieser RFC schlägt vor, eine neue Einstellung EG (Genauigkeit) = - 1 und PG (serialize_precision) = - 1 einzuführen, die den Modus 0 von zend_dtoa () verwendet, der einen besseren Algorithmus zum Runden von Gleitkommazahlen verwendet (-1 wird verwendet, um den 0-Modus anzuzeigen). .

Kurz gesagt, es gibt eine neue Möglichkeit, PHP 7.1 json_encodedazu zu bringen, die neue und verbesserte Präzisions-Engine zu verwenden. In php.ini müssen Sie ändern serialize_precisionzu

serialize_precision = -1

Sie können überprüfen, ob es mit dieser Befehlszeile funktioniert

php -r '$price = ["price" => round("45.99", 2)]; echo json_encode($price);'

Du solltest bekommen

{"price":45.99}
Machavity
quelle
G(precision)=-1und PG(serialize_precision)=-1 kann auch in PHP 5.4
Kittygirl
1
Sei vorsichtig mit serialize_precision = -1. Mit -1 echo json_encode([528.56 * 100]);druckt dieser Code[52855.99999999999]
vl.lapikov
3
@ vl.lapikov Das klingt allerdings eher nach einem allgemeinen Gleitkommafehler . Hier ist eine Demo , bei der Sie deutlich sehen können, dass dies nicht nur ein json_encodeProblem ist
Machavity
38

Als Plugin-Entwickler habe ich keinen allgemeinen Zugriff auf die php.ini-Einstellungen eines Servers. Basierend auf Machavitys Antwort habe ich diesen kleinen Code geschrieben, den Sie in Ihrem PHP-Skript verwenden können. Setzen Sie es einfach oben auf das Skript und json_encode funktioniert wie gewohnt weiter.

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'serialize_precision', -1 );
}

In einigen Fällen muss eine weitere Variable festgelegt werden. Ich füge dies als zweite Lösung hinzu, da ich nicht sicher bin, ob die zweite Lösung in allen Fällen gut funktioniert, in denen sich die erste Lösung als wirksam erwiesen hat.

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'precision', 17 );
    ini_set( 'serialize_precision', -1 );
}
Alev
quelle
3
Seien Sie vorsichtig, da Ihr Plugin möglicherweise unerwartete Einstellungen für den Rest der Entwickleranwendung ändert. Aber, IMO, ich bin nicht sicher, wie zerstörerisch diese Option werden könnte ... lol
igorsantos07
Beachten Sie, dass der Änderungsgenauigkeitswert (zweites Beispiel) einen größeren Einfluss auf andere mathematische Operationen haben kann, die Sie dort haben. php.net/manual/en/ini.core.php#ini.precision
Ricardo Martins
@ RicardoMartins: Gemäß Dokumentation ist die Standardgenauigkeit 14. Die obige Korrektur erhöht diese auf 17. Daher sollte sie noch genauer sein. Sind Sie einverstanden?
Alev
@alev Was ich gesagt habe ist, dass es ausreicht, nur die serialize_precision zu ändern und andere PHP-Verhaltensweisen Ihrer Anwendung nicht zu beeinträchtigen
Ricardo Martins
4

Ich verschlüsselte Geldwerte und hatte Dinge wie das 330.46Kodieren 330.4600000000000363797880709171295166015625. Wenn Sie die PHP-Einstellungen nicht ändern möchten oder können und die Struktur der Daten im Voraus kennen, gibt es eine sehr einfache Lösung, die für mich funktioniert hat. Wirf es einfach in eine Zeichenfolge (beide folgenden tun dasselbe):

$data['discount'] = (string) $data['discount'];
$data['discount'] = '' . $data['discount'];

Für meinen Anwendungsfall war dies eine schnelle und effektive Lösung. Beachten Sie nur, dass dies bedeutet, dass es beim Dekodieren aus JSON eine Zeichenfolge ist, da es in doppelte Anführungszeichen gesetzt wird.

texelate
quelle
4

Ich habe dieses Problem gelöst, indem ich sowohl precision als auch serialize_precision auf denselben Wert gesetzt habe (10):

ini_set('precision', 10);
ini_set('serialize_precision', 10);

Sie können dies auch in Ihrer php.ini einstellen

was auch immer_sa
quelle
das funktioniert mit php 7.4.9
michabbb
3

Ich hatte das gleiche Problem, aber nur serialize_precision = -1 hat das Problem nicht gelöst. Ich musste noch einen Schritt tun, um den Genauigkeitswert von 14 auf 17 zu aktualisieren (wie er in meiner PHP7.0-INI-Datei festgelegt wurde). Anscheinend ändert das Ändern des Werts dieser Zahl den Wert des berechneten Floats.

Alin Pop
quelle
3

Die anderen Lösungen haben bei mir nicht funktioniert. Folgendes musste ich zu Beginn meiner Codeausführung hinzufügen:

if (version_compare(phpversion(), '7.1', '>=')) {
    ini_set( 'precision', 17 );
    ini_set( 'serialize_precision', -1 );
}
Mike P. Sinn
quelle
Ist das nicht im Grunde die gleiche Antwort wie Alin Pop?
igorsantos07
1

Was mich betrifft, war das Problem, als JSON_NUMERIC_CHECK als zweites Argument von json_encode () übergeben wurde, das alle Zahlen in int (nicht nur integer) umwandelte.

Acuna
quelle
1

Speichern Sie es als Zeichenfolge mit der genauen Genauigkeit, die Sie benötigen number_format, und verwenden json_encodeSie dann die folgende JSON_NUMERIC_CHECKOption:

$foo = array('max' => number_format(472.185, 3, '.', ''));
print_r(json_encode($foo, JSON_NUMERIC_CHECK));

Du erhältst:

{"max": 472.185}

Beachten Sie, dass dadurch ALLE numerischen Zeichenfolgen in Ihrem Quellobjekt als Zahlen im resultierenden JSON codiert werden.

pasqal
quelle
1
Ich habe dies in PHP 7.3 getestet und es funktioniert nicht (Ausgabe hat immer noch zu hohe Präzision). Anscheinend ist das JSON_NUMERIC_CHECK-Flag seit PHP 7.1 gebrochen - php.net/manual/de/json.constants.php#123167
Philipp
0
$val1 = 5.5;
$val2 = (1.055 - 1) * 100;
$val3 = (float)(string) ((1.055 - 1) * 100);
var_dump(json_encode(['val1' => $val1, 'val2' => $val2, 'val3' => $val3]));
{
  "val1": 5.5,
  "val2": 5.499999999999994,
  "val3": 5.5
}
B. Asselin
quelle
0

Es scheint, dass das Problem auftritt, wenn serializeund serialize_precisionauf unterschiedliche Werte eingestellt sind. In meinem Fall 14 bzw. 17. Wenn Sie beide auf 14 setzen, wurde das Problem behoben, ebenso wie das Setzen serialize_precisionauf -1.

Der Standardwert von serialize_precision wurde ab PHP 7.1.0 auf -1 geändert, was bedeutet, dass "ein erweiterter Algorithmus zum Runden solcher Zahlen verwendet wird". Wenn dieses Problem jedoch weiterhin auftritt, kann dies daran liegen, dass eine PHP-Konfigurationsdatei aus einer früheren Version vorhanden ist. (Vielleicht haben Sie Ihre Konfigurationsdatei beim Upgrade beibehalten?)

Eine andere zu berücksichtigende Sache ist, ob es in Ihrem Fall sinnvoll ist, überhaupt Gleitkommawerte zu verwenden. Es kann sinnvoll sein, Zeichenfolgenwerte zu verwenden, die Ihre Zahlen enthalten, um sicherzustellen, dass die richtige Anzahl von Dezimalstellen in Ihrem JSON immer erhalten bleibt.

Code Commander
quelle
-1

Sie können [max] => 472.185 von einem Float in einen String ([max] => '472.185') vor dem json_encode () ändern. Da json ohnehin eine Zeichenfolge ist, wird durch Konvertieren Ihrer Gleitkommawerte in Zeichenfolgen vor json_encode () der gewünschte Wert beibehalten.

Everett Staley
quelle
Dies ist bis zu einem gewissen Grad technisch richtig, aber sehr ineffizient. Wenn ein Int / Float in einer JSON-Zeichenfolge nicht in Anführungszeichen gesetzt wird, kann Javascript es als tatsächliches Int / Float anzeigen. Wenn Sie Ihre Wiedergabe durchführen, müssen Sie jeden einzelnen Wert einmal auf der Browserseite in ein Int / Float zurücksetzen. Ich habe mich oft mit mehr als 10000 Werten befasst, als ich auf Anfrage an diesem Projekt gearbeitet habe. Am Ende wäre viel Aufblähungsverarbeitung passiert.
Gwi7d31
Wenn Sie JSON verwenden, um Daten irgendwohin zu senden, und eine Nummer erwartet wird, Sie jedoch eine Zeichenfolge senden, kann dies nicht garantiert funktionieren. In Situationen, in denen der Entwickler der sendenden Anwendung keine Kontrolle über die empfangende Anwendung hat, ist dies keine Lösung.
Osullic