最近在项目中遇到一个问题,测试服务器接口返回金额精度缺失,导致app解析不正确发生闪退。调试后发现浮点数在json_encode后损失了精度,如下图所示。

CPU浮点运算数 i7浮点运算_浮点数

网上说是因为php7.1版本的serialize_precision配置不当导致,但是查看配置文件后发现配置正确,默认值为-1。

在服务器上测试浮点数运算如下:

CPU浮点运算数 i7浮点运算_php_02

测试后发现浮点数的加减运算会导致精度损失。

我们都知道计算机是通过二进制数存储浮点数的一个近似值,浮点数之间的比较和计算难免有误差。

单精度 float 的存储方式如下:

CPU浮点运算数 i7浮点运算_浮点数_03

双精度 double 的存储方式如下:

CPU浮点运算数 i7浮点运算_浮点数_04

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所致。

CPU浮点运算数 i7浮点运算_数据库_05

CPU浮点运算数 i7浮点运算_CPU浮点运算数_06

综上,虽然这个问题主要原因是随意修改配置,且测试服务器php版本比生产环境的7.0版本高,但是生产环境也出现过浮点数精度导致的bug。因此,如果项目对浮点数精度要求严格,尤其是金融项目,建议将浮点数转成整型存储和运算,也有利于数据库存储查询的性能,减少不必要的困扰。