浮点数存储格式及精度详解

  • A+
所属分类:未分类

浮点数在内存存储中都分为3个部分:

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。

所以:

http://bbs.pediy.com/showthread.php?t=84132

下面就看看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;

关于浮点数与零的比较问题,我有一些自己的看法。标准的做法是:

http://www.piaoyi.org/asp/float-0-FormatNumber.html

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。

所以应该是完美表示的问题,不是对所有的浮点数都是如此。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: