关于 IEEE 754 浮点数

首先讲讲数在计算机上的表示

数的机器表示

众所周知 机器很笨 它是不认识什么 有符号数 和 无符号数的 它只认识 二进制串 那么在计算机上 是如何表示 有符号数和无符号数的呢?

计算机不区分有符号数和无符号数

首先在计算机眼中 它确确实实只认识 二进制串 二进制串所表示的数 是有符号数还是无符号数 取决于 用户(编码者)怎么看 当你认为 0xffff 为正数时 它就是 65535 当你认为 0xffff 为负数时 就为 -1

其次 在计算机中 它会将送进来的数 统一用补码计算 使用补码计算就不用区分 是有符号数还是无符号数 因为补码的加减法的规则相同

比如 一个二进制为: 1000 0001 当你对其 +1 后 则为 1000 0010

  • 若你将 1000 0001 当做无符号数 则为 129 + 1 = 130 此时 1000 0010 为无符号数的 130
  • 若视为 有符号数 则为 -127 + 1 = -126 此时 1000 0010 为有符号数的 -126

也就是说 补码的存在 使得CPU 计算有符号数和无符号数的 加法过程统一 此外为了方便用户 CPU计算完成后 还会设置 eflags 状态寄存器 根据状态位即可同时知道 有符号数和无符号数的运算结果

至于 最高有效位 是 0 还是 1 来表示的正负数 是程序员自己认为的 机器不认识 也不懂 它只知道执行指令

浮点数的表示

根据 IEEE 754 标准
二进制浮点数V 可以表示为 V = (-1)^S * M * 2^E
计算机中浮点数二进制存储结构可以表示为 [sign| exp | frac ]
其中 s 为符号位 和整数的二进制表示不同的是 它只表示符号位 不带任何权重

  • 单精度浮点数: exp宽为 8位 frac宽为 23位
  • 双精度浮点数: exp宽为 11位 frac宽为52位

规格化浮点数

exp != 000...0 && exp != 111...1 则为规格化浮点数

此时 E + Bias = Exp

  • Bias取值
    • Bias = 2^e-1 e = exp位数
    • 单精度数: 127
    • 双精度数: 1023

之所以 E 要 Exp 减去 Bias 的原因是 E是一个 unsigned int 而科学计数法中的 E 是有正有负的 因此 E要减去一个中间数

frac域的第一位隐含为 1 在 32位浮点数中 frac的宽为 23位 省略了第一位以后 则多了一位 可以保存24位

规格化浮点数实例

Float F = 15213.0

  • 15213D = 11101101101101B = 1.1101101101101B X 2^13
  • M = 1.1101101101101B
  • frac= 1101101101101B(规格化浮点数 舍弃第一位)
  • E = 13
  • Bias = 127(因为是32位浮点数)
  • Exp = 13 + 127 = 140 = 10001100B

最终在机器中表示为 [0|1000 1100|1101 1011 0110 1]

非规格化浮点数

exp = 000...0

  • E = -Bias + 1 Bias = 2^e-1 e = exp位数
  • M = 0.xxxxx
  • 当 exp = 000…0, frac = 000…0
    • 表示 0
    • 有 +0 与 -0
  • 当 exp = 000…0, frac != 000…0
    • 表示 非常接近于 0
    • 会逐步丧失精度
  • 当 exp = 111…1, frac = 000…0
    • 表示无穷
    • 1.0/0.0 = +无穷 -1.0/0.0 = -无穷
  • 当 exp = 111…1, frac != 000…0
    • NaN
举个例子 Int to Float
1
2
int num = 8;
float* pfloat = #

*pfloat 的值为多少?

1
2
3
4
5
6
7
8
首先 num 为 0x00000008
第一位 为符号位 为 0 因为是正数
其次 符号位后面八位 为exp域 为 0000 0000 全0 则 E = -Bias + 1 = 2^e-1 + 1 = -126
最后 M 23位 = 000 0000 0000 0000 0000 1000
(省略掉最前面的0 因为是非规格化浮点数 若为规格化浮点数则省略最前面的1)
得到
V = (-1)^S * M * 2^E = 0x0.00000000000000000001000 * 2^(-126) = 1.000 * 2^(-146)
很接近于0 因此 *pfloat 为 0.000000
再举个例子 Float to Int
1
2
3
int num = 8;
float* pfloat = #
*pfloat = 8.0;
1
num 的值为多少?
1
2
3
4
首先 8.0 是一个正数 符号位 为 0
其次 8.0 = 1000.0B = 1.000 * 2^3 E = 3 exp = E + Bias = 3 + 127 = 130 = 1000 0010B
M = 000 补到 23位
因此 在二进制为 [0|1000 0010|000 0000 0000 0000 0000] 转换为十进制 为 1107296256