Chinaunix首页 | 论坛 | 博客
  • 博客访问: 988581
  • 博文数量: 158
  • 博客积分: 4380
  • 博客等级: 上校
  • 技术积分: 2367
  • 用 户 组: 普通用户
  • 注册时间: 2006-09-21 10:45
文章分类

全部博文(158)

文章存档

2012年(158)

我的朋友

分类: C/C++

2012-11-20 10:29:50

[2006-01-23补充] (本应写在文章后面)
To  反对者:
假设计算机用浮点数 a_f 来表示实数 a_v,计算机用浮点数 b_f 来表示实数表示 b_v。
c_f 是计算机 a_f 和 b_f 相乘的结果,c_v 是实数 a_v 和  b_v 相乘的结果。
照你所说,就应该是 if(  c_f ==  c_v ± EPSILON  ),但请问这个EPSILON应该取值为多少?是0.1,还是1.0E-999?
假如还没有明白的话,我再问一个问题:用你这个EPSILON之后,即使你保证了应该相等的相等了①,但同时如何能保证不应该相等的决不相等②?比如你用 if( 0.3 *3 == 0.9±0.001 ) 确实可以输出“0.3*3等于0.9”,但也会输出“0.300001*3等于0.9”,小学生也知道后面一个是不成立的。但如果你把EPSILON写得非常小,使得“0.300001*3不等于0.9”,那么它又会输出“0.3*3不等于0.9”。总之,①和②是不可能同时成立的。
这只是问题之一,问题之二在于浮点数不是定点数,它和实数之间的差并不是一个固定值,何况这里面还包含运算过程中带来的累积差。

--------------------------------------------------------

[期待读者] 使用浮点数格式的语言的用户。(Fortran用户没有这个烦恼)
[前言] 本以为这是一个极其简单和基础的问题,但连续三天分别在三个不同的C/C++论坛遇到这个问题,令我不得不有发通牢骚的责任^_^。
[正文]
在浮点数上自作聪明的使用EPSILON的例子中,最出名的当属林锐博士的《高质量C++/C编程指南》,其开篇第一项就说“float x 与零值比较的 if 语句”写成“if( x == 0.0f )”是错的,应当写成“if( x>=-EPSILON && x<=EPSILON )”。
a. “if( x>=-EPSILON && x<=EPSILON )”是否是自作聪明的纂改了题意(题意是问是否等于0,而不是问是否接近0),先撇开不谈;
b. “if( x == 0.0f )”是否有错误(我不知道哪个标准说这句话错了;如果指的是逻辑,我告诉你这种用法也很平常),也先撇开不谈。
撇开a和b不谈,把问题简化为:if(a==b) 是否应当写成 if( fabs(a-b) < EPSILON ) 或类似的形式?

首先问为什么要把if(a==b)写成if( fabs(a-b) < EPSILON )?别人告诉我理由是浮点数不精确。
对于“不精确”要细细的描述,是什么不精确?是浮点数在计算机内部存储和计算不精确,还是浮点数不能精确的表示现实中的实数?
如果认为是前者的人,return "回学校重学 数字计算机 和 模拟计算机 那一章";
如果是后者,也就是不认为浮点数在计算机内部存储和计算不精确的人,既然如此,if(a==b) 和 if( fabs(a-b) < EPSILON ) 就同坐一条船,要么都对,要么都错。
还没明白^_^?因为 if( a-b < EPSILON ) 可以写成 if( a < b+EPSILON ),再把 b+EPSILON 用 c 替代就成了 if( a < c ),如果a不能和c用operator==,那么也就不能用<、>、<=、>=,道理是一样的。
(讲到这里就结束了,下面的事例是为不举例就听不懂的人准备的)

对于浮点数无法使用EPSILON的举证:
1。无法确定EPSILON,或者说唯一合理的EPSILON就是0.0。
假设把EPSILON定义为0.000001,OK!如果 a=0.0000001 请问 a 和 2a 等不等?用if( 2a-a < EPSILON )来计算它俩就是相等的,明显瞎扯。
有人说这很简单呀,只要把EPSILON定义得更小一点就可以了。错,因为总有比EPSILON更小的可能,所以EPSILON只有可能定义为0,if( fabs(a-b) < EPSILON )又等于if(a==b)了。
有人说不要那么吹毛求疵嘛,确定一个平衡些的EPSILON就行了。也错,如果你真能清楚自己当初为什么要把if(a==b)写成if( fabs(a-b) < EPSILON )的原因的话,就不会有这个蠢提议了,之所以要把if(a==b)写成if( fabs(a-b) < EPSILON )原意是因为浮点数是定长定有效位数但不定位的,所以EPSILON也应该是一个变数,而非可以用#define或const定义的常量。
还没明白^_^?回想《计算机基础》上的浮点数在实数轴上的分布点,是不是越接近±0.5-±1.0越密,其间隔是非定长的。
2。现实需求中不存在EPSILON
(因为说了太多次,所以也不说了)

[总结]:浮点数不是定点数,因为不是定点数,所以无法使用EPSILON;意图使用EPSILON来解决浮点数和实数不一致问题的人,其错误本质还是一模一样的,只是换了一种犯错误的形式而已。

[题外话之一]:如何解决浮点数和实数不一致的问题?这本身就属于设计错误,如果你需要的是实数的计算结果,那就不应该使用浮点数来计算。

[题外话之二]:网上也有很多使用EPSILON的算法,但
a. 其算法是固定的,参与计算的数值和结果的范围也是确定的,那么有可能(也只是有可能,并非一切算法都……)存在一个合理的EPSILON,如果它被验证过的话。
b. 如果不是a,那么就一定是从fortran那里照搬过来的,照搬的人不明白浮点数的特性。

[题外话之三]:林锐博士的《高质量C++/C编程指南》开篇第一项一共说了三点,除了上面已经讲过的float和零值比较之外,另外两点也都错误。
a. “请填写BOOL , float, 指针变量 与“零值”比较的 if 语句”
C/C++中没有BOOL这个字,C中的名_Bool,在stdbool.h中定义为bool;C++中的名bool。不知道他这个BOOL从哪里来的,并说成是《高质量C++/C编程指南》?
b. “请写出 char  *p 与“零值”比较的 if 语句。”
他的答案是 if( p == NULL ),我在一个论坛里说“如果是C++,那就应该写成 if( p == 0 ),写成NULL的人,Bjarne在《The C++ Programming Language》中为你们哭泣。”

阅读(6502) | 评论(36) | 转发(0) |
给主人留下些什么吧!~~

网友评论2012-11-20 10:34:58

周星星
这两个问题是否一致,你自己去想。

“那么,我们该怎么做?所以,才用范围代替。”
--- 没有因果关系吧。如果你问我“该怎么做?”,我得回答就是:
如果你一定要判断是否相等,那就只能使用无限精度的实数。
但如果你不需要无限精度,而又要判断是否相等,那么你应该先计算出(10.0+135.3)/10-14.53浮点数运算的结果,假设为 RESULT,然后写成if( (10.0+135.3)/10-14.53 == RESULT )。
浮点数和实数计算是有差别的,差别不是一个范围。

网友评论2012-11-20 10:34:51

周星星
“限于阅历,我真的不清楚要求判断(10.0+135.3)/10-14.53是否等于0有什么问题”
--- 问题在于计算机的存储是有限的,而实数是无限的。
a. 范围的无限。(这个与本题无关。)
b. 精度的无限。假设你用一亿位(足够了吧?!)来存储一个浮点数,那么有个 vender2 的就会对你说“限于阅历,我真的不清楚要求判断0.0……(一亿位)05*2是否等于0.0……(一亿位)1有什么问题”。
c. 进制转化的无限。能在有限位数内用10进制表示的数,用2进制可能就是一个无限位数,比如0.3。

网友评论2012-11-20 10:34:42

清风雨
看了部分,后面就不想细看下去了。因为以前也见你在论坛发表过一番感想。
这个问题,恐怕星星有所偏颇。
bool r1 = 0.0001 == 0.0001;
bool r2 = 0.0001f == 0.0001;
这两个表达式是并没有问题,只是,不加注意,或者通过运算,不是这么显示的表达,我们通常会认为r1和r2都是true的,而事实上r2是false(就你的32为PC而言)。
而通常,往往遇到的是隐式的,类似if( m == 0.0f ) 这样的比较,不论m是float还是double的,显然,和我们主观的预期通常都是相违背的(因为你运算得来的m常常并不是精确为0的,而是近似0的,而我们并不会引起注意,尤其是对初学者)。那么,我们该怎么做?所以,才用范围代替。
可能是一些朋友的简短的解释,让你有了这个想法,但,不管怎么说,对浮点,请不要精确比较,该用精度范围。

网友评论2012-11-20 10:34:34

颜学铭
我认为float的比较应该在具体的上下文环境下讨论才有意义。根据你的上下文环境对精度的要求而定,EPSILON应该有不同的取值。比如你的计算结果通常在百万级以上,那么假设误差为0.1左右,其相对误差也很小。又如果你的计算结果本身就在0.0-1.0之间,那么你误差0.1其相对误差就很大了。
EPSILON也不是取值越小越好,如果小到你能表示的精度以下,其比较结果也就无意义了。
举个例子来说,假定你有一台GPS采集仪,能够测量经度和纬度,其精确度以秒计,秒以下就不精确了,那么你认为读数E103度5分30.5秒和E103度5分30.9秒到底哪一个更精确?因为秒以下就不精确了,所以我们认为N103度5分30秒是这个地点的准确值。因此我们认为两次测定的是同一个经度位置。

网友评论2012-11-20 10:34:25

颜学铭
细节决定成败!
越是细微之处,越要谨小慎微,程序员在日常编码中越是细微之处越应该知其然而后知其所以然。君不见bug常隐藏于细节之中。一家之言。