• 计算机中浮点数的表示形式


    IEEE 浮点表示

    IEEE 浮点标准:V = (-1)s * M * 2E 表示一个浮点数:

    • 符号(sign) s 决定 V 的正(s=0)或负(s=1),对于 0 后面会有说明
    • 尾数(Mantissa) 二进制小数
    • 阶码(Exponent) E 的作用是对浮点数加权,这个权重是 2 的 E 次幂

    将浮点数的位分为 3 个部分:

    • 1 位的符号位 s 表示 s
    • k 位的阶码字段 exp = ek-1...e1e0 表示 E
    • n 位的小数字段 frac = fn-1...f1f0 表示 M

    以 C 语言为例,不同的精度下,s、exp、frac 有不同的位数:

      单精度:
      31  30      23 22                                       0
      +---+---------+-----------------------------------------+
      | s |   exp   |             frac                        |
      +---+---------+-----------------------------------------+
      双精度:
      63  62             52 51                               32
      +---+----------------+----------------------------------+
      | s |       exp      |           frac(51:32)            |
      +---+----------------+----------------------------------+
      31                                                      0
      +-------------------------------------------------------+
      |                     frac(31:0)                        |
      +-------------------------------------------------------+
    
    • float:s 1位、exp 的 k=8位、frac=23位,合计 32位
    • double:s 1位、exp 的 k=11位、frac=52位,合计 64位

    分类

    以 C 语言单精度为例,根据 exp 存储的位的不同,所表示的浮点数可以分成 3 中不同的情况,而最后一种情况中情况分两个变种:

    1. 规格化

      +-------------------------------------------------------+
      | s | exp!=0 & exp!=255 |         frac                  |
      +-------------------------------------------------------+
      

      这是最常见的情况,exp 的位模式既不为全 0,也不为全 1
      规格化的值有两点需要特别注意:

      1. 阶码 E 包含一定的偏置 Bias,也就是说E = exp - Bias,exp 是无符号数,Bias = 2k-1 - 1,偏置的作用是为了在规格化取值范围与非规格化取值范围之间平滑过渡

      2. 尾数 M 的值并不是 frac 所表示的小数值,实际情况是M = 1 + frac
        通常情况下,二进制整数部分通过调整小数点(也就是修改 E)来变成 1,所以 IEEE 的表示法直接将这一位的 1 省去,这样二进制小数部分就能多存储一位,提高了精度,也就是说这个 frac 隐含了开头的 1

        举个例子:假设 frac 有 5 位,现在要存储一个二进制数 b,b 的值是 0.101011(2),调整一下权重:1.01011 * 2-1(2),farc 存储的就是小数点后面的这 5 位01011

    2. 非规格化

      +-------------------------------------------------------+
      | s |     exp=0         |         frac                  |
      +-------------------------------------------------------+
      

      exp 位模式全为 0,E = exp - Bias,M = frac
      规格化数因 frac 隐含开头的 1,M >= 1,故而无法表示 0 这个数。
      当 exp 和 frac 都是 0 时,s = 1 得到 -0.0,s = 0 得到 +0.0

      • 无穷大(Infinity)
        +-------------------------------------------------------+
        | s |     exp=255       |         frac=0                |
        +-------------------------------------------------------+
        
        exp 所有位皆是 1,frac 所有位皆是 0 ,表示无穷大
        s = 1 时表示负无穷大,s = 0 时表示正无穷大
      • NaN(Not a Number)
        +-------------------------------------------------------+
        | s |     exp=255       |         frac!=0               |
        +-------------------------------------------------------+
        
        exp 所有位皆是 1,frac 的位模式不全为 0 ,表示 NaN 不是一个数

    阶码的值决定了该浮点数是规格化的、非规格化的、无穷大或者 NaN

    转换示例

    举例,将 1 个 float 数据转换为 4Byte 的二进制数据存储起来:

    float a = 10.25F;
    
                     Decimal       Binary 
    整数部分:          10    ====>  1010
    小数部分:         0.25   ====>  0.01  
    科学计数法: 1.025 * 10^1 ====>  1010.01 = 1.01001 * 2^3
    
    s = 0           ====> 0
    E = 3           ====> E 包含偏置,IEEE 用 0111 1111 = 2^7 - 1 = 127 来表示 E = 0,所以当 E = 3 时,二进制表示为: 1000 0010 = 130 = 127 + 3
    M = 1.01001     ====> 小数点前是 1,所以直接去掉,只保留小数部分,二进制表示为:0100 1000
                s      exp              frac
    10.25 ====> 0 | 1000 0010 | 01001000000000000000000
    
          ====> 0100 0001 0010 0100 0000 0000 0000 0000
    

    需要注意的 3 个地方:

    1. 小数部分:0.25 = 1/4,即 2 的 -2 次方,也就是二进制的 1 小数点左移 2 位,所以得到的二进制表示为 0.01。需要注意的是:不是所有的十进制小数都能转换为二进制小数,比如十进制的 0.3,所以只能取近似值,通过 2-n(n>0,n 为整数) 来接近 0.3

    2. 规格数:基数为 2,位数最高位为 1 的数为规格数,此时能够表示的数据精度最高。通过阶码的大小来控制小数点的位置使得尾数变为规格数的过程,称为规格化。在存储时尾数只保存了小数部分。但是规格数无法表示 0

    3. 阶码是有偏置的:2k-1 - 1

    Java 中的应用

    将给定的字节数组转换为对应的浮点数,JDK 中 Float 和 Double 均提供了对应的方法来处理这种情况,以 Float 为例,使用的方法java.lang.Float#intBitsToFloat(int bits),将参数 bits 的位模式解析为浮点数,API 中的说明:

    int s = ((bits >> 31) == 0) ? 1 : -1;
    int e = ((bits >> 23) & 0xff);
    int m = (e == 0) ?
                    (bits & 0x7fffff) << 1 :
                    (bits & 0x7fffff) | 0x800000;
    

    浮点结果等于算术表达式 s·m·2e-150 的值
    对应给定的浮点数,进行四舍五入可以使用java.math.BigDecimal#setScale(int, int)方法,可以设定保留小数的位数,以及四舍五入的方式

    class Scratch {
        public static void main(String[] args) {
            int intbis = 0B0100_0001_0010_0100_0000_0000_0000_0000;
            System.out.println(Integer.toBinaryString(intbis));
            System.out.println(Float.intBitsToFloat(intbis));
            System.out.println(Integer.toBinaryString(Float.floatToIntBits(10.25F)));
        }
    }
    

    参考

  • 相关阅读:
    synchronized原理
    实现二叉搜索树
    2.oracle内存结构和后台进程02
    1.Oracle的内存结构和后台进程01
    15.Oracle的用户、Schema、数据库、表空间、数据文件的相互关系
    14.oracle的归档日志
    8.事务是隔离还是不隔离?
    7.行锁功过:怎么减少行锁对性能的影响?
    50.Mysql不完全恢复之innodb_force_recovery参数
    49.Mysql命令之Mysqlcheck
  • 原文地址:https://www.cnblogs.com/magexi/p/9202172.html
Copyright © 2020-2023  润新知