概要
php浮点数精度计算的坑
$a = 2586;
$b = 2585.98;
var_dump($a-$b);
期望的结果是:float(0.02)
实际结果:
float(0.019999999999982)
原因
小数的二进制表示有时是不可能精确的。
其实道理很简单,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。
这也就解释了为什么浮点型减法出现了"减不尽"的精度丢失问题。
方案
1、通过乘100的方式转化为整数加减,然后在除以100转化回来……
2、使用number_format转化成字符串,然后在使用(float)强转回来……
3、php提供了高精度计算的函数库,实际上就是为了解决这个浮点数计算问题而生的。
php提供的高精度函数库bc
1.运行条件
运行bcsub函数需要 php-bcmath的扩展。(一般来说,在7版本以后都已经是自带的了)
2.
BC的主要相关函数
bcadd — 将两个高精度数字相加
bccomp — 比较两个高精度数字,返回-1, 0, 1
bcdiv — 将两个高精度数字相除
bcmod — 求高精度数字余数
bcmul — 将两个高精度数字相乘
bcpow — 求高精度数字乘方
bcpowmod — 求高精度数字乘方求模,数论里非常常用
bcscale — 配置默认小数点位数,相当于就是Linux bc中的”scale=”
bcsqrt — 求高精度数字平方根
bcsub — 将两个高精度数字相减
可能为缺陷
1.直接截取的数字
2.不遵循四舍五入原则
3.返回值类型是字符串
为何需要封装?
当然是因为上面的缺陷啦
当然也是为了更人性化的使用
代码示例
示例
//只能实现基本的加减乘除
if (!function_exists('hlg_math')) {
/**
* 高精度数字计算封装
* @param int $left
* @param string $symbol
* @param int $right
* @param int $default
* @return string|null
* @author: baiwei
* @Time: 2020/7/17 0012 10:51
*/
function hlg_math($left = 0, $symbol = '+', $right = 0, $default = 2)
{
//设置全局保留的位数
bcscale($default);
switch ($symbol) {
case '+':
$res = bcadd(floatval($left), floatval($right));
break;
case '-':
$res = bcsub(floatval($left), floatval($right));
break;
case '*':
$res = bcmul(floatval($left), floatval($right));
break;
case '/':
$res = bcdiv(floatval($left), floatval($right));
break;
case '%':
$res = bcmod(floatval($left), floatval($right));
break;
default:
$res = 0;
break;
}
// 直接返回浮点类型
return floatval($res);
}
}
关于舍去以及四舍五入
1.鲁迅曾经说过:对于浮点数的计算没有绝对的精确
2.谁说的舍去就一定是缺陷?
在遵照鲁迅名言的情况下,没有绝对的精准时,舍去保留若干位和四舍五入保留若干位其实都为等阶的不准确
例如:
1. 如果是卖家对用户发放金额的情况下
是不是卖家会更倾向于舍去算法?
因为毕竟是舍去,这*至少不亏
2. 如果是需要配合微信支付的相关手续费接口
微信手续费的计算甚至于有千分之六的概念,
那么就一定浮点数会超过3位,
他们采用的就是四舍五入的方式保留了2位
如果要BC支持四舍五入
在设置全局保留N位的情况下隐式保留N+1位,然后对参数进行round四舍五入保留N位置即可解决不能四舍五入的难点
关于封装
一般来说计算都需要三个参数,左右数以及运算符,这里看看大家有没有更好的封装建议