環境:PHP 7.3.12
問題:$json_arr = ["aa" => 0.1]; echo json_encode($json_arr, JSON_UNESCAPED_UNICODE);輸出結果
{"aa":0.10000000000000001}
浮點數 0.1 轉成 JSON,變成 0.10000000000000001
以前沒問題的程式,突然發現有這情況。
原來 PHP 從 7.1 開始,json_encode() 改使用 serialize_precision 的設定值處理小數精度。
而我的環境從 PHP 5.6 慢慢升級到 PHP 7.3.12,設定檔還是沿用舊版的。
舊版使用 precision 設定值處理 json_encode() 的浮點數精度。EG(precision)
7.1版後使用 serialize_precision 設定值處理 json_encode() 的浮點數精度。PG(serialize_precision)
舊版 PHP,php.ini 中 precision = 14,serialize_precision = 17
; The number of significant digits displayed in floating point numbers. ; http://php.net/precision precision = 14 ; When floats & doubles are serialized store serialize_precision significant ; digits after the floating point. The default value ensures that when floats ; are decoded with unserialize, the data will remain the same. serialize_precision = 17
7.1版後,php.ini 中 precision = 14,serialize_precision = -1
; The number of significant digits displayed in floating point numbers. ; http://php.net/precision precision = 14 ; When floats & doubles are serialized, store serialize_precision significant ; digits after the floating point. The default value ensures that when floats ; are decoded with unserialize, the data will remain the same. ; The value is also used for json_encode when encoding double values. ; If -1 is used, then dtoa mode 0 is used which automatically select the best ; precision. serialize_precision = -1
據 https://bugs.php.net/bug.php?id=74221 上說
[2017-03-07 23:06 UTC] nikic@php.net You can restore the previous behavior by setting serialize_precision to the value of precision, which is 14 by default. Alternatively and preferably, you can set serialize_precision to -1. In this case PHP will automatically determine the minimal precision necessary to still accurately represent the number.
解決方法一:將 serialize_precision 設為 14,也就是舊版 json_encode() 處理使用的 precision = 14 這個值
ini_set('serialize_precision', 14); $json_arr = ["aa" => 0.1]; echo json_encode($json_arr, JSON_UNESCAPED_UNICODE);輸出結果
{"aa":0.1}
解決方法二:將 serialize_precision 設為 -1,也就是新版的預設值。
ini_set('serialize_precision', -1); $json_arr = ["aa" => 0.1]; echo json_encode($json_arr, JSON_UNESCAPED_UNICODE);輸出結果
{"aa":0.1}
precision、serialize_precision 都可設定為 -1,官網說明是會使用較佳的算法。
https://www.php.net/manual/en/ini.core.php#ini.precision
precision int The number of significant digits displayed in floating point numbers. -1 means that an enhanced algorithm for rounding such numbers will be used.https://www.php.net/manual/en/ini.core.php#ini.serialize-precision
serialize_precision int The number of significant digits stored while serializing floating point numbers. -1 means that an enhanced algorithm for rounding such numbers will be used.https://wiki.php.net/rfc/precise_float_value
This RFC proposes to introduce a new setting EG(precision)=-1 and PG(serialize_precision)=-1 that uses zend_dtoa()'s mode 0 which uses better algorigthm for rounding float numbers (-1 is used to indicate 0 mode). The RFC also proposes changing ini for JSON precision to PG(serialize_precision).
結論:
設成新版預設值 serialize_precision = -1,應該是最佳的解法。
如果不放心,怕又遇到非預期情況,且可接受使用字串形式,也可先將浮點數轉成字串再處理。
$json_arr = ["aa" => (string) 0.1]; echo json_encode($json_arr, JSON_UNESCAPED_UNICODE);輸出結果
{"aa":"0.1"}
參考:
- https://www.php.net/manual/en/ini.core.php
PHP: Description of core php.ini directives - Manual - https://bugs.php.net/bug.php?id=74221
PHP :: Bug #74221 :: Float precision is altered when encoding to JSON for floats 0.99... and under - https://stackoverflow.com/questions/42981409/php7-1-json-encode-float-issue
php - PHP7.1 json_encode() Float Issue - Stack Overflow - https://wiki.php.net/rfc/precise_float_value
PHP: rfc:precise_float_value - https://www.jianshu.com/p/44d0855da415
2019-11-13 PHP7.x json_encode() Float Issue - 简书
沒有留言:
張貼留言