解读IEEE标准754:浮点数表示

解读IEEE标准754:浮点数表示

技术杂谈小彩虹2012-11-20 15:49:1981923A+A-

一、背景
  在IEEE标准754之前,业界并没有一个统一的浮点数标准,相反,很多计算机制造商都设计自己的浮点数规则,以及运算细节。那时,实现的速度和简易性比数字的精确性更受重视。
  直到1985年Intel打算为其的8086微处理器引进一种浮点数协处理器的时候,聪明地意识到,作为设计芯片者的电子工程师和固体物理学家们,也许并不能通过数值分析来选择最合理的浮点数二进制格式。于是Intel在请加州大学伯克利分校的 William Kahan教授──最优秀的数值分析家之一来为8087 FPU设计浮点数格式; 而这个家伙又找来两个专家来协助他,于是就有了KCS组合(Kahn, Coonan, and Stone)。 他们共同完成了Intel的浮点数格式设计,而且完成地如此出色,以致于IEEE组织决定采用一个非常接近KCS的方案作为IEEE的标准浮点格式。目前,几乎所有计算机都支持该标准,大大改善了科学应用程序的可移植性。

二、表示形式
  从表面上看,浮点数也是一串0和1构成的位序列(bit sequence),并不是三头六臂的怪物,更不会咬人。然而IEEE标准从逻辑上用三元组{S,E,M}表示一个数N,如下图所示:

解读IEEE标准754:浮点数表示


  N的实际值n由下列式子表示:

解读IEEE标准754:浮点数表示


其中:
  ★ n,s,e,m分别为N,S,E,M对应的实际数值,而N,S,E,M仅仅是一串二进制位。
  ★ S(sign)表示N的符号位。对应值s满足:n>0时,s=0; n<0时,s=1。
  ★ E(exponent)表示N的指数位,位于S和M之间的若干位。对应值e值也可正可负。
  ★ M(mantissa)表示N的尾数位,恰好,它位于N末尾。M也叫有效数字位(sinificand)、系数位(coefficient), 甚至被称作“小数”。

三、浮点数格式

  IEEE标准754规定了三种浮点数格式:单精度、双精度、扩展精度。前两者正好对应C语言里头的float、double或者FORTRAN里头的real、double精度类型。限于篇幅,本文仅介绍单精度、双精度浮点格式。
  ★ 单精度:N共32位,其中S占1位,E占8位,M占23位。
  

解读IEEE标准754:浮点数表示


  ★ 双精度:N共64位,其中S占1位,E占11位,M占52位。

解读IEEE标准754:浮点数表示



  值得注意的是,M虽然是23位或者52位,但它们只是表示小数点之后的二进制位数,也就是说,假定 M为“010110011...”, 在二进制数值上其实是“.010110011...”。而事实上,标准规定小数点左边还有一个隐含位,这个隐含位通常,哦不,应该说绝大多数情况下是1,那什么情况下是0呢?答案是N对应的n非常小的时候,比如小于 2^(-126)(32位单精度浮点数)。不要困惑怎么计算出来的,看到后面你就会明白。总之,隐含位算是赚来了一位精度,于是M对应的m最后结果可能是"m=1.010110011...”或者“m=0.010110011...”

四、计算e、m
  首先将提到令初学者头疼的“规格化(normalized)”、“非规格化(denormalized)”。噢,其实并没有这么难的,跟我来!掌握它以后你会发现一切都很优雅,更美妙的是,规格化、非规格化本身的概念几乎不怎么重要。请牢记这句话:规格化与否全看指数E!
  下面分三种情况讨论E,并分别计算e和m:
  
  1、规格化:当E的二进制位不全为0,也不全为1时,N为规格化形式。此时e被解释为表示偏置(biased)形式的整数,e值计算公式如下图所示:

解读IEEE标准754:浮点数表示


  上图中,|E|表示E的二进制序列表示的整数值,例如E为"10000100",则|E|=132,e=132-127=5 。 k则表示E的位数,对单精度来说,k=8,则bias=127,对双精度来说,k=11,则bias=1023。
  此时m的计算公式如下图所示:
  

解读IEEE标准754:浮点数表示


  标准规定此时小数点左侧的隐含位为1,那么m=|1.M|。如M="101",则|1.M|=|1.101|=1.625,即 m=1.625

  2、非规格化:当E的二进制位全部为0时,N为非规格化形式。此时e,m的计算都非常简单。

解读IEEE标准754:浮点数表示


  注意,此时小数点左侧的隐含位为0。 为什么e会等于(1-bias)而不是(-bias),这主要是为规格化数值、非规格化数值之间的平滑过渡设计的。后文我们还会继续讨论。
  有了非规格化形式,我们就可以表示0了。把符号位S值1,其余所有位均置0后,我们得到了 -0.0; 同理,把所有位均置0,则得到 +0.0。非规格化数还有其他用途,比如表示非常接近0的小数,而且这些小数均匀地接近0,称为“逐渐下溢(gradually underflow)”属性。
  
  3、特殊数值: 当E的二进制位全为1时为特殊数值。此时,若M的二进制位全为0,则n表示无穷大,若S为1则为负无穷大,若S为0则为正无穷大; 若M的二进制位不全为0时,表示NaN(Not a Number),表示这不是一个合法实数或无穷,或者该数未经初始化。
  
五、范例
  仔细研读第四点后,再回忆一下文章开头计算n的公式,你应该写出一个浮点编码的实际值n了吧? 还不能吗?不急,我先给你示范一下。我们假定N是一个8位浮点数,其中,S占1位,E占4位,M占3位。下面这张表罗列了N可能的正数形式,也包含了e、m等值,请你对照着这张表,重温一下第四点,你会慢慢明白的。说实在的,这张表花了我不少功夫呢,幸好TeX画表格还算省事! 
  

解读IEEE标准754:浮点数表示


  这张表里头有很多有趣的地方,我提醒一下:
  ★ 看 N 列,从上到下,二进制位表示是均匀递增的,且增量都是一个最小二进制位。这不是偶然,正是巧妙设计的结果。观察最大的非规格数,发现恰好就是M全为1, E全为0的情况。于是我们求出最大的非规格数为:

解读IEEE标准754:浮点数表示


  上面的公式中,h为M的位数(如范例中为3)。注意,公式等号右边的第一项同时又是最小规格数的值(如范例中为 8/512 );第二项则正是最小非规格数的值(如范例中为1/512)即该浮点数能表示的最小正数。
  ★ 看 m 列,规格化数都是 1+ x 的形式,这个1正是隐含位1; 而非规格化数隐含位为0, 所以没有 "1+" 。
  ★ 看 n 列,非规格化数从上到下的增量都是 1/512, 且过渡到规格化数时,增量是平滑的,依旧是1/512。这正是非规格化数中e等于(1-bias)而不是(-bias)的缘故,也是巧妙设计的结果。 再继续往下看,发现增量值逐渐增大。可见,浮点数的取值范围不是均匀的。
  
六、实战
  我们用一小段汇编来测试一下,浮点数在内存中是如何表示的。测试环境: GentooLinux2006.0/GNU assembler version 2.16.1/GNU gdb 6.4/AMD XP1600+。 如下所示

Shell代码 
  1. ~/coding/assemble $ gdb float

  2. GNU gdb 6.4

  3. Copyright 2005 Free Software Foundation, Inc.

  4. GDB is free software, covered by the GNU General Public License, and you are

  5. welcome to change it and/or distribute copies of it under certain conditions.

  6. Type "show copying" to see the conditions.

  7. There is absolutely no warranty for GDB. Type "show warranty" for details.

  8. This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".

  9. (gdb) list

  10. 1 .section .data

  11. 2 f1:

  12. 3 .float 5

  13. 4 f2:

  14. 5 .float 0.1

  15. 6 .section .text

  16. 7 .global _start

  17. 8 _start:

  18. 9 nop

  19. 10

  20. (gdb) x/f &f1

  21. 0x80490a4 <f1>: 5

  22. (gdb) x/xw &f1

  23. 0x80490a4 <f1>: 0x40a00000

  24. (gdb) x/f &f2

  25. 0x80490a8 <f2>: 0.100000001

  26. (gdb) x/xw &f2

  27. 0x80490a8 <f2>: 0x3dcccccd

  28. (gdb)

~/coding/assemble $ gdb floatGNU gdb 6.4Copyright 2005 Free Software Foundation, Inc.GDB is free software, covered by the GNU General Public License, and you arewelcome to change it and/or distribute copies of it under certain conditions.Type "show copying" to see the conditions.There is absolutely no warranty for GDB. Type "show warranty" for details.This GDB was configured as "i686-pc-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1".(gdb) list1 .section .data2 f1:3 .float 54 f2:5 .float 0.1 6 .section .text7 .global _start8 _start:9 nop10 (gdb) x/f &f10x80490a4 <f1>: 5(gdb) x/xw &f10x80490a4 <f1>: 0x40a00000(gdb) x/f &f20x80490a8 <f2>: 0.100000001(gdb) x/xw &f20x80490a8 <f2>: 0x3dcccccd(gdb)
  从上面的gdb命令结果可以看出,浮点数5被表示为 0x40a00000,二进制形式为( 0100 0000 1010 0000 ... 0000 0000)。红色数字为E,可以看出|E|=129>0, 则e=129-bias=129-127=2 ; 蓝色数字为M, 且|E|>0,说明是规格化数,则m=|1.M|=|1.01000..000|=1.25 ; 由n的计算公式可以求得 n=(-1)^0 * 1.25 * 2^2 = 5, 结果被验证了。
  同样,你也可以验证一下十进制浮点数0.1的二进制形式是否正确,你会发现,0.1不能表示为有限个二进制位,因此在内存中的表示是舍入(rounding)以后的结果,即 0x3dcccccd, 十进制为0.100000001, 误差0.000000001由此产生了。
  
七、未完成
  关于浮点数,还有很多东西(比如舍入误差、除零异常等等)值得我们深入探讨,但已经无法在此继续。这篇文章的目的仅在初步解释IEEE标准754对浮点数的规定以及一些奇妙的地方。写这篇文章花掉了我整天的时间,但也使我彻底记住了以前让我胆怯的东西──最重要的是,希望这篇文章对大家有点用处,也算我为计算机科学基础理论版以及Linuxsir.org做的一点贡献。
  

参考书目:
①: Randall Hyde, The Art of Assembly Language, Vol.1, 4.2.1
②: Randal E. Bryant, David R. O’Hallaron, Computer Systems A Programmer’s Perspective (Beta Draft), PartⅠ, Chapt.Ⅱ, 2.4
③: Rechard Blum, Professional Assembly Language


点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交
  • 3条评论
  • 小彩虹2021-01-05 15:54:14
  • 还有一些数比如:
    9007199254740992+1=9007199254740992。
    9007199254740992+2=9007199254740994。
    9007199254740992+3=9007199254740996。
    9007199254740992+4=9007199254740996。
    9007199254740992+5=9007199254740996。
    9007199254740992+6=9007199254740998。
  • 小彩虹2021-01-05 15:54:02
  • 所以在计算的时候需要注意:
    1.避免大数“吃掉”小数
    比如算 10^9+1=0.1*10^10 + 0.0000000001*10^10,在8位计算机中加号后面的表达式因为对阶而下溢为0
    2.避免两个近似数相减
    此时可以变减为其他运算,比如lnx1-lnx2=ln(x1/x2)
    3.简化计算步骤,减少计算次数
    可以避免误差的积累,比如算x^255,一般要算254次,但用秦九韶法可以大大减少运算次数
    4.注意计算顺序
    比如算I[n]=x^n/(x+5)在0到1上的积分,I[n]=1/n-5I[n-1], 如果这样算I[n]+5I[n-1]=1/n,那么I[n-1]前的系数5将造成误差以5倍递增,但如果改为: I[n-1]=-1/5I[n]+1/n, 那么。。。
    5.避免两个大数相运算
  • 小彩虹2021-01-05 15:53:54
  • 大家可以用电脑中自带的计算器算下:0.2+0.4等于多少。然后再试试在IE的地址栏输入:
    javascript: var a = 0.2;var b = 0.4;alert(a+b);然后回车。这时你会发现。0.2+0.4=0.6000000000000001
    其实这个结果也是上面说的原理。
    其实不止0.2+0.4是这样的结果。还有好多

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们