浮点数的相等比较

来源:互联网 发布:b站翻唱软件 编辑:程序博客网 时间:2024/04/25 11:30

今天群里边一哥们儿——哦,确切地说,应该是一姊妹——问到了Python中怎样判断一个浮点数的小数部分是不是0,本意是要找和C语言中的fmod函数相同功能的函数的,在Python中在math模块中有这个函数,可是却挑起了一个浮点数精度的话题。

突然想到偶们学习C语言的时候老师讲到过,整数的相等比较可以直接使用==来判断,但是浮点数的比较不能简单的用==来比较。那怎么对浮点数是否相等进行判断呢?

遂Google了一番,又找到了那个经典的方法:利用差值的绝对值的精度来判断。

具体就是:f1和f2是两个浮点数,precision是我们自己设置的精度,比如1e-6。

则可以用 fabs(f1-f2)<=precision 来判断f1和f2是否相等。

如果要求更高的精度,则把precision定得更小就行了。

 

 

网上有一位大牛讲,这个方法还存在问题:

“首先,precision是一个绝对的数据,也就是误差分析当中所说的绝对误差,使用一个固定的数值,对于float类型可以表达的整个数域来说是不可以的。比如precision取值为0.0001,二f1和f2的数值大小也是0.0001附近的,那么显然不合适。另外对于f1和f2大小是10000这样的数据的时候,它也不合适,因为10000和10001也可以真伪是相等的呢。适合它的情况只是f1或者f2在1或者0附近的时候。”

 

这位大牛给出了这样的解决方法:

既然绝对误差不可以,那么自然的我们就会想到了相对误差

bool IsEqual(float a, float b, float relError ) {

       return ( fabs ( (a-b)/a ) < relError ) ? true : false;

}

这样写还不完善,因为是拿固定的第一个参数做比较的,那么在调用

IsEqual(a, b, relError ) 和 IsEqual(b, a, relError ) 的时候,可能得到不同的结果

同时如果第一个参数是0的话,就有可能是除0溢出

这个可以改造

把除数选取为a和b当中绝对数值较大的即可

bool IsEqual(float a, float b, relError )

{

      if (fabs(a)<fabs(b)) return  ( fabs((a-b)/a) > relError ) ? true : false;

      return  (fabs( (a-b)/b) > relError ) ? true : false;

};

 

使用相对误差就很完善吗?

也不是, 在某些特殊情况下, 相对误差也不能代表全部

比如在判断空间三点是否共线的时候,使用判断点到另外两个点形成的线段的距离的方法的时候

只用相对误差是不够的,应为线段距离可能很段,也可能很长,点到线段的距离,以及线段的长度做综合比较的时候,需要相对误差和绝对误差结合的方式才可以

相对完整的比较算法应该如下:

bool IsEqual(float a, float b, float absError, float relError )

{

         if (a==b) return true;

        if (fabs(a-b)<absError ) return true;

        if (fabs(a>b) return  (fabs((a-b)/a>relError ) ? true : false;

       return  (fabs((a-b)/b>relError ) ? true : false;

}

这样才相对完整。

 

然后这位大牛又给出了一个“最后的比较算法”:

(虽然文章较长,为了不改变原文的叙述,在这里将其按照原文录出)

我们先看正数的情况

根据IEEE的内存结构, 指数在高位,尾数在低位

浮点数大的对应的把其内存结构按照整数来理解进行比较的时候,情况也是成立的

因此在这里如果把他们进行比较的话,作为整数运算效率会非常的高,比如

float f1 = 1.23;

float f2 = 1.24

f1 > f2 成立

(int&)f1 > (int&)f2 也是成立的

 

而且,仔细研究IEEE的浮点结构,可以发现在《浮点数比较》当中提到的浮点数精度的问题——不是所有的浮点数都可以精确的表达

可以精确表达的浮点数实际上是有限的,就是那些IEEE的各种情况的枚举了 2^32个。不能表达的占据了大多数

 

IEEE在32位的情况下,尾数是23位的(暗含了第一个位数是1)

对于可以精确表达的浮点数来说,如果我们把这23位当作整数来理解, 它加1,就意味着可以找到比当前对应浮点数大的最小的浮点数了

反之,我们把两个浮点数,对应的整数做差值运算,得到的整数表明的是两个浮点数之间有多少个实际可以表达的浮点数(对应的指数相同的情况下很好理解;指数不同的时候,也是同样有效的)

 

这样,对于两个正的浮点数,他们的大小比较就可以用 (int&)f1 - (int&)f2 来进行比较了

差值的结果实际上就应该是相对误差了

这个相对误差,不等同于普遍意义上的相对误差

它所表达的是,两个浮点数之间可能还有多少个可以精确表达的浮点数

这样通过指定这个阈值来控制两个浮点数的比较就更有效了

对于两个正的浮点数

bool IsEqual(float f1, float f2, int absDelta)

{

     if ( abs ( (int&)f1 - (int&)f2 ) < absDelta ) return true;

}

这里用abs而不是fabs这在asm上面的运算差距也是很大的了

 

对于两个负数进行比较的情况也是相同的

只不过负数内存对应的整数加1,相应的找到的是更小的负数而已

 

但是负数和整数之间现在还不能进行直接的比较,因为根据IEEE的内存结构

正数和负数是不同的,对应的整数不能连续

正的最小的数就是0了,对应的整数也是0x00000000

负的最小的数就是-0,对应的整数则是0x 80000000

不用奇怪-0

在IEEE的表达当中是有两个0的,一个是 +0  一个是-0

 

有趣的是,按照 f1 == f2 的判断  +0和-0是相等的

 


通过对比我们可以发现,

+0 和正的浮点数可以按照转换成为整数的方式直接进行比较

-0 和负的浮点数可以按照转换成为整数的方式直接进行比较

 

如果我们能够把他们连接起来,整个整数方式的直接比较就完备了

对比一下负数的结构, 可以找到一个简单的办法了:

        把负数内存对应的整数减去 -0 ,他们就连续了

而且更好的结果是,所有的负数经过这次减法后,对应的整数也都是负数了

这样整个整数比较就变得连续了,而且在整个浮点数范围内都是有效的了

 

最后的比较算法就是:

//  函数:   bool IsEqual(float f1, float f2, int absDelta)

//  功能:把比较两个浮点数是否近似相同

//  输入:f1, f2参与比较的两个浮点数

//               absDelta 两个浮点数之间允许有多少个其他可以精确表达的浮点数存在,相当于相对误差

//  输出:   true,两个浮点数进行相等; false 两个浮点数不等

//  注意:仅仅适合IEEE 32位浮点数结构

bool IsEqual(float f1, float f2, int absDelta)

{

       int i1, i2;

       i1 = ( f1>0)  ? ((int&)f1)  : ( (int&) f1 - 0x80000000 );

       i2 = (f2>0)  ? ((int&)f2)  : ( (int&) f2 - 0x80000000 );

       return   ((abs(i1-i2))<absDelta) ? true : false;

}


这位大牛的讲解我暂时还没有弄懂,暂且先录在这里,以后在慢慢读。别人看是,也清晰明了。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/sharkw/archive/2007/12/14/1937581.aspx