剖析浮点数

[转载][url]http://blog.csdn.net/masefee/archive/2010/01/30/5272554.aspx[/url]

这里历史和发展就不说了,直接从IEEE浮点标准说起。
在 IEEE 标准中,浮点数是将特定长度的连续字节的所有二进制位分割为特定宽度的[b]符号域,指数域和尾数域[/b]三个域,其中保存的值分别用于表示给定二进制浮点数中的符号,指数和尾数。这样,通过尾数和可以调节的指数(所以称为"浮点")就可以表达给定的数值了。
具体的格式:

[table]
| 符号位 阶码 尾数 长度|
|float 1 8 23 32|
|double 1 11 52 64 |
[/table]

注:浮点数在32位机子上有两种精度,float占32位,double占64位。
我们应该不要特殊看到浮点数的内存存储形式,它跟整数没有什么区别,只是在这4字节或者8字节里有3个区域,整数有符号只有符号位及后面的数值。
那么,我们先来看32位浮点数 的换算:

[b]1. 从浮点数到16进制数[/b]

float var = 5.2f;
就这个浮点数,我们一步一步将它转换为16进制数。

首先,整数部分5,二进制表示为:0101。
其次,小数部分0.2,转换为二进制的计算方法,那么就是依次乘以2,取整数部分作为二进制数,取小数部分继续乘以2,一直算到小数结果为0为止。那么对0.2进行计算:

[table]
|0.2*2 = 0.4 * 2 = 0.8 * 2 = 1.6(0.6) * 2 = 1.2(0.2)*2 = 0.4 * 2 = 0.8 * 2 = 1.6(0.6) * 2 = 1.2 ... ...|
|00110011... ...|
[/table]
0.2的二进制就计算出来了,结果就为:0.00110011... ...
这里的省略号是你没有办法计算完。二进制序列无限循环,没有到达结果为0的那一天。那么此时我们该怎么办?
这里就得取到一定的二进制位数后停止计算,然后舍入。我们知道,float是32位,后面尾数的长度只能最大23位。因此,计算结束的时候,整数部分加上小数部分的二进制一共23位二进制。
因此5.2 的二进制表示就为:
[table]
|101.00110011001100110011|
[/table]
一共23位。

此时,使用科学计数法表示,结果为:
[table]
|1.0100110011001100110011 * 2^2|
[/table]

由于我们规定,使用二进制科学计数法后,小数点左边必须为1,这样这个1就不用存储了,我们在从16进制数换算到浮点数的时候加上这个1就是了,省略到这个1的目的是为了后面的小数部分能够多表示一位,精度就更高一些了哟。
那么省略到小数点前面的1后的结果为:
[table]
|.0100110011001100110011[color=blue]0[/color] * 2^2|
[/table]
这里后面蓝色的0就是补上的,这里不是随便补的一个0,而是0.2的二进制在这一位上本来就应该为0,如果该为1,我们就得补上一个1.
但是,在对阶或向右规格化时,尾数要向右移位,这样被右移的尾数的低位部分会被丢掉,从而造成一定的误差,因此要进行舍入处理。 常用的舍入方法有两种:一种是“0舍1入”法,即如果右移时被丢掉数位的最高位为0则舍去,为1则将尾数的末位加“1”,另一种是“恒置1”,即只要数位被移掉,就在尾数的末位恒置“1”。

举个例子:
123.456的二进制到23位时:[color=blue]111 1011.0111 0100 1011 1100 01...[/color]

后面还有依次为01...等低位,由于最高位的1会被隐藏,向后扩展一位如果不做舍入操作则结果为:
[color=blue]1.11 1011 0111 0100 1011 1100 0 * 2^6[/color]
但是经过舍入操作后,由于被舍掉的位的最高位是1,或者“恒置1”法,最后面的0都应该是1。因此最终就应该是:
[color=blue]1.11 1011 0111 0100 1011 1100 1 * 2^6[/color]
在这里需要说明,不管是恒置1,还是0舍1入法,其根本都是为了减小误差。

5.2的尾数在这里就计算好了,就是: 0100110011001100110011[color=blue]0[/color]
再来看阶数,这里我们知道是2^2次方,那么指数就是2。同样IEEE标准又规定了,因为中间的阶码在float中是占8位,而这个阶码又是有符号的(意思就是说,可以有2^-2次方的形式)。[b]注意这里偏置量的概念:[/b]
float 类型的偏置量 Bias = 2^(k-1) -1 = 2^(8-1) -1 = 127 ,但还要补上刚才因为左移作为小数部分的 2 位(也就是科学技术法的指数),因此阶码为 127 + 2=129 ,就是 IEEE 浮点数表示标准:
[table]
|V = (-1)^s × M × 2^(e - Bias)|
|s:符号位 M:尾数 e:阶码|
[/table]
这里的阶码就是129,二进制就是:10000001
因此,拼接起来后:
[img]http://dl.iteye.com/upload/attachment/364786/d56f2e58-24a0-310f-8b7f-bd814f6ba5cb.jpg[/img]

还原:
这里因为之前我们都知道有个固定的1给省略了,因此这里要给加上去。加上去之后:
1 010 0110 0110 0110 0110 011 0
这里是24位,我们先不管,小数点添进去:
1 . 010 0110 0110 0110 0110 011 0 * 2^2
然后将科学计数法变换成普通的二进制小数:
1 01 . 0 0110 0110 0110 0110 011 0
到这里,就真正可以把整数部分换成十进制了:
1 01 . 0 0110 0110 0110 0110 011 0
5. xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
我们知道了,整数部分是5,后面的小数部分再进行逆运算:
0 . 0 0110 0110 0110 0110 011 0 =
0 + 0*2^-1 + 0*2^-2 + 1*2^-3 + 1*2^-4 + 0*2^-5 + 0*2^-6 + 1*2^-7 + ... ... + 0*2^-21 这样一个式子,我们算出结果来,放在浮点数里:
5.1999998。
因此我们可以看到精度已经有损失了。

64位浮点数 的换算:
这里就不再具体说明怎么换算的了,只需要提到2个地方:
一是,中间的阶码在double中占有11位,因此阶码就不是+127了,而是加上1023,因为11位能表示的最大无符号数是2047,因此有符号范围[-1024, 1023]。
二是,尾数是52位,因此精度更高,能表示的数也就越大。我们在换算5.2的时候,后面的小数二进制+前面的5的二进制再省略一位后的总位数要填满52位。

给出一段验证代码:

#include <stdio.h>
#include <string.h>

int main()
{
union
{
float f;
int i;
}u;
u.f = 5.2f;
printf("%.7f\n", u.f); //5.1999998一旦放入,精度已经发生了变化
printf("%x\n", u.i);

union
{
float f;
int i;
}v;
v.i = 0x40A66666;
printf("%.7f\n", v.f);
printf("%x\n", v.i);
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值