分类: C/C++
2010-09-09 18:36:44
1)符号位(Sign):0代表正,1代表为负;
2)指数位(Exponent):用于存储科学计数法中的指数数据,并且采用移位存储(移码表示)(需要加偏置值Bias);
3)尾数部分(Mantissa):(原码表示)
类型float大小为4字节,即32位,内存中的存储方式如下:
高地址<-------------------------------------->低地址
| 符号位 | 指数 | 尾数 |
| 1 bit | 8 bit | 23 bit |
31<------>30<--------->22<---------------------->0
类型double大小为8字节,即64位,内存布局如下:
高地址<---------------------------------------->低地址
| 符号位 | 指数 | 尾数 |
| 1 bit | 11 bit | 52 bit |
63<------>62<------------>51<------------------>0
科学计数法存储数据:
如:
http://hi.baidu.com/lxsbupt/blog/item/55315b8b41623dd9fc1f1037.html
8.25用十进制的科学计数法表示就为:8.25*10^0,而120.5可以表示为:1.205*10^2。对于计算机来讲,就是二进制的科学计数法:
8.25用二进制表示可表示为1000.01,120.5用二进制表示为:1110110.1。用二进制的科学计数法表示1000.01可以表示为1.00001*2^3,1110110.1可以表示为1.1101101*2^6,任何一个数的科学计数法表示都为1.xxx*2^n, 尾数部分就可以表示为xxxx,第一位都是1,所以可以将小数点前面的1省略,故23bit的尾数部分,可以表示的精度却变成了24bit,道理就是在这里。那24bit能精确到小数点后几位呢,我们知道9的二进制表示为1001,所以4bit能精确十进制中的1位小数点,24bit就能使float能精确到小数点后6位。(为什么float与0比较,是与-0.000001到0.000001比较了)
对于指数部分比较特殊:
http://dev.firnow.com/course/3_program/c++/cppsl/2008626/128485.html
根据偏置指数e的值,被编码的浮点数可分成三种类型。
(1)规格化数
当有效数字M在范围1≤M<2中且指数e的位模式ek-1…e1e0既不全是0也不全是1时,浮点格式所表示的数都属于规格化数。这种情况中小数f(0≤f<1 ) 的二进制表示为0. fn-1…f1f0。有效数字M=1+f,即M=1. fn-1…f1f0 (其中小数点左侧的数值位称为前导有效位) 。我们总是能调整指数E,使得有效数字M在范围1≤M<2中,这样有效数字的前导有效位总是1,因此该位不需显示表示出来,只需通过指数隐式给出。
需要特别指出的是指数E要加上一个偏置值Bias,转换成无符号的偏置指数e,也就是说指数E要以移码的形式在存放计算机中。且e、E和Bias三者的对应关系为e=E+Bias,其中Bias=2k-1-1。
(2)非规格化数
当指数e的位模式ek-1…e1e0全为零(即e=0)时,浮点格式所表示的数是非规格化数。这种情况下,E=1-Bais,有效数字M=f=0. fn-1…f1f0 ,有效数字的前导有效位为0(0≤M<1)。
非规格化数的引入有两个目的。其一是它提供了一种表示数值0的方法,其二是它可用来表示那些非常接近于0.0的数。
(3)特殊数
当指数e的位模式ek-1…e1e0全为1时,小数f的位模式fn-1…f1f0全为0(即f=0)时,该浮点格式所表示的值表示无穷,s=0 时是+∞,s=1时是-∞。
当指数e的位模式ek-1…e1e0全为1时,小数f的位模式fn-1…f1f0不为0(fn-1、…、f1、f0、至少有一个非零即f≠0)时,该浮点格式所表示的值被称为NaN(Not a Number)。比如当计算 或∞-∞时用作返回值,或者用于表示未初始化的数据。
不同的类型偏置值(Bias)不一样。如下图:
由于偏置值的作用,实际的二进制科学计数的幂要加上127(单精度)才为实际存储的值(好多文章将这指数的存储解释为有符号数是不太准确的)。本来8位的指数位可以表示0-255,除掉非规格数和特殊数就是1-254,那么实际可以表示的指数范围就是-126~128。
所以:
下面就看看8.25和120.5在内存中真正的存储方式:
首先看下8.25,用二进制的科学计数法表示为:1.0001*clip_image002[2]
按照上面的存储方式,符号位为0,表示为正;指数位为3+127=130,位数部分为1.00001,故8.25的存储方式如下:
0xbffff380: 01000001000001000000000000000000
分解如下:0--10000010--00001000000000000000000
符号位为0,指数部分为10000010,尾数部分为00001000000000000000000
同理,120.5在内存中的存储格式如下:
0xbffff384: 01000010111100010000000000000000
分解如下:0--10000101--11100010000000000000000
那么如果给出内存中一段数据,并且告诉你是单精度存储的话,你如何知道该数据的十进制数值呢?其实就是对上面的反推过程,比如给出如下内存数据:
01000001001000100000000000000000
第一步:符号位为0,表示是正数;
第二步:指数位为10000010,换算成十进制为130,所以指数为130-127=3;
第三步:尾数位为01000100000000000000000,换算成十进制为(1+1/4+1/64);
所以相应的十进制数值为:2^3*(1+1/4+1/64)=8+2+1/8=10.125
当然,还有一个重要的问题就是浮点数的存储精度问题。这个问题也实际上就是小数在计算机中的二进制表示问题。例如0.2这个小数的表示,根据十进制小数到二进制小数的转换规则(即小数*2取整数部分直到余数为0),所以0.2是不能完全表示的(是一个无限循环排列,精度决定于尾数的位数。
http://hi.baidu.com/lxsbupt/blog/item/55315b8b41623dd9fc1f1037.html
总结:浮点数在内存中的存储表示是以2的负数次方来模拟和逼近的,如果浮点数的小数部分可以用二进制完美地表示,则浮点数转化为二进制存储的时候不会存在精度丢失,否则内存中的这种表示浮点数的方法将会导致浮点数的精度丢失,如上面的0.2;
关于浮点数与零的比较问题,我有一些自己的看法。标准的做法是:
const float EPSINON = 0.000001;
if ((x >= - EPSINON) && (x <= EPSINON))
浮点型变量并不精确,其中EPSINON是允许的误差(即精度),所以不可将float变量用“==”或“!=”与数字比较,应该设法转化成“>=”或“<=”形式。如果写成if (x == 0.0),则是错误的。
该网页中讲到:
1.0f在计算机中可能存为0.999999或1.00001等,很难恰好是1.0。
我不太理解这种说法,因为1.0如果按照IEEE754的规定是可以用二进制完美表示的,小数位并未形成无限循环啊?关于浮点数与零比较的问题,我觉得关键是在于浮点数不能用二进制完美表示所产生的运算误差,如:
若a, b 为 float, c 为 int ,a=2.00, b=0.01, c=200,
现在来计算 a-c*b 和 0 的大小时,我们这样判断:
if a-c*b <= 0 then
就是错误的。 因为这里b=0.01是不能完美二进制表示的,所以必然产生运算误差使得计算结果必然大于0。
经测试:
float a=50.00, b=0.25;
int c=200;
if((a-c*b)<=0)
cout << "ok"<< endl;
else
cout << "no" << endl;
输出ok。
所以应该是完美表示的问题,不是对所有的浮点数都是如此。