文档章节

PHP浮点运算中一个不算BUG的BUG

苗雨顺
 苗雨顺
发布于 2011/01/27 16:36
字数 1150
阅读 2.7K
收藏 4

对于这个问题的发现源于一个计时器程序,先看下这段代码吧。

<?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位的。

但出现这种现象的根本原因在哪?

所以我去网上逛了逛,看了些东西后,得到了一些启发。所以又写了一段测试代码:

<?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);

float(0.3);

not equals

大家是不是感觉很奇怪,两个变量都是浮点型的,都是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编写了一段类似的浮点数比较的代码,结果也是不相等的。实际上在所有的语言里都会存在此问题,因为这是计算机原理所决定的。但也不排除一些语言做了些后期处理,可以直接比较两个浮点数。

以上仅是个人理解,不保证绝对正确,有错误还请多多谅解。

© 著作权归作者所有

苗雨顺
粉丝 15
博文 31
码字总数 12536
作品 0
东城
程序员
私信 提问
加载中

评论(1)

xiaomanong
xiaomanong
写得不错。转走了。
PHP 一种访问私有属性的方法

在鸟哥《深入理解PHP原理之对象(一)》看到一段挺有意思的代码 <?phpclass Foo { }$foo = new Foo();$arr = (array) $foo;vardump($arr["0Foo0name"]);vardump($arr["0*0age"]);//output:str......

solu
2014/05/10
500
1
@有失效的情况

我今天在回复帖子的时候先直接打了“@红薯”不能自动解析,然后使用修改的方式在红薯后面加了一个空格还是解析不了。 BUG应该是修改你没有去解析,这个算不算BUG啊。

蝶衣人生
2014/04/21
203
6
纵使有花兼明月何堪无酒亦无人/xukey

xukey base on ukey 支持PHP7 请选择PHP7分支,修复内存泄露的bug Based on the Twitter Snowflake algorithm PHP unique ID generator functions list: string ukeynextid(void); Get the n......

纵使有花兼明月何堪无酒亦无人
2016/05/05
0
0
[译] X 为啥不是 hook?

原文地址:Why Isn’t X a Hook? 原文作者:Dan Abramov 译文出自:掘金翻译计划 本文永久链接:github.com/xitu/gold-m… 译者:Jerry-FD 校对者:yoyoyohamapi, CoolRice 由读者翻译的版本...

掘金翻译计划
2019/02/20
0
0
JFinal中多文件上传的一个问题

@JFinal您好,JFinal多文件上传中,用Enumeration files = multipartRequest.getFileNames();来获取多个文件添加到uploadFiles中去,这个在多个input上传的情况下没有问题,但是当用到HTML5属...

justPlay8
2013/04/25
4.6K
3

没有更多内容

加载失败,请刷新页面

加载更多

Kettle自定义jar包供javascript使用

我们都知道 Kettle 是用 Java 语言开发,并且可以在 JavaScript 里面直接调用 java 类方法。所以有些时候,我们可以自定义一些方法,来供 JavaScript 使用。 本篇文章有参考自:https://www...

CREATE_17
昨天
82
0
处理CSV文件中的逗号

我正在寻找有关如何处理正在创建的csv文件的建议,然后由我们的客户上传,并且该值可能带有逗号(例如公司名称)。 我们正在研究的一些想法是:带引号的标识符(值“,”值“,”等)或使用|...

javail
昨天
79
0
如何克隆一个Date对象?

将Date变量分配给另一个变量会将引用复制到同一实例。 这意味着更改一个将更改另一个。 如何实际克隆或复制Date实例? #1楼 简化版: Date.prototype.clone = function () { return new ...

技术盛宴
昨天
73
0
计算一个数的数位之和

计算一个数的数位之和 例如:128 :1+2+8 = 11 public int numSum(int num) { int sum = 0; do { sum += num % 10; } while ((num = num / 10) > 0); return sum;......

SongAlone
昨天
124
0
为什么图片反复压缩后普遍会变绿,而不是其他颜色?

作者:Lion Yang 链接:https://www.zhihu.com/question/29355920/answer/119088684 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 业余版概要:安卓的...

shzwork
昨天
71
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部