三亚到福建火车:IEEE浮点数表示法(zz)

来源:百度文库 编辑:偶看新闻 时间:2024/04/27 09:49:24
java笔记j2ee,web2.0......
BlogJava    首页    新随笔    新文章    联系    聚合    管理
posts - 4,  comments - 5,  trackbacks - 0
IEEE浮点数表示法(zz)
最近一段时间看到版上关于浮点变量精度的讨论比较多,那么我就给对这个问题有疑惑的人详细的讲解一下intel的处理器上是如何处理浮点数的。为了能更方便的讲解,我在这里只以float型为例,从存储结构和算法上来讲,double和float是一样的,不一样的地方仅仅是float是32位的,double是64位的,所以double能存储更高的精度。还要说的一点是文章和程序一样,兼容性是有一定范围的,所以你想要完全读懂本文,你最好对二进制、十进制、十六进制的转换有比较深入的了解,了解数据在内存中的存储结构,并且会使用VC.net编译简单的控制台程序。OK,下面我们开始。
大家都知道任何数据在内存中都是以二进制(1或着0)顺序存储的,每一个1或着0被称为1位,而在x86CPU上一个字节是8位。比如一个16位(2字节)的short int型变量的值是1156,那么它的二进制表达就是:00000100 10000100。由于Intel CPU的架构是Little Endian(请参数机算机原理相关知识),所以它是按字节倒序存储的,那么就因该是这样:10000100 00000100,这就是定点数1156在内存中的结构。
那么浮点数是如何存储的呢?目前已知的所有的C/C++编译器都是按照IEEE(国际电子电器工程师协会)制定的IEEE 浮点数表示法来进行运算的。这种结构是一种科学表示法,用符号(正或负)、指数和尾数来表示,底数被确定为2,也就是说是把一个浮点数表示为尾数乘以2的指数次方再加上符号。下面来看一下具体的float的规格:
float
共计32位,折合4字节
由最高到最低位分别是第31、30、29、……、0位
31位是符号位,1表示该数为负,0反之。
30-23位,一共8位是指数位。
22-0位,一共23位是尾数位。
每8位分为一组,分成4组,分别是A组、B组、C组、D组。
每一组是一个字节,在内存中逆序存储,即:DCBA
我们先不考虑逆序存储的问题,因为那样会把读者彻底搞晕,所以我先按照顺序的来讲,最后再把他们翻过来就行了。
现在让我们按照IEEE浮点数表示法,一步步的将float型浮点数123456.0f转换为十六进制代码。在处理这种不带小数的浮点数时,直接将整数部转化为二进制表示:1 11100010 01000000也可以这样表示:11110001001000000.0然后将小数点向左移,一直移到离最高位只有1位,就是最高位的1:1.11100010010000000一共移动了16位,在布耳运算中小数点每向左移一位就等于在以2为底的科学计算法表示中指数+1,所以原数就等于这样:1.11100010010000000 * ( 2 ^ 16 )好了,现在我们要的尾数和指数都出来了。显而易见,最高位永远是1,因为你不可能把买了16个鸡蛋说成是买了0016个鸡蛋吧?(呵呵,可别拿你买的臭鸡蛋甩我~),所以这个1我们还有必要保留他吗?(众:没有!)好的,我们删掉他。这样尾数的二进制就变成了:11100010010000000最后在尾数的后面补0,一直到补够23位:11100010010000000000000(MD,这些个0差点没把我数的背过气去~)
再回来看指数,一共8位,可以表示范围是0 - 255的无符号整数,也可以表示-128 - 127的有符号整数。但因为指数是可以为负的,所以为了统一把十进制的整数化为二进制时,都先加上127,在这里,我们的16加上127后就变成了143,二进制表示为:10001111
12345.0f这个数是正的,所以符号位是0,那么我们按照前面讲的格式把它拼起来:
0 10001111 11100010010000000000000
01000111 11110001 00100000 00000000
再转化为16进制为:47 F1 20 00,最后把它翻过来,就成了:00 20 F1 47。
现在你自己把54321.0f转为二进制表示,自己动手练一下!
有了上面的基础后,下面我再举一个带小数的例子来看一下为什么会出现精度问题。
按照IEEE浮点数表示法,将float型浮点数123.456f转换为十六进制代码。对于这种带小数的就需要把整数部和小数部分开处理。整数部直接化二进制:100100011。小数部的处理比较麻烦一些,也不太好讲,可能反着讲效果好一点,比如有一个十进制纯小数0.57826,那么5是十分位,位阶是1/10;7是百分位,位阶是1/100;8是千分位,位阶是1/1000……,这些位阶分母的关系是10^1、10^2、10^3……,现假设每一位的序列是{S1、S2、S3、……、Sn},在这里就是5、7、8、2、6,而这个纯小数就可以这样表示:n = S1 * ( 1 / ( 10 ^ 1 ) ) + S2 * ( 1 / ( 10 ^ 2 ) ) + S3 * ( 1 / ( 10 ^ 3 ) ) + …… + Sn * ( 1 / ( 10 ^ n ) )。把这个公式推广到b进制纯小数中就是这样:
n = S1 * ( 1 / ( b ^ 1 ) ) + S2 * ( 1 / ( b ^ 2 ) ) + S3 * ( 1 / ( b ^ 3 ) ) + …… + Sn * ( 1 / ( b ^ n ) )
天哪,可恶的数学,我怎么快成了数学老师了!没办法,为了广大编程爱好者的切身利益,喝口水继续!现在一个二进制纯小数比如0.100101011就应该比较好理解了,这个数的位阶序列就因该是1/(2^1)、1/(2^2)、1/(2^3)、1/(2^4),即0.5、0.25、0.125、0.0625……。乘以S序列中的1或着0算出每一项再相加就可以得出原数了。现在你的基础知识因该足够了,再回过头来看0.45这个十进制纯小数,化为该如何表示呢?现在你动手算一下,最好不要先看到答案,这样对你理解有好处。
我想你已经迫不及待的想要看答案了,因为你发现这跟本算不出来!来看一下步骤:1 / 2 ^1位(为了方便,下面仅用2的指数来表示位),0.456小于位阶值0.5故为0;2位,0.456大于位阶值0.25,该位为1,并将0.45减去0.25得0.206进下一位;3位,0.206大于位阶值0.125,该位为1,并将0.206减去0.125得0.081进下一位;4位,0.081大于0.0625,为1,并将0.081减去0.0625得0.0185进下一位;5位0.0185小于0.03125,为0……问题出来了,即使超过尾数的最大长度23位也除不尽!这就是著名的浮点数精度问题了。不过我在这里不是要给大家讲《数值计算》,用各种方法来提高计算精度,因为那太庞杂了,恐怕我讲上一年也理不清个头绪啊。我在这里就仅把浮点数表示法讲清楚便达到目的了。
OK,我们继续。嗯,刚说哪了?哦对对,那个数还没转完呢,反正最后一直求也求不尽,加上前面的整数部算够24位就行了:1111011.01110100101111001。某BC问:“不是23位吗?”我:“倒,不是说过了要把第一个1去掉吗?当然要加一位喽!”现在开始向左移小数点,大家和我一起移,众:“1、2、3……”好了,一共移了6位,6加上127得131(怎么跟教小学生似的?呵呵~),二进制表示为:10000101,符号位为……再……不说了,越说越啰嗦,大家自己看吧:
0  10000101  11101101110100101111001
42  F6  E9  79
79  E9  F6  42
下面再来讲如何将纯小数转化为十六进制。对于纯小数,比如0.0456,我们需要把他规格化,变为1.xxxx * (2 ^ n )的型式,要求得纯小数X对应的n可用下面的公式:
n = int( 1 + log (2)X );
0.0456我们可以表示为1.4592乘以以2为底的-5次方的幂,即1.4592 * ( 2 ^ -5 )。转化为这样形式后,再按照上面第二个例子里的流程处理:
1. 01110101100011100010001
去掉第一个1
01110101100011100010001
-5 + 127 = 122
0  01111010  01110101100011100010001
最后:
11 C7 3A 3D
另外不得不提到的一点是0.0f对应的十六进制是00 00 00 00,记住就可以了。
最后贴一个可以分析并输出浮点数结构的函数源代码,有兴趣的自己看看吧:
// 输入4个字节的浮点数内存数据
void DecodeFloat( BYTE pByte[4] )
{
printf( "原始(十进制):%d  %d  %d  %d\n" , (int)pByte[0],
(int)pByte[1], (int)pByte[2], (int)pByte[3] );
printf( "翻转(十进制):%d  %d  %d  %d\n" , (int)pByte[3],
(int)pByte[2], (int)pByte[1], (int)pByte[0] );
bitset<32> bitAll( *(ULONG*)pByte );
string strBinary = bitAll.to_string, allocator >();
strBinary.insert( 9, "  " );
strBinary.insert( 1, "  " );
cout << "二进制:" << strBinary.c_str() << endl;
cout << "符号:" << ( bitAll[31] ? "-" : "+" ) << endl;
bitset<32> bitTemp;
bitTemp = bitAll;
bitTemp <<= 1;
LONG ulExponent = 0;
for ( int i = 0; i < 8; i++ )
{
ulExponent |= ( bitTemp[ 31 - i ] << ( 7 - i ) );
}
ulExponent -= 127;
cout << "指数(十进制):" << ulExponent << endl;
bitTemp = bitAll;
bitTemp <<= 9;
float fMantissa = 1.0f;
for ( int i = 0; i < 23; i++ )
{
bool b = bitTemp[ 31 - i ];
fMantissa += ( (float)bitTemp[ 31 - i ] / (float)( 2 << i ) );
}
cout << "尾数(十进制):"  << fMantissa << endl;
float fPow;
if ( ulExponent >= 0 )
{
fPow = (float)( 2 << ( ulExponent - 1 ) );
}
else
{
fPow = 1.0f / (float)( 2 << ( -1 - ulExponent ) );
}
cout << "运算结果:" << fMantissa * fPow << endl;
}
累死了,我才发现这篇文章虽然短,然而确是最难写的。上帝,我也不是机算机,然而为什么我满眼都只有1和0?看来我也快成了黑客帝国里的那个看通迅员了……希望大家能不辜负我的一翻辛苦,帮忙up吧!
也就是32位浮点数在内存中如果存储为 79  E9  F6  42
则先将其到序 42  F6  E9  79使其高位在前
再将其转化为二进制
0 10000101 11101101110100101111001
最高位是符号位 0表示是正值
接下来8位是指数位 转换为十进制再减127 结果6
右移6位111011。01110100101111001
最前面添1 变为1111011。01110100101111001
整数部分为1111011 转为十进制 123
小数部分01110100101111001
其中0对应2的-1次方,接下来的1对应2的-2次方
即0*2(-1)+1*2(-2)+1*2(-3)+1*2(-4)+0*2(-5)+1*2(-6)...... ~ 0.456
最后相加接近于0。456
结果123.456posted on 2006-10-08 22:27 凌宇 阅读(917) 评论(3)  编辑  收藏

FeedBack:
# re: IEEE浮点数表示法(zz)
2006-10-08 22:29 | 凌宇
IEEE浮点数表示
从存储结构和算法上来讲,double和float是一样的,不一样的地方仅仅是float是32位的,double是64位的,所以double能存储更高的精度。
任何数据在内存中都是以二进制(0或1)顺序存储的,每一个1或0被称为1位,而在x86CPU上一个字节是8位。比如一个16位(2字节)的short int型变量的值是1000,那么它的二进制表达就是:00000011 11101000。由于Intel CPU的架构原因,它是按字节倒序存储的,那么就因该是这样:11101000 00000011,这就是定点数1000在内存中的结构。
目前C/C++编译器标准都遵照IEEE制定的浮点数表示法来进行float,double运算。这种结构是一种科学计数法,用符号、指数和尾数来表示,底数定为2——即把一个浮点数表示为尾数乘以2的指数次方再添上符号。下面是具体的规格:
````````符号位 阶码 尾数 长度
float 1 8 23 32
double 1 11 52 64
临时数 1 15 64 80
由于通常C编译器默认浮点数是double型的,下面以double为例:
共计64位,折合8字节。由最高到最低位分别是第63、62、61、……、0位:
最高位63位是符号位,1表示该数为负,0正;
62-52位,一共11位是指数位;
51-0位,一共52位是尾数位。
^P
按照IEEE浮点数表示法,下面将把double型浮点数38414.4转换为十六进制代码。
把整数部和小数部分开处理:整数部直接化十六进制:960E。小数的处理:
0.4=0.5*0+0.25*1+0.125*1+0.0625*0+……
实际上这永远算不完!这就是著名的浮点数精度问题。所以直到加上前面的整数部分算够53位就行了(隐藏位技术:最高位的1不写入内存)。
如果你够耐心,手工算到53位那么因该是:38414.4(10)=1001011000001110.0110101010101010101010101010101010101(2)
科学记数法为:1.001……乘以2的15次方。指数为15!
于是来看阶码,一共11位,可以表示范围是-1024 ~ 1023。因为指数可以为负,为了便于计算,规定都先加上1023,在这里,15+1023=1038。二进制表示为:100 00001110
符号位:正—— 0 !
合在一起(尾数二进制最高位的1不要):
01000000 11100010 11000001 11001101 01010101 01010101 01010101 01010101
按字节倒序存储的十六进制数就是:
55 55 55 55 CD C1 E2 40
回复  更多评论

# re: IEEE浮点数表示法(zz)
2007-09-01 15:19 |
非常感谢
但是没看懂
我的QQ283066589
麻烦作者加我一下好吗?  回复  更多评论

# re: IEEE浮点数表示法(zz)
2008-08-04 17:02 |
写的不错。辛苦了  回复  更多评论

IT新闻  新用户注册  刷新评论列表
标题
姓名
主页
验证码*  
内容(请不要发表任何与政治相关的内容)
Remember Me?
登录  使用高级评论  新用户注册  返回页首  恢复上次提交
[使用Ctrl+Enter键可以直接提交]
Copyright ?2009 凌宇 Powered by: 博客园 模板提供:沪江博客
<2008年8月>
日一二三四五六
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456
常用链接
我的随笔
我的评论
我的参与
最新评论
留言簿(1)
给我留言
查看公开留言
查看私人留言
随笔档案(3)
2008年7月 (1)
2006年11月 (2)
文章档案(12)
2006年12月 (2)
2006年11月 (7)
2006年10月 (2)
2006年9月 (1)
相册
文章贴图
校园
绝版中国照片
收藏夹
JAVA
持久层设计 EJB3
Java
EJB3 Persistence开发手册
Java JDK 5.0学习笔记
最新随笔
1. Log4j的配置
2. Oracle中删除表中相同记录的分析
3. static属性与instance属性 初始化的时机是不同的(曾犯的错误)
搜索
最新评论 
1. re: JAVA字符串转日期或日期转字[未登录]
非常感谢!!!!
--john
2. re: public,private,protect的访问机制
jkjh
--亦心
3. re: IEEE浮点数表示法(zz)
写的不错。辛苦了
--匿名用户
4. re: IEEE浮点数表示法(zz)
非常感谢
但是没看懂
我的QQ283066589
麻烦作者加我一下好吗?
--张利阳
5. re: IEEE浮点数表示法(zz)
评论内容较长,点击标题查看
--凌宇
阅读排行榜
1. Oracle中删除表中相同记录的分析(370)
2. static属性与instance属性 初始化的时机是不同的(曾犯的错误)(131)
3. Log4j的配置 (35)
评论排行榜
1. Log4j的配置 (0)
2. Oracle中删除表中相同记录的分析(0)
3. static属性与instance属性 初始化的时机是不同的(曾犯的错误)(0)