情是这样的,项目里发现一个奇怪的现象,json_encode一个带浮点价格的数据, 出现溢出, 比如:
<?php
echo json_encode(277.2);
// 输出结果为: 277.199999999999989
这明显是不能接受的, 数据虽然很接近, 但毕竟已经变更了
下意识地认为这是php的一个bug, 不能准确地json序列化一个浮点小数
这个问题google了半天竟然也无果, 然后我跟同事说, 你们json_encode数据里有小数的时候, 记得先number_format()转化成字符串.
自己又写了个fix方法, 遍历数据, 把is_float()的数据都用number_format()转化了
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源码, 发现这段代码:
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忘了.