php踩过的那些坑(5)浮点数计算

1、前方有坑php

php在使用加减乘除等运算符计算浮点数的时候,常常会出现意想不到的结果,特别是关于财务数据方面的计算,给很多工程师惹了不少的麻烦。好比今天工做终于到的一个案例:算法

$a = 2586;函数

$b = 2585.98;测试

var_dump($a-$b);网站

指望的结果是:float(0.02)orm

实际结果:blog

float(0.019999999999982)内存

php踩过的那些坑(1)浮点数计算

人生有坑,到处提防字符串

2、防坑攻略:数学

一、经过乘100的方式转化为整数加减,而后在除以100转化回来……

二、使用number_format转化成字符串,而后在使用(float)强转回来……

三、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官方网站

3、为啥有坑:

php的bug?不是,这是全部语言基本上都会遇到的问题,因此基本上大部分语言都提供了精准计算的类库或函数库。

要搞明白这个缘由, 首先咱们要知道浮点数的表示(IEEE 754):

浮点数, 以64位的长度(双精度)为例, 会采用1位符号位(E), 11指数位(Q), 52位尾数(M)表示(一共64位).

符号位:最高位表示数据的正负,0表示正数,1表示负数。

指数位:表示数据以2为底的幂,指数采用偏移码表示

尾数:表示数据小数点后的有效数字.

这里的关键点就在于, 小数在二进制的表示, 小数如何转化为二进制呢?

算法是乘以2直到没有了小数为止。这里举个例子,0.9表示成二进制数

0.9*2=1.8 取整数部分 1

0.8(1.8的小数部分)*2=1.6 取整数部分 1

0.6*2=1.2 取整数部分 1

0.2*2=0.4 取整数部分 0

0.4*2=0.8 取整数部分 0

0.8*2=1.6 取整数部分 1

0.6*2=1.2 取整数部分 0

.........

0.9二进制表示为(从上往下): 1100100100100......

注意:上面的计算过程循环了,也就是说*2永远不可能消灭小数部分,这样算法将无限下去。很显然,小数的二进制表示有时是不可能精确的 。其实道理很简单,十进制系统中能不能准确表示出1/3呢?一样二进制系统也没法准确表示1/10。这也就解释了为何浮点型减法出现了"减不尽"的精度丢失问题。

换句话说:咱们看到十进制小数,在计算机内存储的不是一个精确的数字,也不可能精确。因此在数字加减乘除后出现意想不到的结果。

4、防坑提示

基于以上缘由,因此永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。若是确实须要更高的精度,应该使用任意精度数学函数或者 gmp 函数

相关文章
相关标签/搜索