感謝 @地獄星星:
緣由已找到, 該現象只出如今PHP 7.1+版本上
建議使用默認值 serialize_precision = -1 便可
參考: https://wiki.php.net/rfc/prec...
----------------------------------------------------------------------------------php
事情是這樣的,項目裏發現一個奇怪的現象,json_encode一個帶浮點價格的數據, 出現溢出, 好比:git
<?php echo json_encode(277.2); // 輸出結果爲: 277.199999999999989
這明顯是不能接受的, 數據雖然很接近, 但畢竟已經變動了
下意識地認爲這是php的一個bug, 不能準確地json序列化一個浮點小數
這個問題google了半天居然也無果, 而後我跟同事說, 大家json_encode數據裏有小數的時候, 記得先number_format()轉化成字符串.json
本身又寫了個fix方法, 遍歷數據, 把is_float()的數據都用number_format()轉化了app
function json_encode_pre($d, $depth=128, $level=0){ if($level>$depth) return $d; if(is_array($d)){ foreach ($d as $i => $v) { $d[$i] = json_encode_pre($v, $depth, $level+1); } return $d; } if(is_float($d)){ # 測試發現number_format有效數字低於18(保守取16)時,不會溢出 $p = 16 - strlen(intval($d)); $f = number_format($d, $p); if($p>1){ $f = preg_replace('/0+$/','', $d); } return $d; } return $d; } echo number_format(277.2, 14); // 當18位有效數字處理(277.2已有4位有效數字) // 277.199999999999989 echo number_format(277.2, 12); // 當16位 // 277.2000000000000 echo json_encode(json_encode_pre(277.2)); // "277.2"
看到這結果時, 便猜想是有效數字位數的問題致使了溢出, PHP怎麼會有這麼蠢的設計呢?這是我當時的想法.測試
而後想着是否是能從源碼下手, 因而查了下php源碼, 發現這段代碼:google
static inline void php_json_encode_double(smart_str *buf, double d, int options) /* {{{ */ { size_t len; char num[PHP_DOUBLE_MAX_LENGTH]; php_gcvt(d, (int)PG(serialize_precision), '.', 'e', num); len = strlen(num); if (options & PHP_JSON_PRESERVE_ZERO_FRACTION && strchr(num, '.') == NULL && len < PHP_DOUBLE_MAX_LENGTH - 2) { num[len++] = '.'; num[len++] = '0'; num[len] = '\0'; } smart_str_appendl(buf, num, len); }
是的, 居然發現了配置項serialize_precision, 這個配置我當初的理解是serialize()方法用的, 而我當初嘗試修改過配置項precision, 然並卵, 原來json_encode會用到serialize_precision, 因而修改php.ini, 把它設爲16, 原來是17仍是18忘了..net
<?php echo json_encode(277.2); // 輸出結果爲: 277.2
若是你想重現個人問題, 很簡單, 把serialize_precision設爲20或更大, 至於這個有效數字最大值是多少纔不會溢出, 我還不知道跟什麼有關係, 但願能有大神指點了.
我是經過改配置數值, 出現溢出的值再減去2來用的.設計
附上配置項說明, 從官方說明上看, 很難想像是跟json_encode是有關係的.code
; php.ini ; 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 = 16 ; The number of significant digits displayed in floating point numbers. ; http://php.net/precision precision = 16
另外源碼裏有個json_encode的選項JSON_PRESERVE_ZERO_FRACTION, 這個的意思是若是是個是個整數, 是否保留小數點和0, 來看測試結果:orm
<?php echo json_encode(277.0); // 277 echo json_encode(277.0, JSON_PRESERVE_ZERO_FRACTION); // 277.0