• 深入浅出IEEE浮点数表示法


    使用二进制定点表示浮点数十分麻烦,且由于有些浮点数的二进制不能精确表示,只能添加小数位数不断近似,使得位数开销极大。20世纪70年代已经有一些计算机厂家推出了自己的浮点数表示标准,但并不统一,于是在1985年IEEE 742标准被提出,作为目前国际浮点数的表示标准。

    IEEE 742标准规定了一个浮点数表示公式:V = (-1)^s * M * 2^E

    • 怎么理解这个公式?

      其实这里有一个知识点:浮点数是用科学计数法来表示的。

      比如十进制数25.125

      25.125_{(D)} = 11001.001_{B} = 1.1001001 * 2^4

       

      你也可以把它表示成0.11001001 * 2^5,11.001001 * 2^3, 110.01001 * 2^2,11100100.1 * 2^{-3}等等,实际上浮点数的得名也是源自于它的点位漂浮不定的情况,在实际应用中我们采用IEEE 标准,用科学计数法统一表示这些浮点数。

       

      回头看公式,其中V(Value)表示浮点数的十进制值;

      S(Sign)符号位表示浮点数的正符号,取1为负,0为正;上例显然是正数, S = 0。

      注意,面对这个符号位要摆脱之前整数补码取反加1求负的思想,实际上求浮点数的负值只要偏转一个符号位就行了。

      M(Mantissa)表示尾数,表示 二进制浮点数 科学计数法表示形式 下的小数部分; 上例中M = 1.1001001

      E (Exponent) 表示阶码,是一个有符号数,表示 二进制浮点数 科学计数法表示形式 的2次幂的值,上例中 E = 4

       

    • 怎么把数装进计算机的二进制位?

      IEEE 提供了浮点数的32位和64位表示形式:

      image-20211029165937801

      这里除了符号位外又看到两个变量exp 和 frac 。

      这两个参数的作用是什么?这里又要开始讲IEEE标准,制定IEEE标准的这帮人根据exp的值分了三类情况:规格化,非规格化,特殊值情况,我们先主讲规格化的情况。

      • 规格化

        即exp的位模式既不全为0也不全为1的情况,该情况下IEEE二进制数对应到科学计数法表示要通过如下公式:

        E = exp - bias

        M = 1 + frac

        exp是exponent,即指数,注意别和阶码E混淆,exp是一个无符号整数,而E是有符号整数;

        frac是fraction, 即M去掉整数部分后的小数字段, 注意规格化表示下尾数的第一位一定是1,正如科学计数法下非0数表示的第一位一定非0的情况。

         

        其中bias 是一个偏置值,和之前2.3用于保证整数向0舍入的bias不一样,这个bias是用于浮点数计算时能对齐两数的阶码使用的。这一部分我并不是了解的很清楚,目前要记住E是一个移码,参考:

        为什么要用移码来表示阶码(指数)呢?

        bias 的取值是 bias = 2^{n-1} - 1 ,其中 n 是exp的数据位数,单精度下bias 值为 127(n = 8), 双精度下为 1023 (n = 11)。

         

        单精度情况下,阶码E的有符号表示范围为-127 ~ +128 ,但是-127 和 128 在规格化里是没被用来表示的,原因是在IEEE标准中这两个数分别被用来规定了非规格化和特殊值的情况,也即单精度的规格化阶码exp范围是 00000001(-126)-11111110(127) , 之后讨论。

        注意,看exp不能按正常的二进制转十进制的思维转成十进制,需要减去偏差。

        单精度情况exp和E对照:

        exp(B)exp(D)E
        0000 0000 (非规格化) 0 -126(非规格化情况E = 1 - bias)
        0000 0001 1 -126
           
        1111 1110 254 127
        1111 1111 (特殊值) 255 --

        规格化情况的公式变为:

        V = egin{cases} (-1)^s * (1 + frac)*2^{exp - 127},float\ (-1)^s * (1 + frac)*2^{exp - 1023},doubleend{cases}

         

        按照规格表示,我们得到了25.125D(即1.1001001 * 2^4)的单精度表示方法:

        1_{(s,1位)} 10000011_{(exp,8位)} 10010010000000000000000_{(fra,23位)}

        注意,在实际的位表示中开头1被省略掉了(即implied leading 1, 隐含的1开头表示),用以获得一个额外的尾数位以提高精度。

         

        • 非规格化

          阶码域全为0时,表示的值就是非规格化的,非规格数可以用来表示0及其他一些非常小的数,注意+0.0和-0.0在浮点数中是同时存在的,仅有符号位的不同。非规格化中M和E的公式定义为:

          M = frac

          E = 1- bias

          非规格化的尾数是0开头的,故M代表的就是小数部分,至于E 为什么等于 1-bias会比较费解,其实这里的1是对非规格化数没有隐含的1的补偿,其有益于最大非规格化数到最小规格化数的平滑转变。

          另外,值比较小的规格化的数在更低位数的机器中可能成为非规格化数。我们知道,增大尾数位有益于提高浮点数的表示精度从而得到更大的规格化值,而增大指数为有益于提高浮点数的表示范围得到更小更精确的规格化值,我们可以试想下列情况:

          设阶码位为k, 尾数位为n,偏置量bias

          32位环境下的某小数0.001953的二进制表示形式为:

          0_{(s,1位)}01110101_{(exp,8位)} 11111111111101111001110_{(fra,32位)}

          其计数表示为1.1111111111110111100111 * 2^{-10}

           

          放到k = 4,n = 3,bias = 7的机器环境中表示为:

          0_{(s,1位)}0000_{(exp,4位)} 001_{(fra,3位)}

          E = 1 - 7 = -6, M = 0.001 ,故0.001953在该环境下表示为0.001* 2^{-6}

          (0.000000001_{B},显然是1/512的近似结果,具体看CSAPP图2-35参考),

          若要让小数点到达1前,阶码值需要到-9,即规格化表示是1.0 * 2^{-9},可是该环境下的阶码E范围为 -6 ~ 7,显然超出了取值范围,E取最小值也无法满足开头是1的规格化条件,只能表示成非规格形式。

          另外可见,如果表示的浮点数的计数表示超过了E的最大范围,就会导致溢出,值变为+infty。比如上述环境中256.0的计数表示为 1.0* 2^8, 用4个指数位已经不够表示了(E = 14(1110) - 7 = 7 < 8(1111)),于是全1溢出。

        • 特殊值

          特殊值包括正无穷,负无穷和NaN(即不是一个数,Not a Number), 在阶码全为1的时候出现,其中当小数位全为0时,s=1表示负无穷,s=0表示正无穷,如果小数位不全为数就是NaN, 它们的用处是用于表示未初始化的数据,比较少见,这里不再详谈。

        image-20211029235949624

        参考:

        http://kaito-kidd.com/2018/08/08/computer-system-float-point/

        南京大学 计算机系统基础(一)主讲:袁春风老师

  • 相关阅读:
    HashMap源码分析jdk1.8
    Struts1.x总结
    session的使用
    浅谈EL
    浅谈JavaBean
    try、catch、finally带return的执行顺序总结
    jvm内存模型
    left join 、right join 、inner join之间的区别
    js按键事件
    log4j配置详解
  • 原文地址:https://www.cnblogs.com/Tracker647/p/15518547.html
Copyright © 2020-2023  润新知