浮点数的问题

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

浮点数及其比较问题
一、浮点数表示及精度问题
1 、浮点数
IEEE754定义了单精度浮点数和双精度数浮点数,即float和double。

float有32bit,double 有64bit。

它们都包括符号位、指数和尾数。

符号位指数尾数
float 31(1) 30-23(8) 22-0(23)
double 63(1) 62-52(11) 51-0(52)
符号位有1bit,0表示正、1表示负。

设一个数的指数是e,指数部分的值是bias+e。

加上一个bias是为了表示负数。

float的bias是127,double的bias是1023。

指数全0或全1有特殊含义,不算正常指数。

* float的指数部分有8bit,可以取值1~254,减掉127,得到对应的指数范围-126~127。

* double的指数部分有11位,可以取值1~2046,减掉1023,得到对应的指数范围-1022~1023。

这里的指数是以2为底的,同样尾数也是二进制的。

IEEE754要求浮点数以规范形式存储,即小数点前有1位非零数字。

对于二进制数,非零数字只有1。

所以IEEE754在存储时省略了这个小数点前面的1,只存储小数点后面的位。

2、误差
看个例子,设:
double a=0.2;
在PC上,我们可以看到a对应的存储区数据是:
9A 99 99 99 99 99 C9 3F
PC的数据是小尾的,即低位字节在后,将其写成高位字节在前,得到:
3F C9 99 99 99 99 99 9A
可见符号位为0。

指数位是0x3FC,即1020,减掉1023,得到指数-3。

尾数是999999999999A。

所以完整的数字就是16进制的1.999999999999A乘上2^-3。

即:a=(1+9*(1/16+1/16^2+...+1/16^12)+10/16^13)*2^-3
(1/16+...+1/16^12)可以用等比级数求和公式a1*(1-q^n)/(1-q)计算,其中a1=1/16,q=1/16,n=12,因此:
a=(1+9*(1-1/16^12)/15+10/16^13)*2^-3
用windows的计算器计算上式,得到
a=0.2000 0000 0000 0000 1110 2230 2462 5157
这也不是精确解,但已经可以看到用double表示0.2时存在的误差。

这个例子说明在用有限字长的二进制浮点数表示任意实数a可能引入误差。

设实数a的指数为e,尾数位数为n,显然:
误差<(1/2^n)*2^e
3、精度
可以把机器精度定义为满足条件:fl(1+ε)>1
的最小浮点数ε。

其中fl(1+ε)是1+ε的浮点表示。

显然double的机器精度是1/2^52。

float的机器精度是1/2^23。

浮点数的精度取决于尾数部分。

尾数部分的位数越多,能够表示的有效数字越多。

十进制数的计算公式为(n^m表示n的m次幂,B表示前面的数字是二进制):
S * 2^(E-127) * (1.M)B
单精度数的尾数用23位存储,加上默认的小数点前的1位1,2^(23+1) = 16777216。

因为10^7 < 16777216 < 10^8,所以说单精度浮点数的有效位数是7位。

双精度的尾数用52位存储,2^(52+1) = 9007199254740992,10^16 < 9007199254740992 < 10^17,所以双精度的有效位数是16位。

4、特殊的浮点数
前面提到浮点数的指数全0或全1有特殊含义,让我们来看看这些特殊的浮点数:* 指数和尾数都是全0表示0。

根据符号位不同可以分为+0和-0。

* 指数全0,尾数不为全0,这些数是非规范数,即尾数部分不假设前面存在小数点前的1。

或者说这些数太接近0了,因为指数已经不能再小,所以这些数不能写成规范形式。

例如:double数0000 0000 0000 0001的尾数是0 0000 0000 0001,即1/2^52,对应的数是1/(2^52)*2^-1022,即4.9406564584124654e-324。

* 指数全1,尾数全0表示无穷大,即inf。

根据符号位不同可以分为+inf和-inf。

* 指数全1,尾数不为全0表示NaN,即Not a Number,不是数。

尾数最高位为1的NaN被称作QNaN(Quiet NaN)。

尾数最高位为0的NaN被称作SNaN(Signalling NaN)。

通常用QNaN表示不确定的操作,用SNaN表示无效的操作。

在计算机内部,double就是一个64位数。

从0x0000 0000 0000 0000~0xFFFF FFFF FFFF FFFF,每个64位数都对应一个浮点数或NaN。

double内部表示的设计是很有规律的,按照对应64位数的顺序依次为+0、正非规范数、正规范数、正无穷大、符号位为正的NaN、-0、负非规范数、负规范数、负无穷大、符号位为负的NaN。

5、结束语
float和int都是32bit,但float的尾数只用了23bit。

int的精度高于float,float 的表示范围大于int。

float牺牲精度换取了更大的表示范围。

double的尾数是52bit,高于32bit的int,所以用dobule表示int不会有精度损失。

double是科学计算的常用类型,了解double 的内在和限制,有助于我们更好地使用它。

二、浮点数比较问题
1、直接进行关系比较的错误(==)
浮点数可以进行比较,但是由于不同浮点类型中表示精度的差异,所以会引起一些错误。

例1 :
#include <iostream.h>
void main()
{
float f1=7.123456789;
float f2=7.123456787;
cout<<(f1!=f2?”not same\n”:”same\n”);
float g=1.0/3.0;
double d=1.0/3.0;
cout<<(g = =d?”same\n”:”not same\n”);
}
运行结果:
same //f1与f2相同
not same //g与d不同
f1与f2的前6位有效数位相等而后面的数不同,在计算机中可能被表示为同一个数。

因float型精度有限,不能分辨其差异,造成判断有误(认为是相同的数)。

解决的方法是使用double型数据。

例2:
#include <iostream.h>
#include <math.h>
#include <iomanip.h>
void main()
{
double d1=123456789.9*9;
double d2=1111111109.1;
cout<<(d1==d2?" same\n":"not same\n");
cout<< ((fabs(d1-d2)<1e-6)?"same\n":"not same\n");
cout<<setprecision(9);
cout<<setiosflags(ios::fixed)<<d1<<"\n"<<d2<<"\n";
}
运行结果:
not same
same
1111111109.100000143
1111111109.099999905
2、使用浮点数进行相等(==)和不相等(!=)比较的操作通常是有问题的。

浮点数的相等比较,一般用两者相减的值是否落在0的邻域中来判断。

3、测试一个浮点值与零的关系也存在误差。

在林锐的<<高质量C/C++编程>>中提到:不可将浮点变量用“==”或“!=”与任何数字比较。

原因是有精度限制。

应该转换成以下形式
if ((x>=-EPSINON) && (x<=EPSINON))
其中EPSINON是允许的误差(即精度)。

浮点数与0的比较:
之所以要这样if(fabs(i)<=1e-6),是因为对于一个数,在数学上,只要这个数小于一个任意小的正数,我们就可以说这个数等于零。

这是与极限相关的一个概念。

如果想深入了解为什么,可以看看数学分析。

所以,在计算机编程时,我们之所以用这样的方法来比较实型,是因为这样可以任意达到所需的精度,也就是说fabs(i)可以小于更大或者更小的一个数来达到确认是否等于零的目的。

浮点数与零的比较:
等于0关系:fabs(i)<=1e-6
大于0关系:i>1e-6
小于0关系:i<1e-6
例:求一元二次方程的解
●a=0 不是二次方程,是一次方程
●b2-4ac=0 有两个相等实根
●b2-4ac>0 有两个不等实根
●b2-4ac<0 有两个共轭复根
#include <iostream.h>
#include <math.h>
#include <iomanip.h>
void main( )
{
double a,b,c,disc,x1,x2,realpart,imagpart;
cout<<"请输入a,b,c:";
cin>>a>>b>>c;
if(fabs(a)<=1e-6)
cout<<"不是2次方程, 是1次方程\n";
else
{
disc=b*b-4*a*c;
if(fabs(disc)<=1e-6)
cout<<"有两个相等的实根:"<<(-b/(2*a));
else if(disc>1e-6)
{
x1=(-b+sqrt(disc))/(2*a);
x2=(-b-sqrt(disc))/(2*a);
cout<<"有两个不等的实根:\n";
cout<<setprecision(5)<<"x1="<<x1<<endl;
cout<<setprecision(5)<<"x2="<<x2<<endl;
}
else
{
realpart=-b/(2*a);
imagpart=sqrt(-disc)/(2*a);
cout<<"有两个复根:\n";
cout<<setprecision(5)<<realpart<<"+"<<imagpart<<"i"<<endl;
cout<<setprecision(5)<<realpart<<"-"<<imagpart<<"i"<<endl;
}
}
}。

相关文档
最新文档