浮点数在内存中的存储结构
基础概念
- 十进制整数转二进制为除 2 取余, 逆序排列
- 十进制小数转二进制为乘 2 取整, 顺序排列
- 二进制转十进制是每位加权求和
-
n 进制的科学计数法 $a*n^b$ ($0<|a|<n$) 其中 a 和 b 都是 n 进制数字, 类比十进制, b 表示 n 进制指数 (小数点移动的位数)
例: 二进制 $10000_2=1*2^{100_2}$, 指数相当于十进制的 4, 也就是小数点向右移动 4 位
浮点数国际标准 IEEE 754
国际标准 IEEE 754 标准使用一个三元组 ${S, E, M}$ 表示一个浮点数N
-
S符号位, 0 和 1 分别表示正和负 -
E阶码, 用移码表示 -
M尾码, 按照 IEEE 754 标准存储浮点数需要先规格化, 最后的尾码一定是 $(1.xxxxx)_2$ 的二进制格式, 第一位一定是1, 因此尾码存储时舍去了第1位的1, 只保存了二进制小数部分, 这样可以多表示一位
浮点数的规格化
同一个浮点数的表示规格并不统一, 比如 $(1.11)_2\times 2^0 = (0.111)_2\times 2^1 = (0.0111)_2\times 2^2$.
为了数据的表示精度, 就必须充分利用尾码的有效位数, IEEE 754 标准中当尾码大小不是 $(1.xxxx)_2$ 格式时就左右移动小数点并同时修改阶码大小, 直到达到要求, 该过程称为浮点数规格化
浮点数的表示
浮点数中的指数 e 可能有正有负, 为了方便表示, 将 e 增加一个固定的偏移量得到移码 E 作为阶码
移码 E 在浮点数中是一段二进制的无符号整数, 依据二进制位计算出 E 的值再去掉固定偏移量, 即可得到指数 e 的值
平常使用最多的是单精度(32位)和双精度(64位)浮点数, 尾码部分默认小数点前的一位1省去了, 后面不再赘述
单精度浮点数
符号位(S) 1位, 阶码(E) 8位, 尾码(M) 23位
其中阶码偏移量为 127 (0x7F), 尾码仅表示小数部分, 前面的1和小数点省略
真值表示: $X=(-1)^S\times (1.M) \times 2^{E - 127}$ $e=E-127$
由于 $2^{23}=8388608$ 最大可表示精度为 7 位, 但不能表示所有的 7 位数, 可保证 6 位精度
双精度浮点数
符号位(S) 1位, 阶码(E) 11位, 尾码(M) 52位
其中阶码偏移量为 1023 (0x3FF), 尾码仅表示小数部分, 前面的1和小数点省略
真值表示: $X=(-1)^S\times (1.M) \times 2^{E - 1023}$ $e=E-1023$
由于 $2^{52}=4503599627370496$ 最大表示精度为 16 位, 不能表示所有 16 位数字, 保证精度为 15 位
特殊值
在特殊情况下, 浮点数的计算不再按照常规的方式计算, 而是直接表示特殊的真值, 以下对于单精度和双精度都一样
-
真值0: 当阶码E全0, 且尾数M全0时, 表示真值X为0, 由于符号位不同, 所以有正负0两种表示
-
无穷大: 当阶码E全1, 且尾码M全0时, 表示真值为无穷大, 符号位不同可以表示正负无穷大
-
NaN: 当阶码全1, 且尾码M非全0时, 全都表示NaN, NaN没有正负之分, 符号位0或1都是NaN
编码测试
编写一个函数, 用于将数值以二进制打印
|
|
先看一下1.0的结果 printBits(@as(f32, 1.0)) 结果为 1 f32 : 00111111 10000000 00000000 00000000
其中S=0, $E=(01111111)_2=127$, M=0
也就是 $1.0=(-1)^S\times (1.M) \times 2^{E-127}=1\times 1.0 \times 2^0$
再试试0.5, printBits(@as(f32, 0.5)) 结果为 0.5 f32 : 00111111 00000000 00000000 00000000 与1.0的区别仅仅是阶码小了1
真值计算 $0.5=(-1)^(0)\times (1.0) \times 2^{126-127}=1\times 1.0 \times 2^{-1}$
用源码验证一下所有特殊值是否如我们所想
|
|
输出结果如下
|
|
可以发现, 无穷大的阶码全1, 尾码全0, 改变符号位可得到正/负无穷大; NaN阶码全1, 尾码非0, 改变符号位依然是NaN, 试了一下在无穷大的基础上, 将尾码倒数第4位(随机一位)设置为1, 输出为NaN, 符合预期