先看一段程序代码,判断下输出会是什么:

1
2
3
4
5
6
7
8
<?php
    $a = 0.2; $b = 0.1;
    if (($a + $b) == 0.3) {
        echo '值为0.3';
    } else {
           echo '值不为0.3';
    }
?>

结果会输出什么?会输出 值为0.3 ?

错误!正确输出为 值不为0.3 !为什么?

其根本原因是计算机所使用二进制01代码无法准确表示某些带小数位的十进制数据。

下面,我们来分析下:

我们知道将一个十进制数值转换为二进制数值,需要通过下面的计算方法:

整数部分:连续用该整数除以2,取余数,然后商再除以2,直到商等于0为止。然后把得到的各个余数按相反的顺序排列。简称”除2取余法”。

小数部分:十进制小数转换为二进制小数,采用”乘2取整,顺序排列”法。用2乘以十进制小数,将得到的整数部分取出,再用2乘余下的小数部分,然后再将积 的整数部分取出,如此进行,直到积中的小数部分为0或者达到所要求的精度为止。然后把取出的整数部分按顺序排列起来,即先取出的整数部分作为二进制小数的 高位,后取出的整数部分作为低位有效位。简称”乘2取整法”。

按照上述方法,我们把0.1和0.2分别转换为其对应的二进制数据表示:

1
2
0.1 => (00011001100110011001100110011001...)2
0.2 => (00110011001100110011001100110011...)2

后面的省略号表示已经算不完了,后面在无限重复0011 这段二进制数值。

目前计算机上存储浮点数值是按照IEEE(电气和电子工程师协会)754浮点存储格式标准来存储的。

以下摘自查阅资料

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

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

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

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

假设数据类型为双精度浮点数类型,也只能存储52位,该小数的二进制代码已无法正确表示0.1及0.2,根据这个二进制代码肯定无法正确得到结果0.3。

就像鸟哥一篇博客说的那样:”你看似有穷的小数, 在计算机的二进制表示里却是无穷的”。所以尽量不要去比较浮点数值。

小结

关于浮点数值计算会产生误差的问题,这是使用基于IEEE754数值的浮点计算的通病,PHP并非独此一家,其他使用相同数据格式的语言也存在这个问题!