对于这个问题的发现源于一个计时器程序,先看下这段代码吧。php
<?php $s=gettime(); usleep(10000); $e=gettime(); var_dump($s); var_dump($e); echo $e-$s."\n"; function gettime(){ list($sec,$usec)=explode(" ",microtime()); return $sec+$usec; } ?>
输出结果为:算法
float(1296098836.2033)
float(1296098836.2135)
0.010126113891602函数
有没有发现一个很诡异的现象?被减数和减数整数部分是相同的,小数点后都只是四位,可是这两个数相减以后得出的结果,小数点后的位数却要大于4。其实想解释也不难,由于被减数和减数的精度都为14,因此为不形成精度损失,因此结果的精度也必须是14位的。测试
但出现这种现象的根本缘由在哪?code
因此我去网上逛了逛,看了些东西后,获得了一些启发。因此又写了一段测试代码:ci
<?php ini_set('precision',14); $a=0.3; $b=0.1+0.2; var_dump($a); var_dump($b); $a==$b?print "equals\n":print "not equals\n"; ?>
输出的结果是:字符串
float(0.3);get
float(0.3);io
not equalsfunction
你们是否是感受很奇怪,两个变量都是浮点型的,都是0.3,为何不相等呢?
使用序列化函数serialize查看一下两个数的实际值:
<?php echo serialze(0.3),"\n"; echo serialize(0.1+0.2),"\n"; ?>
输出结果:
d:0.299999999999999988897769753748434595763683319091796875;
d:0.3000000000000000444089209850062616169452667236328125;
你会发现这两个数实际上都不是真正的0.3,为何这样呢?
其实这个问题要追溯到微机原理(也有多是计算机组成原理,记不清楚是哪一本书了),这里面讲了计算机是如何用二进制来存储定点小数。大体是这样 的:若是用一个字节的长度来表示一个定点小数,第一位表示小数的符号,0为正,1为负;后面7位表示小数的值,第2位至第8位的位权分别是 1/2,1/4,1/8,1/16,1/32,1/64,1/128,而后用这些权值的和来表示全部的小数。如何表示0.625呢?
这个有固定的算法:首先,将小数点左侧的整数部分变换为其二进制形式,处理小数部分的算法是将咱们的小数部分乘以基数 2,记录乘积结果的整数部分,接着将结果的小数部分继续乘以 2,并不断继续该过程。
0.625 x 2 = 1.25 1
0.25 x 2 = 0.5 0
0.5 x 2 = 1 1
当最后的结果为1时,结束这个过程。这时右侧的一列数字就是咱们所需的二进制小数部分,即 0.101。这样,咱们就获得了完整的二进制形式 0.101 ,按阶展开:(1*(1/2))+(0*(1/4))+(1*(1/8))=0.5+0.125=0.625。
咱们上面先的例子比较特殊,这个数只须要三次运算就可以结束。那会不会有没法结束的状况,不难想象,不少小数根本不能通过有限次这样的过程而获得结 果(好比最简单的 0.1)。但浮点数尾数域的位数是有限的,为此,浮点数的处理办法是持续该过程直到由此获得的尾数足以填满尾数域,以后对多余的位进行舍入。也就是说,十 进制到二进制的变换也并不能保证老是精确的,而只能是近似值。事实上,只有不多一部分十进制小数具备精确的二进制浮点数表达。再加上浮点数运算过程当中的误 差累积,结果是不少咱们看来很是简单的十进制运算在计算机上却每每出人意料。这就是最多见的浮点运算的"不许确"问题。
因此,在计算机里表示的浮点数只是一个近似的数,并非像表示整数那样精确没有误差。既然它只是一个约数,那么你用精确的==来比较两位不精确的约数就没有太大意义了。若是必定要比较两个浮点数,能够考虑先转换成字符串,而后再去比较。
我在Linux下使用c编写了一段相似的浮点数比较的代码,结果也是不相等的。实际上在全部的语言里都会存在此问题,由于这是计算机原理所决定的。但也不排除一些语言作了些后期处理,能够直接比较两个浮点数。
以上仅是我的理解,不保证绝对正确,有错误还请多多谅解。