最近在项目中遇到一个问题,测试服务器接口返回金额精度缺失,导致app解析不正确发生闪退。调试后发现浮点数在json_encode后损失了精度,如下图所示。
网上说是因为php7.1版本的serialize_precision配置不当导致,但是查看配置文件后发现配置正确,默认值为-1。
在服务器上测试浮点数运算如下:
测试后发现浮点数的加减运算会导致精度损失。
我们都知道计算机是通过二进制数存储浮点数的一个近似值,浮点数之间的比较和计算难免有误差。
单精度 float 的存储方式如下:
双精度 double 的存储方式如下:
php7.1版本可以通过设置serialize_precision和precision两个配置项来指定浮点数精度,但是也不能保证满足项目对于浮点数运算的严格要求。
解决方案是将项目中的金额单位从元(浮点数,保留两位小时)转成分(整型)运算,最后除以100避免了浮点数加减运算导致的精度损失。
示例代码如下:
/**
* 元转分
*
* @param $amount
* @return mixed
*/
public static function yuanToFen($amount)
{
return $amount * 100;
}
/**
* 分转元
*
* @param $amount
* @return float
*/
public static function fenToYuan($amount)
{
return $amount / 100;
}
/**
* 元相加运算,不损失精度
*
* @param $yuanAmounts
* @return int
*/
public static function yuanAdd(...$yuanAmounts)
{
$fenAmountTotal = 0;
foreach ($yuanAmounts as $yuanAmount) {
$fenAmountTotal += intval(self::yuanToFen(round($yuanAmount, 2)));
}
return self::fenToYuan($fenAmountTotal);
}
由于本地安装php7.1版本测试未发现相同现象,仔细对比服务器上配置后发现是由于precision配置项被改成-1所致。
综上,虽然这个问题主要原因是随意修改配置,且测试服务器php版本比生产环境的7.0版本高,但是生产环境也出现过浮点数精度导致的bug。因此,如果项目对浮点数精度要求严格,尤其是金融项目,建议将浮点数转成整型存储和运算,也有利于数据库存储查询的性能,减少不必要的困扰。