php json_encode() 中数字精度丢失问题

今天遇到微信H5支付出现异常,而且只有一个商品出现异常,其他商品都可以正常调起微信H5支付,这个就是比较奇怪了,所以去到代码里去寻找问题。

初步发现是,这个商品无法成功获取到微信H5的支付链接。

打印这个$headers,发现报如下错误:提示需要一个合法的64位有符号整数

仔细看下,发现value的值出现问题了,本来是整数的,这里成了浮点数,明显出现了精度丢失的情况。

再看代码,发现原本是整数值的$payparm参数,经过json_encode($payparm)后,出现了精度丢失的问题。

这里的解决办法是,将$payparm中的整数参数,使用intval()再强制转换一下。例如 $value = intval($value);

随后又搜索了下 json_encode()  中数字精度丢失问题,发现有许多不错的解决办法,这里搬来保存一下。

 

目录
1.前言
2.案例演示
3.解决方法
3.1方法1:强行转换成字符串保证精度
3.2方法2:格式化数字number_format函数
3.3方法3:修改配置项serialize_precision
3.4知识点补充
3.5其他取整函数
4.bcsub()函数精度相减
1.前言
最近在工作中,后台出现了这样的错误,传递的手续费是29块钱,但是传递到了第三方接口就报错了,查看日志显示该手续费变成了28.999999999999996,日志如下:

明明是29怎么就变了?通过断点检测,原来是json_encode()转换的问题

2.案例演示
$param={

‘amount’=>5000,

‘fee’=>29

}

var_dump(json_encode($param));//{"amount":5000,"fee":28.999999999999999999996}
3.解决方法
3.1方法1:强行转换成字符串保证精度
$request['param']['feeAmount']=(string)$request['param']['feeAmount'];

注意:使用这种方法千万要注意,对接接口是否有变量类型要求

3.2方法2:格式化数字number_format函数
number_format(number,decimals,decimalpoint,separator)

参数:

number参数是要格式化的数据
decimals参数是保留的小数
decimalpoint参数是规定用作小数点的字符串
separator参数是规定用作千位分隔符的字符串
案例:
$request['param']['feeAmount']=(int)number_format($request['param']['feeAmount'],0);

注意:number_format返回的是字符串string,要注意接口是否有规范要求,如果有,则须强行转换为int或接口规范的类型

疑问1:这时候应该有人在想能不能直接强制转换为int呢?注意,float强制转换成int有坑!

答:int类型是向下取整的,比如:12910.9 会被转换为 12910

疑问2:这时就会有人问我,浮点数显示的是8,为什么转换成整数会变成7?

答:floor((0.1+0.7)*10),其结果是7而不是8,是因为该结果内部表示的是7.9999.....所以不要相信浮点数结果精确,也不要比较两个浮点数是否相等
3.3方法3:修改配置项serialize_precision
json_encode() 转换浮点小数溢出现象只出现在PHP 7.1+版本,是因为php源码对于json_encode()转换使用到了serialize_precision配置项,如下图

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);
}
关于PHP函数serialize_precision (integer)的一些概念了解:

适用范围:PHP_INI_ALL;

默认值:100

serialize_precision指令的数量决定了双打和彩车被序列化后的浮点数字存储。设置到一个合适的值,确保精度的数字时,可能丢失以后反序列化。所以我们要使得json_encode()转换浮点数没有小数溢出,建议使用默认值 serialize_precision = -1 即可 。

3.4知识点补充
json_encode有个选项JSON_PRESERVE_ZERO_FRACTION,表示如果是个整数, 是否保留小数点和尾数0,举例如下:

<?php
echo json_encode(223.0);// 223
echo json_encode(223.0, JSON_PRESERVE_ZERO_FRACTION);// 223.0
3.5其他取整函数
四舍五入取整 round(param)
向上取整 ceil(param)
向下取整 floor(param)

4.bcsub()函数精度相减
格式:

string bcsub ( string $left_operand , string $right_operand [, int $scale = int ] )

说明:

2个任意精度数字的减法

参数:

left_operand:字符串类型的左操作数.

right_operand:字符串类型的右操作数.

scale:此可选参数用于设置结果中小数点后的小数位数。也可通过使用 bcscale() 来设置全局默认的小数位数,用于所有函数。

返回值:

返回减法之后结果为字符串类型.

代码案例:

<?php
$a  =  '1.234' ;
$b  =  '5' ;
echo  bcsub ( $a ,  $b );      // -3
echo  bcsub ( $a ,  $b ,  4 );   // -3.7660
————————————————
版权声明:本文为CSDN博主「拯救世界的派大星」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_46266407/article/details/105556444

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值