ACM于1990年宣布William M. Kahan在浮点数运算标准的制定上的贡献而获得图灵奖[1]。
--关于浮点数
极理想情况下,判断两个实数 a 和 b 相等,这里不妨将两个数分别定义为double型,直接使用 a == b 语句。众所周知,这样的代码是不能正常工作的,原因是,1.0000001 和 1.0 这两个数在绝大多数的情况下,认为它们是相等的,所以经常能看到如下的 C 或者 C++ 代码 if(fabs(a-b)<=eps_0) ,一般地,eps_0设置为很小的数,比如1.0e-6 或者 1.0e-7,直观地来讲,就是说当两个数之间的距离(称为绝对误差)很小很小的时候,我们就认为它们是相等的。
如果数字的数量级很大时,比如下面这两个数10000.0001和10000.000,显然使用上述的距离判断的结果两者不相等,不过,我们来看它们之间的相对误差为(1000.0001-10000.000) /10000.000=1.0e-9,那么这两者也应该认为是相等的,于是使用相对误差的想法,将代码改写为 if(fabs(a-b)<=eps*fabs(a)) [2] 。 相对和绝对的区别,就像前段时间朋友圈广泛流传的关于赚钱这件事情,别人的一个小目标是别人的小目标,每个人要根据自己的能力有自己的小目标(相对),如果把别人的小目标当作所有人的小目标(绝对),只是一句调侃而已,自然当不得真。
使用相对误差可以回避掉数字数量级较大的问题,不过,它对于数量级较小的问题解决起来效果不佳,考虑一种极端情况,比如被比较的两个数字分别为 a=0.0 和 b=0.00000001,这个时候这两个数字相差很小,认为相等,但是使用相对误差的代码时,由于右端项为零,而左端项大于零,显然等式不成立,即使用该方法判断两个数字并不相等。 比较完善的方式是将绝对误差和相对误差结合起来考虑,来设计C++代码:
bool isEqual(const double a, const double b) {
const eps_0 = 1.0e-6, rel_error = 10e-4;
bool isEqualFlag = FALSE;
if (fabs(a-b)<=eps_0) {
isEqualFlag = TRUE;
}
else {
if(fabs(a) >= 1000.0) {
if(fabs(a-b)<=rel_error*fabs(a)) {
isEqualFlag = TRUE;
}
}
}
return isEqualFlag;
}
这段函数貌似应该可以正常工作了,程序中的 rel_error 和 1000.0 可以根据实际需要进行修改,红色的标注部分是采用王珉老师的关于分情况讨论的方式,这样相较于不分情况讨论的方式来说,能够有效减少计算量,有兴趣的同学童鞋可以查看后面的回复。
注:读过 Kanan 的文章或者书,名字有印象,读的内容则完全木有印象了 :-(
参考资料:
[1] http://baike.baidu.com/link?url=dKEdlcVXDrqZPOwlYrdGJHIUj2R2o6FTrytdnEKegTdp8pAobEAgln8xafbg9aVeGrmX7ZZDzfH4EmpnmOLCYzDnlvuhooaZwN1Hr1V53gkTmvLeTCuHbZrd4DAOaYoMHoWy35OqHJ_iW42A1fT61q
[2] http://baike.baidu.com/link?url=zliuoxROiLTdcJ7gdz0smcHam_4wEUeOpbFJG_mvS7aicaZvHt37-w0GYSi83ApuCAAGi-O8-7NrStNdPMciN4ztQFvyYBlHrjVv8YIgZmEoK2gllb-3cjV9j9cSEC7E
[3] http://www.cnblogs.com/zourrou/archive/2011/05/08/2040712.html