`
izuoyan
  • 浏览: 8934513 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

浮点数比较的问题

 
阅读更多

首先看一段代码(VC中,C++环境下):


请问是输出Yes呢还是输出No呢?
答案:
在控制台中经过几秒后输出No。
问题来了,debug过程中,查询“1.0 / a + 1.0 / 3 + 1.0 / c”的值的确为1.0000000000000。

那么为什么if判断中返回0呢?

经分析其实是精度的问题,1.0/3=0.333333333333……,1.0/c=0.16666666666666…….。如果精确相加当然为1.可是不要忘了对于计算机这个只能处理有限的离散数据的数字计算机来说,显然只能近似计算。

那么有没有什么办法处理浮点数相比较的问题呢?先学习一下,浮点数在计算机中的存储吧。
float与double的范围和精度
1. 范围
float和double的范围是由指数的位数来决定的。
float的指数位有8位,而double的指数位有11位,分布如下:
float:
1bit(符号位) 8bits(指数位) 23bits(尾数位)
double:
1bit(符号位) 11bits(指数位) 52bits(尾数位)
于是,float的指数范围为-127~+128,而double的指数范围为-1023~+1024,并且指数位是按补码的形式来划分的。
其中负指数决定了浮点数所能表达的绝对值最小的非零数;而正指数决定了浮点数所能表达的绝对值最大的数,也即决定了浮点数的取值范围。
float的范围为-2^128 ~ +2^128,也即-3.40E+38 ~ +3.40E+38;double的范围为-2^1024 ~ +2^1024,也即-1.79E+308 ~ +1.79E+308。

2. 精度
float和double的精度是由尾数的位数来决定的。浮点数在内存中是按科学计数法来存储的,其整数部分始终是一个隐含着的“1”,由于它是不变的,故不能对精度造成影响。
float:2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但绝对能保证的为6位,也即float的精度为6~7位有效数字;
double:2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位。

下面介绍如何解决浮点数的比较(以下内容来自网友,感觉很不错):

浮点数比较(一)

在数学运算当中经常会涉及到判断两个数是否相等的情况

对于整数很好处理 A==B这样的一个语句就可以解决全部的问题

但是对于浮点数是不同的

首先,浮点数在计算机当中的二进制表达方式就决定了大多数浮点数都是无法精确的表达的

现在的计算机大部分都是数字计算机,不是模拟机,数字机的离散化的数据表示方法自然无法精确表达大部分的数据量的。

其次计算机浮点数的精度在单精度float类型下,只有7位,在进行浮点运算的时候,这个精度往往会导致运算的结果和实际期望的结果之间有误差

因为前两个原因,我们很难用 A==B来判定两个浮点数是否相同

很自然,我们可以想到 fabs(A-B) < epsilon 这样的一种判别方法

但是这种判别方法稳妥吗?

它也不稳妥。

首先, epsilon是一个绝对的数据,也就是误差分析当中说说的绝对误差

使用一个固定的数值,对于float类型可以表达的整个数域来说是不可以的

比如epsilon取值为0.0001,而a和b的数值大小也是0.0001附近的,那么显然不合适

另外对于a和b大小是10000这样的数据的时候,它也不合适,因为10000和10001也可以认为是相等的呢

适合它的情况只是a或者b在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;

}

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics