IEEE-754标准与浮点数运算

序言

本文主要简述了计算机中数字的表示方式,以及IEEE-754标准的由来及具体规定,最后简单的叙述了浮点数的运算规则。阅读这篇文章之前,最好有关于有关于机器数中的原码、反码、补码三种形式的一些概念。

计算机"眼中"的数字

计算机内数据和指令都是由晶体管和门电路等元件完成的,对于这些元件来说,或者是其唯一的状态,这种状态的表现就是二进制的理念。就像黑客帝国世界中漫天飞的01一样,计算机世界使用的机器语言也只有01。而在机器语言中,当计算机想要表示一个数字时,这时就得使用机器数了,机器数所表示的真实数值称为真值

机器数(computer number)是将符号"数字化"的数,是数字在计算机中的二进制表示形式。机器数有2个特点:一是符号数字化,二是其数的大小受机器字长的限制

机器数有两个特点:

  1. 符号数字化,通常机器数以最高位表示正负号,即0代表正数,1代表负数。
  2. 数值的大小受字长显示,如8bit的机器,其可表示的真值数值范围为-127`+127`,**机器数**即`11111111`01111111

那么我们以8bit二进制为例,看看机器数原码如何表示10进制中的3-3

对于正数符号位为0,所以机器数为00000011
对于负数符号位为1,所以机器数为10000011

IEEE标准的由来

在上文中,我们知道计算机通过二进制可以精确的表达整数,那么计算机是如何表达小数,或者说计算机是如何处理这些小数的呢?

在早期,计算机表示小数的方式是定点小数的方式去表示的,定点小数中。小数点隐含在第一位编码和第二位中间。

如真值为正值的小数0.101,使用定点小数在计算机中表示就为0 101

然而,早期使用定点小数无法表示过大和过小的值,并且在计算过程由于数值范围的限定,会出现数值溢出的问题。

随着技术的更新,在1978年的时候,Intel公司推出了首枚16bit微处理器(CPU)8086。这台x86的老祖宗虽然自身无法处理小数的运算,但是在编译器层面可以通过用整数指令模拟出小数的运算,不过这种运算的方式效率是非常低的。

为了解决这类问题,1980年Intel公司推出了首款x87浮点协处理器运算单元(FPU)8087,通过主板上额外的协处理器插槽,安装后不仅可以解决小数的运算问题,并且对于不同的应用,性能提升了20%~500%。

对于计算机发展来说,8087是款非常棒的FPU,但是它的意义真正体现在这款FPU的设计师之一的William Kahan教授设计了IEEE-754标准的雏形,而正是因为这套标准,我们计算机才能精准的处理小数。

1985年时,IEEE推出了IEEE 754-1985标准,随着大佬们的努力,IEEE还推出了目前的版本——IEEE 754-2008

而我们使用的高级语言中浮点数的运算,如C、C++、JavaScript、Java都是基于这个标准而定。


IEEE 754规范
  1. 组成

    IEEE 754标准中,浮点格式主要分为四种类型,即单精度格式、双精度格式、扩展单精度格式和扩展双精度格式。它们的构成都由以下三部分组成:

    • 符号位,控制数值的正负
    • 指数位,控制数值的大小
    • 尾数位,控制数值的精度

    我们以64位的双精度Double类型为例,他的构成是如下图所示。

    img

    • 第1位:符号位,0表示正数,1表示负数
    • 第2位到第12位(共11位):指数部分,指数部分一共有11个二进制位,最大是11个1,即范围为02047
    • 第13位到第64位(共52位):尾数部分(即有效数字)

    所有四种浮点格式各个部分组成如下图,引用自QAWRA浮点格式参数

  2. 正规化

    对于十进制计算来说,小数0.0011既可以表示成1.1 x 10<sup>-3</sup>,也可以表示成11 x 10<sup>-4</sup>

    但是对于机器数而言,统一的标准规定它首要需要正规化,也就是必须唯一地表示小数点的位置:

    img

    上图中b是由01构成的二进制数,b通常由十进制二进制而来,它会自动忽略起始位置的隐藏位1.b最终被存入上图中尾数部分p加上移码后的二进制数存入上图指数部分

    对于一个非0的二进制数来说,我们可以保证尾数部分一直以1开始,那么我们通过把第一位的1当做隐藏位,默认它的存在,并不把它存入尾数部分,这样就可以提高1位数据表示的精度。

  3. 移码

    上述介绍中有个词:移码

    我们知道指数是有正值或负值的,既然有负值那么就得用最高位来表示正负符号。也就是说如果是11位机器数,实际上影响数值大小的位数只有10位,并且我们可以得出十进制范围为 -1023~1024

    如果我们以-1023~1024作为该数的指数范围区间,那么为了比较数据是否在范合理围内,将不可避免的进行负值加法的运算,为了解决这个问题,大佬们提出移码 的概念。其中有两个特殊值:-10231024后面会解释。

    用移码来表示阶码有以下几种好处。

    1.方便比较大小和加减

    2.保证浮点数的机器零为0

    3.特殊值(0和max)比较容易检验

    4.提高表示数据的精度

  4. 非正规化

    正规化告诉我们浮点数的最左边隐藏位无论何时都是1,那么对于0就无法表示了。大佬们通过规定如果指数部分为0时,尾数部分就再也不是1.bbbbbb的形式了,而是0.bbbbbb这种非正规化的表示形式,这样如果尾数部分全部为0时,即表示该数值为0。下图是64位双精度非正规化的表示方式:

    在这里插入图片描述

  5. 无穷大和NaN

    当指数为最小值时表示非正规化,而当指数为最大值+1,尾数部分全部为0就表示无穷大,当尾数不全为0就为NaN

  6. 机器数中的ε

    ε表示1与大于1的最小浮点数之差,不同精度的浮点的ε是不同的。一般来说ε是根据不通精度的尾数部分宽度来定的。例如双精度有52位的尾数宽度p,那么该精度下ε为2-52,如下:在这里插入图片描述

  7. 无小数浮点数二进制表示

    首先我们通过上述结论我们可以得到以下求值公式。

    (-1) ^ 符号位 * 1.bbbbb... * 2 ^ 指数部分
    

    下面我们使用4399这个十进制整数我们套用上述公式,反推得到以下64位2进制浮点数结果。

    1. 首先我们将4399转为2进制数1000100101111,因为是正数,那么符号位为 0。 然后我们根据正规化的二进制浮点数表达,那么它以1.bbbbb...这种形式表示为1.000100101111 x 2<sup>12</sup>
    2. 去除隐藏位1之后尾数部分为:000100101111
    3. 指数12经过偏移加上1023换算为二进制:10000001011
    4. 最终在计算机中存储的结果为:0-10000001011-00010010111100...000

    最后,推荐大家一个IEEE 754 64位转换工具方便大家测试自己的运算结果。


浮点数的运算规则

在了解了浮点数表示的标准,与机器数编码标准后,我们来看看计算机中的浮点数是如何运算的。

浮点数运算分为两个部分,阶码运算与尾数运算,并且所有运算均采用补码运算,具体的运算一般可以分为以下五个个步骤,我将试着通过一个浮点数加减法的实际案例结合以下五个步骤进行同步说明:

假设有两组IEEE-754标准的64位双精度浮点数:

第一组x浮点数为: 0 01111111011 1001100110011001100110011001100110011001100110011010

第二组y浮点数为: 0 01111111100 1001100110011001100110011001100110011001100110011010

现在需要进行求x+y的值。

  1. 对阶

    对于浮点数来说,两浮点数进行加减,首先看两数的阶码是否相同,即小数点位置是否对齐。若两数阶码相同,表示小数点是对齐的,就可以进行尾数相加减,反之,此时需要使两数的阶码相同,这个过程叫做对阶。

    对于对阶来说需要注意的是:

    • 对阶的原则是小阶对大阶小阶对大阶的好处是,当小阶不同于大阶时,只需要移除小阶数的尾数部分的低位部分,如果是大阶对小阶的话,就需要移除大阶的高位部分了,这样的误差是无法接受的。
    • 为了减少精度的损失,我们将尾数移除的数字进行保留,供以后的舍入(第四部分)处理。
    x的阶码为 01111111011
    y的阶码为 01111111100
    计算 01111111011 - 01111111100
    转为补码计算的加法 01111111011 + (-01111111100) = 01111111011 + 10000000100[补]
    得出的结果转为原码 11111111111[补] = -1[十]
    由于得知x与y的阶差为-1,所以我们需要将小阶x的尾数右移一位
    原x的尾数部分1001100110011001100110011001100110011001100110011010
    右移后x的尾数11001100110011001100110011001100110011001100110011010
    根据0舍1入原则,右移移位后,尾数补上隐藏位的1,舍去移除的0,最后得到x的尾数部分为:
    1100110011001100110011001100110011001100110011001101
    

    注意对阶完成后,不管是加减运算,此时原数的阶码已经为大阶的阶码了

  2. 尾数计算

    由于已经对阶完成,我们只需要将52位尾数部分+1位隐藏进行加减计算。

    如果隐藏位被右移了,那么默认补0

      0.1100110011001100110011001100110011001100110011001101
    + 1.1001100110011001100110011001100110011001100110011010
    ————————————————————————————————————————————————————————
     10.0110011001100110011001100110011001100110011001100111
    

    10.0110011001100110011001100110011001100110011001100111 就是尾数计算后的结果。

  3. 结果规格化

    根据规格化的要求,我们需要将第2部计算出的尾数右移1位,并将阶码+1

    0  01111111101  0011001100110011001100110011001100110011001100110011(1)
    这里计算后1隐藏位还是继续隐藏。
    

    右移后的尾数为上面的数字,这里需要注意的是,最低位被右规了1,造成精度丢失的问题

  4. 舍入

    根据0舍1入的规则,由于我们右规了1,所以要进行+1的预算。

      0011001100110011001100110011001100110011001100110011
    + 0000000000000000000000000000000000000000000000000001
    ————————————————————————————————————————————————————————
      0011001100110011001100110011001100110011001100110100
    
  5. 溢出判断

    阶码01111111101是没有发生上下溢出的情况的。所以舍入后的结果为最终的尾数部分。

最终我们得到IEEE-754的浮点数0 01111111101 0011001100110011001100110011001100110011001100110100

将它转换为10进制数得到0.30000000000000004440892098500626

而题目中的x和y是0.10.2

说到这里聪明的你应该知道为什么0.1+0.2不等于0.3了吧。

  • 50
    点赞
  • 198
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值