PHP7.1 json_encode()フロートの問題


98

これはもっと注意が必要なので、問題ではありません。json_encode()PHP7.1.1を使用するアプリケーションを更新しましたが、フロートが17桁に拡張されることがあるという問題が発生していました。ドキュメントによると、PHP 7.1.xは、serialize_precisiondouble値をエンコードするときに精度の代わりに使用し始めました。私はこれが例の値を引き起こしたと推測しています

472.185

になる

472.18500000000006

その値が通過した後json_encode()。発見してから、PHP 7.0.16に戻り、json_encode()。の問題は発生しなくなりました。また、PHP 7.0.16に戻す前に、PHP7.1.2に更新しようとしました。

この質問の背後にある理由は、PHP-浮動小数点精度に由来しますが、これのすべての理由は、の精度からserialize_precisionの使用法への変更によるものjson_encode()です。

誰かがこの問題の解決策を知っているなら、私は推論/修正について聞いて喜んでいます。

多次元配列からの抜粋(前):

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

                    )

そして通過した後json_encode()...

"staticYaxisInfo":
            {
                "17":
                {
                    "variable_id": "17",
                    "static": "1",
                    "min": 0,
                    "max": 472.18500000000006,
                    "locked_static": "1"
                }
            },

6
ini_set('serialize_precision', 14); ini_set('precision', 14);おそらく以前のようにシリアル化するでしょうが、フロートの特定の精度に本当に依存している場合は、何か間違ったことをしています。
apokryfos 2017年

1
「誰かがこの問題の解決策を知っているなら」 -どのような問題ですか?ここでは問題は見当たりません。PHPを使用してJSONをデコードすると、エンコードした値が返されます。また、別の言語を使用してデコードすると、おそらく同じ値が得られます。いずれにせよ、値を12桁で印刷すると、元の(「正しい」)値が返されます。アプリケーションで使用されるフロートに12桁を超える精度が必要ですか?
axiac 2017年

12
@axiac 472.185!= 472.18500000000006。明確な前後の違いがあります。これはブラウザへのAJAXリクエストの一部であり、値は元の状態のままである必要があります。
gwi7d31 2017年

4
最終製品はHighchartsであり、文字列を受け入れないため、文字列変換の使用を避けようとしています。float値を取得し、それを文字列としてキャストして送信し、次にparseFloat()を使用してjavascriptに文字列をfloatに解釈させると、非常に非効率的でずさんなものになると思います。ね?
gwi7d31 2017年

1
@axiacあなたがPHPであることに注意してくださいjson_decode()は元のfloat値を戻します。ただし、javascriptがJSON文字列をオブジェクトに戻す場合、潜在的にほのめかしたように値を472.185に戻すことはありません...したがって問題が発生します。私は自分が行っていることに固執します。
gwi7d31 2017年

回答:


101

これは、私が最終的にこのRFCを指すこのバグを見つけるまで、私を少し狂わせました。

現在json_encode()、14に設定されているEG(精度)を使用しています。つまり、数値の表示(印刷)には最大14桁が使用されます。IEEE 754 doubleは、より高い精度をサポートし、serialize()/ var_export()PG(serialize_precision)を使用します。PG(serialize_precision)は、より正確になるようにデフォルトで17に設定されています。json_encode()EG(precision)を使用しているためjson_encode()、PHPのfloatがより正確なfloat値を保持できる場合でも、小数部の下位桁を削除し、元の値を破棄します。

そして(私の強調)

このRFCは、フロート数の丸めに優れたアルゴリズムを使用するzend_dtoa()のモード0を使用する新しい設定EG(precision)=-1およびPG(serialize_precision)=-1の導入を提案しています(-1は0モードを示すために使用されます)

つまり、PHP7.1json_encodeに新しく改良された精度エンジンを使用させる新しい方法があります。ではphp.iniのあなたは変更する必要があるserialize_precision

serialize_precision = -1

このコマンドラインで動作することを確認できます

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

あなたは得る必要があります

{"price":45.99}

G(precision)=-1そしてPG(serialize_precision)=-1 PHP 5.4にも使用することができます
kittygirl

1
に注意してくださいserialize_precision = -1。-1と、このコードecho json_encode([528.56 * 100]);プリント[52855.99999999999]
vl.lapikov

3
@ vl.lapikovしかし、それは一般的な浮動小数点エラーのように聞こえます。ここだデモあなたがそれを明確にだけではありません見ることができ、json_encode問題
Machavity

41

プラグイン開発者として、私はサーバーのphp.ini設定への一般的なアクセス権を持っていません。そこで、Machavityの回答に基づいて、PHPスクリプトで使用できるこの小さなコードを作成しました。スクリプトの上に置くだけで、json_encodeは通常どおり機能し続けます。

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

場合によっては、もう1つの変数を設定する必要があります。最初のソリューションが機能することが証明されているすべての場合に2番目のソリューションが正常に機能するかどうかわからないため、これを2番目のソリューションとして追加します。

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

3
プラグインが残りの開発者アプリケーションの予期しない設定を変更する可能性があるため、注意してください。しかし、IMO、このオプションがどれほど破壊的になるかはわかりません... lol
igorsantos07 2018年

精度値の変更(2番目の例)は、他の数学演算に大きな影響を与える可能性があることに注意してください。php.net/manual/en/ini.core.php#ini.precision
リカルド・マルティンス

@RicardoMartins:ドキュメントによると、デフォルトの精度は14です。上記の修正により、これは17に増加します。したがって、さらに正確になるはずです。同意しますか?
alev

@alev私が言っていたのは、serialize_precisionを変更するだけで十分であり、アプリケーションで発生する可能性のある他のPHPの動作を損なうことはないということです
RicardoMartins20年

6

精度とserialize_precisionの両方を同じ値(10)に設定することで、これを解決しました。

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

php.iniでこれを設定することもできます


4

私は金銭的価値を330.46エンコードしていて、にエンコードするようなものがありました330.4600000000000363797880709171295166015625。PHP設定を変更したくない、または変更できない場合で、データの構造を事前に知っている場合は、非常に簡単な解決策があります。それを文字列にキャストするだけです(以下はどちらも同じことをします):

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

私のユースケースでは、これは迅速で効果的な解決策でした。これは、JSONからデコードして戻すと、二重引用符で囲まれるため、文字列になることに注意してください。


3

同じ問題が発生しましたが、serialize_precision = -1だけでは問題は解決しませんでした。精度の値を14から17に更新するために、もう1つの手順を実行する必要がありました(PHP7.0 iniファイルで設定されているため)。明らかに、その数値の値を変更すると、計算された浮動小数点数の値が変更されます。


3

他の解決策は私にはうまくいきませんでした。コード実行の開始時に追加する必要があったものは次のとおりです。

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

これは基本的にアリンポップの答えと同じではありませんか?
igorsantos07 2018年

1

私の場合、問題は、json_encode()の2番目の引数としてJSON_NUMERIC_CHECKが渡され、すべての数値型をint(整数だけでなく)にキャストしたときでした。


1

を使用して必要な精度で文字列として保存しnumber_format、次にオプションjson_encodeを使用してJSON_NUMERIC_CHECK保存します。

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

あなたが得る:

{"max": 472.185}

これにより、ソースオブジェクト内のすべての数値文字列が、結果のJSON内の数値としてエンコードされることに注意してください。


1
これをPHP7.3でテストしましたが、機能しません(出力の精度が高すぎます)。-明らかにJSON_NUMERIC_CHECKフラグはPHP 7.1以降壊れてphp.net/manual/de/json.constants.php#123167
フィリップ

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
}

0

とを異なる値に設定するserializeと、問題が発生するようserialize_precisionです。私の場合、それぞれ14と17です。両方を14に設定serialize_precisionすると、-1に設定した場合と同様に、問題が解決しました。

serialize_precision PHP 7.1.0では、のデフォルト値が-1に変更されました。これは、「このような数値を丸めるための拡張アルゴリズムが使用される」ことを意味します。ただし、それでもこの問題が発生する場合は、以前のバージョンのPHP構成ファイルが配置されていることが原因である可能性があります。(たぶん、アップグレードしたときに設定ファイルを保持していましたか?)

考慮すべきもう1つのことは、あなたのケースでfloat値を使用することが理にかなっているのかどうかです。数値を含む文字列値を使用して、適切な小数点以下の桁数が常にJSONに保持されるようにすることは意味がある場合とない場合があります。


-1

json_encode()の前に、[max] => 472.185をfloatから文字列([max] => '472.185')に変更できます。とにかくjsonは文字列であるため、json_encode()の前にfloat値を文字列に変換すると、必要な値が維持されます。


これは技術的にはある程度真実ですが、非常に非効率的です。JSON文字列内のInt / Floatが引用符で囲まれていない場合、Javascriptはそれを実際のInt / Floatと見なすことができます。レンディションを実行すると、ブラウザ側ですべての値をInt / Floatにキャストし直す必要があります。リクエストごとにこのプロジェクトに取り組んでいるとき、私はしばしば10000以上の値を扱っていました。多くの膨満感の処理が起こっていただろう。
gwi7d 3119

JSONを使用してデータをどこかに送信していて、数値が期待されているが文字列を送信している場合、それが機能することは保証されません。送信アプリケーションの開発者が受信アプリケーションを制御できない状況では、これは解決策ではありません。
osullic
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.