php在利用加减乘除等运算符打算浮点数的时候,常常会涌现意想不到的结果,特殊是关于财务数据方面的打算,给不少工程师惹了很多的麻烦。比如本日事情终于到的一个案例:
$a = 2586;
$b = 2585.98;

var_dump($a-$b);
期望的结果是:float(0.02)
实际结果:
float(0.019999999999982)
人生有坑,处处戒备
二、防坑攻略:
1、通过乘100的办法转化为整数加减,然后在除以100转化回来……
2、利用number_format转化成字符串,然后在利用(float)强转回来……
3、php供应了高精度打算的函数库,实际上便是为理解决这个浮点数打算问题而生的。
紧张函数有:
bcadd — 将两个高精度数字相加
bccomp — 比较两个高精度数字,返回-1, 0, 1
bcdiv — 将两个高精度数字相除
bcmod — 求高精度数字余数
bcmul — 将两个高精度数字相乘
bcpow — 求高精度数字乘方
bcpowmod — 求高精度数字乘方求模,数论里非常常用
bcscale — 配置默认小数点位数,相称于便是Linux bc中的”scale=”
bcsqrt — 求高精度数字平方根
bcsub — 将两个高精度数字相减
前两种泼皮的办法就不测试了,利用bcsub测试第三种两数相减的例子,
先看bcsub用法(来自官网)
string bcsub ( string $left_operand , string $right_operand [, int $scale = int ] )
参数
left_operand 字符串类型的左操作数.
right_operand 字符串类型的右操作数.
scale 此可选参数用于设置结果中小数点后的小数位数。也可通过利用 bcscale() 来设置全局默认的小数位数,用于所有函数。
返回值 返回减法之后结果为字符串类型.
测试代码:
var_dump(bcsub($a,$b,2));
结果
0.02
其他的函数请参考PHP官方网站
三、为啥有坑:
php的bug?不是,这是所有措辞基本上都会碰着的问题,以是基本上大部分措辞都供应了精准打算的类库或函数库。
要搞明白这个缘故原由, 首先我们要知道浮点数的表示(IEEE 754):
浮点数, 以64位的长度(双精度)为例, 会采取1位符号位(E), 11指数位(Q), 52位尾数(M)表示(一共64位).
符号位:最高位表示数据的正负,0表示正数,1表示负数。
指数位:表示数据以2为底的幂,指数采取偏移码表示
尾数:表示数据小数点后的有效数字.
这里的关键点就在于, 小数在二进制的表示, 小数如何转化为二进制呢?
算法是乘以2直到没有了小数为止。这里举个例子,0.9表示成二进制数
0.92=1.8 取整数部分 1
0.8(1.8的小数部分)2=1.6 取整数部分 1
0.62=1.2 取整数部分 1
0.22=0.4 取整数部分 0
0.42=0.8 取整数部分 0
0.82=1.6 取整数部分 1
0.62=1.2 取整数部分 0
.........
0.9二进制表示为(从上往下): 1100100100100......
把稳:上面的打算过程循环了,也便是说2永久不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。实在道理很大略,十进制系统中能不能准确表示出1/3呢?同样二进制系统也无法准确表示1/10。这也就阐明了为什么浮点型减法涌现了\公众减不尽\公众的精度丢失问题。
换句话说:我们看到十进制小数,在打算机内存储的不是一个精确的数字,也不可能精确。以是在数字加减乘除后涌现意想不到的结果。
四、防坑提示
基于以上缘故原由,以是永久不要相信浮点数结果精确到了末了一位,也永久不要比较两个浮点数是否相等。如果确实须要更高的精度,该当利用任意精度数学函数或者 gmp 函数