文档章节

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

苗雨顺
 苗雨顺
发布于 2011/01/27 16:36
字数 1150
阅读 2645
收藏 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 7 推迟发布,暂定12月3号发正式版

诶, 相信很多人已经在微博上看到了, 但是呢, 还是要说一声: 给大家带来一个不好不坏的消息, 那就是, PHP7, 那啥, 就是, 怎么说呢, 怪不好意思的, 但是呢, 事实上呢, 一定来说呢, 是这样的, 它...

oschina
2015/11/25
11.6K
132
PHP 一种访问私有属性的方法

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

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

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

蝶衣人生
2014/04/21
192
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
程序员的最大噩梦是什么?

有国外程序员在 Quora 上提了一个问题:“程序员的最大噩梦是什么?”。本文摘编了这个问答贴的多个热门回复。 Brain Schmitz Software Engineer Intern at Google (2012, 2013), Microsoft...

oschina
2014/12/08
9K
61

没有更多内容

加载失败,请刷新页面

加载更多

idea下springboot 项目在static目录下添加文件不生效

idea下springboot 项目在static目录下添加文件不生效 问题描述 是这样子的,我的项目目录结构如下: 我在static目录下,创建了index.html和aaaa.jpg这两个文件。然后,启动服务访问 http://l...

wotrd
昨天
5
0
k8s1.14 一、环境

1. 4台虚拟机 (CentOS Linux release 7.2.1511 (Core) ) 192.168.130.211 master 192.168.130.212 node1 192.168.130.213 node2 192.168.130.214 node3 2. 设置服务器hostname 2.1 设置本机......

ThomasCheng
昨天
3
0
盖茨:如果我现在开创一家公司 将会专注于AI

新浪科技讯,北京时间 6 月 26 日凌晨消息,微软联合创始人比尔·盖茨(Bill Gates)在周一接受采访时表示,如果他今天从哈佛大学辍学并开创一家新公司,那么这家公司将会专注于人工智能(A...

linuxCool
昨天
1
0
聊聊feign的Retryer

序 本文主要研究一下feign的Retryer Retryer feign-core-10.2.3-sources.jar!/feign/Retryer.java public interface Retryer extends Cloneable { /** * if retry is permitted, retur......

go4it
昨天
10
0
HyperLogLog简介

  (1)HyperLogLog简介      在Redis 在 2.8.9 版本才添加了 HyperLogLog,HyperLogLog算法是用于基数统计的算法,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个...

SEOwhywhy
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部