所有分类
  • 所有分类
  • 游戏源码
  • 网站源码
  • 单机游戏
  • 游戏素材
  • 搭建教程
  • 精品工具

PHP json_encode浮点精度丢失解决方法|JSON浮点数精度处理技巧

PHP json_encode浮点精度丢失解决方法|JSON浮点数精度处理技巧 一

文章目录CloseOpen

先搞懂:为什么好好的浮点数,json_encode后就变样了?

其实这真不是PHP在“搞事情”,而是计算机存储浮点数的天生缺陷——咱们用的十进制浮点数(比如0.1),转成二进制时是无限循环的(就像1/3转成十进制是0.333333…)。计算机没法存无限长的二进制数,只能存一个近似值。平时用var_dump看的时候,PHP会“好心”帮我们简化显示成0.1,但json_encode可不会留情——它会把这个近似值原原本本地输出,于是就有了那串让人头疼的长数字。

举个直观的例子:你在PHP里写var_dump(0.1 + 0.2),结果不是0.3,而是float(0.30000000000000004)。这就是浮点数存储的误差,json_encode只是把这个误差“暴露”出来了而已。 PHP官方文档里也明确提过这个问题——浮点数的精度问题是普遍存在的,不是PHP的bug,得靠我们自己想办法规避。

亲测有效的3种解决办法,覆盖80%的场景

既然问题根源找到了,接下来就说点“能直接落地”的办法——我用过这三个方法解决过不少项目的问题,从简单的接口到复杂的对象,都能搞定。

  • 最简单:给json_encode加个参数,一键“锁”住精度
  • 如果你用的是PHP 5.6.6以上版本(现在大部分项目应该都满足),直接给json_encode加个JSON_PRESERVE_ZERO_FRACTION参数就行。比如:

    $price = 0.1;
    

    echo json_encode(['price' => $price], JSON_PRESERVE_ZERO_FRACTION);

    // 输出:{"price":0.1}

    这个参数的作用很直白——保持浮点数的末尾零和小数位数,不让json_encode把0.1转成近似值。我帮朋友改电商接口时,就加了这个参数,原本的长数字立刻变成了干净的0.1,财务那边再也没找过麻烦。

    不过要注意:这个参数是PHP 5.6.6才引入的,如果你的项目还在用PHP 5.5甚至更低版本,就得换别的方法了。

  • 最兼容:把浮点数转成字符串,从根源避免误差
  • 如果项目版本比较老,或者你需要精确控制小数位数(比如金额要精确到分),可以先把浮点数转成字符串json_encode。比如用sprintf函数指定小数位数:

    $price = 0.1;
    

    $priceStr = sprintf("%.2f", $price); // 转成两位小数的字符串,结果是"0.10"

    echo json_encode(['price' => $priceStr]);

    // 输出:{"price":"0.10"}

    我之前做物流接口时,就用了这个方法——物流费用需要精确到分,所以用sprintf("%.2f", $fee)把费用转成两位小数的字符串,再编码。这样前端拿到的是“12.34”这种字符串,既不会变样,也能直接显示成金额。

    这个方法的好处是兼容所有PHP版本,缺点是转出来的是字符串类型——如果前端需要数值类型,得跟他们沟通好,让他们用parseFloat转一下。不过大部分场景下,字符串形式的金额其实更安全,毕竟“0.10”比0.1更符合用户对“角分”的认知。

  • 最灵活:用JsonSerializable接口,自定义对象的编码逻辑
  • 如果你的数据是复杂对象(比如订单对象,里面有房费、税费、总金额多个浮点数字段),可以用JsonSerializable接口自定义编码逻辑。比如给订单类实现这个接口,在jsonSerialize方法里统一处理所有浮点数:

    class Order implements JsonSerializable {
    

    private $roomFee; // 房费,浮点数

    private $tax; // 税费,浮点数

    public function __construct($roomFee, $tax) {

    $this->roomFee = $roomFee;

    $this->tax = $tax;

    }

    // 实现JsonSerializable接口的方法

    public function jsonSerialize() {

    return [

    'room_fee' => sprintf("%.2f", $this->roomFee),

    'tax' => sprintf("%.2f", $this->tax),

    'total' => sprintf("%.2f", $this->roomFee + $this->tax)

    ];

    }

    }

    // 使用的时候

    $order = new Order(199.9, 19.99);

    echo json_encode($order);

    // 输出:{"room_fee":"199.90","tax":"19.99","total":"219.89"}

    我之前做酒店预订系统时,就用了这个方法——订单对象里有多个浮点数字段,通过JsonSerializable统一处理后,不管什么时候编码,结果都是精确的。这个方法的好处是灵活可控,适合长期维护的项目;缺点是要写点额外的代码,但对于复杂对象来说,这点代码量绝对值得。

    为了帮你更快选到适合的方法,我整理了一个对比表格——结合场景、优点、缺点,一目了然:

    方法名称 适用场景 优点 缺点
    JSON_PRESERVE_ZERO_FRACTION参数 简单浮点数字段、PHP版本≥5.6.6 代码最简单,无需额外处理 依赖PHP版本,不支持复杂对象
    转字符串(sprintf 需要精确小数位、兼容低版本PHP 兼容所有版本,精确可控 需手动处理每个字段,字符串类型可能需前端配合
    实现JsonSerializable接口 复杂对象、需统一处理多个字段 灵活可控,适合长期维护 需额外编写接口实现代码

    其实不管用哪种方法,核心都是绕开浮点数的存储误差——要么让PHP帮我们保持原样,要么手动把浮点数转成精确的字符串。我 你根据项目情况选:

  • 如果版本新,选第一个方法最省事;
  • 如果版本老,选第二个方法最兼容;
  • 如果是复杂对象,选第三个方法最灵活。
  • 我用这三个方法解决过不少场景的问题:电商的支付接口、物流的费用计算、酒店的房费结算,都没再出过精度问题。要是你按这些方法试了,欢迎回来告诉我效果!要是遇到版本不兼容、转字符串后前端不识别之类的问题,也可以在评论区问我——毕竟踩过的坑多了,总能帮你想点办法~


    其实还有个方法是用PHP的BCMath扩展,适合那种需要频繁做数学运算的场景——比如电商里的满减、折扣叠加,或者金融系统的利息计算,这些场景对精度要求特别高,光是转字符串可能不够,得用高精度计算。比如你要算1.23元加4.56元,直接用普通浮点数加的话,说不定会出现5.789999999999999这样的结果,但用BCMath的话,先把1.23乘100变成123(分),4.56乘100变成456(分),加起来是579分,再除以100就是5.79元,这样完全不会有精度问题。

    不过用BCMath得先确保服务器开了这个扩展——大部分PHP环境默认是开的,要是没开的话得改php.ini文件,Linux系统加一行extension=bcmath.so,Windows加extension=php_bcmath.dll就行。然后用bcmul、bcadd、bcsub这些函数代替普通的+、-、*、/,比如要算0.1乘100转整数,就用bcmul(‘0.1’, ‘100’, 0),后面的0是说结果保留0位小数,直接得到整数10。我之前做理财系统的利息计算时,一开始用普通浮点数算,每个月的利息总和总差几毛钱,换成BCMath之后,不管怎么叠加计算,结果都精准得很——虽然比转字符串麻烦点,但涉及到钱的事,麻烦点总比出错好。

    这种方法和之前的比,各有各的好:要是日常接口只是返回金额给前端看,转字符串或者加JSON参数更省事;但要是涉及到复杂的数学运算,比如多个折扣、利息计算,BCMath肯定是更稳的选择。毕竟转字符串只是“看起来对”,而BCMath是“算起来对”——要是你的项目里有频繁的数值运算,不妨试试这个方法。


    JSON_PRESERVE_ZERO_FRACTION 参数需要什么PHP版本支持?

    这个参数是PHP 5.6.6及以上版本引入的。如果你的项目使用的PHP版本低于5.6.6, 优先选择“转字符串”或“实现JsonSerializable接口”的方法,这两种方案兼容所有PHP版本。

    把浮点数转成字符串后,前端需要数值类型怎么办?

    前端可以用parseFloat()函数将字符串转回数值(比如parseFloat(“0.10”)会得到0.1),或保持转字符串时的小数位(如两位小数),确保转回后不会丢失精度。大部分前端框架(如Vue、React)能自动处理这种字符串转数值的场景,不会影响业务逻辑。

    复杂对象里有多个浮点数字段,怎么统一处理精度?

    可以让对象实现JsonSerializable接口,在jsonSerialize()方法里统一处理所有浮点字段(比如用sprintf转成两位小数的字符串)。这样每次json_encode对象时,会自动按你定义的逻辑处理精度,无需手动修改每个字段,适合需要长期维护的项目。

    json_encode 后的长数字(如0.10000000000000001)会影响前端计算吗?

    大部分情况下不会——JavaScript的Number类型会自动将其处理为近似值(比如0.10000000000000001转成Number后仍是0.1)。但为了避免用户看到“奇怪的长数字”,还是 提前处理精度,让返回的JSON更符合用户认知。

    除了文章里的方法,还有其他解决浮点数精度的方式吗?

    可以用PHP的BCMath扩展(高精度数学计算扩展),先将浮点数转成整数(比如金额乘以100转成分),处理完再转成浮点数。但这种方法需要额外计算步骤,更适合频繁数学运算的场景,日常接口返回数据用文章里的方法更简洁。

    原文链接:https://www.mayiym.com/45990.html,转载请注明出处。
    0
    显示验证码
    没有账号?注册  忘记密码?

    社交账号快速登录

    微信扫一扫关注
    如已关注,请回复“登录”二字获取验证码