浮点数的表示

IEEE 754 二进制浮点数由符号位、阶码和尾数字段组成。以单精度 float32 为例:

S(符号位,1 位) | E(阶码,8 位) | F(尾数的小数部分,23 位)

对于规格化数,阶码 EE 满足 1≤E≤2541\le E\le254,其数值为:

(−1)S×(1.F)2×2E−127(-1)^S\times(1.F)_2\times2^{E-127}

其中,EE 是存储的偏置阶码,E−127E-127 才是真实指数;1.F1.F 中的最高位 1 是隐藏位,不实际存入尾数字段。因此,float32 规格化数有 24 位二进制有效数字。

格式 符号位 阶码位数 小数位数 偏置值(bias) 规格化数的有效位数
float32 1 8 23 127 24
float64 1 11 52 1023 53

隐藏位为 1 的规则只适用于规格化数。 非规格化数(subnormal)的尾数为 (0.F)2(0.F)_2,用于表示接近零的极小数。格式说明

加减运算流程

重点:对阶 → 尾数加减 → 规格化 → 舍入 → 检查溢出与特殊情况。

减法可以统一理解为 A−B=A+(−B)A-B=A+(-B)。将减法转为加法、完成对阶后:

  • 同号: 尾数相加,保留共同的符号。
  • 异号: 尾数的绝对值相减,非零结果的符号由绝对值较大的操作数决定。
  • 完全抵消: 结果为零,其符号按 IEEE 754 的有符号零规则处理。

1. 对阶

小指数向大指数对齐: 将指数较小的数的尾数右移,移动位数等于指数差,同时增大其指数,保持数值不变。

例如,将 1.1012×231.101_2\times2^3 与 1.0012×211.001_2\times2^1 对阶:

1.0012×21=0.010012×231.001_2\times2^1=0.01001_2\times2^3

于是,两个尾数变成同一指数下的 1.1010021.10100_2 和 0.0100120.01001_2。

对阶时应保留足够的辅助信息供后续舍入使用。由于最终可保存的位数有限,较小数的贡献仍可能在舍入后消失,出现“大数吃小数”。

2. 尾数加减

对阶完成后,按符号进行二进制尾数运算。例如:

(1.101002+0.010012)×23=1.111012×23(1.10100_2+0.01001_2)\times2^3 =1.11101_2\times2^3

减法可能使高位抵消,例如:

(1.1012−1.0012)×23=0.1002×23(1.101_2-1.001_2)\times2^3 =0.100_2\times2^3

后者尚未规格化,需要继续调整尾数与指数。

3. 规格化

非零规格化数应写成 1.xxxxx2×2e1.xxxxx_2\times2^e 的形式。

情况 操作 示例
尾数出现进位,整数部分为 10 或 11 尾数右移一位,指数加 1 11.02×23=1.102×2411.0_2\times2^3=1.10_2\times2^4
尾数非零,但整数部分为 0 尾数左移,每移一位,指数减 1 0.1002×23=1.0002×220.100_2\times2^3=1.000_2\times2^2

左移时若达到最小规格化指数仍无法得到前导 1,应按非规格化数处理;结果为零时不再尝试规格化。

4. 舍入

尾数位数有限,超出目标精度的部分需要按舍入规则处理。为正确判断舍入方向,运算中常保留 GRS 位:

辅助位 含义
G:Guard bit,保护位 最低保留位之后的第一位
R:Round bit,舍入位 G 之后的第二位
S:Sticky bit,粘滞位 R 之后所有位的逻辑或;只要其中有一个 1,S 就为 1

这里的 S 是粘滞位,与浮点数格式中的符号位 S 含义不同。

例如:

保留部分   | G | R | 后续位
101101...  | 1 | 0 | 001010...

后续位中存在 1,所以粘滞位为 1,最终 GRS = 101。

默认舍入方式:就近舍入,中间值取偶(round to nearest, ties to even)。 选择最接近精确结果的可表示数;若恰好位于两个可表示数的正中间,则选择最低保留位为 0 的那个数。

在这种舍入方式下,可根据 GRS 判断是否向保留部分加 1:

  1. GRS = 100: 刚好在中间,选择最低保留位为 0 的结果;原最低保留位为 0 时不进位,为 1 时进位。
  2. GRS 大于 100: 超过中间值,进位。
  3. GRS 小于 100: 未到中间值,不进位。

借用“只保留整数”的例子理解:

2.4 → 2    2.6 → 3
2.5 → 2    3.5 → 4

常见的其他舍入方式还有向 +∞+\infty、向 −∞-\infty 和向 0 舍入。

舍入也可能使尾数产生进位,此时还要再做一次右规,并检查指数是否超出范围。

5. 检查溢出与特殊情况

  • 上溢: 结果超出有限数的可表示范围时,根据符号和舍入模式得到无穷大或最大有限数。
  • 极小结果: 可能表示为非规格化数,或舍入为零,不能一概直接置零。
  • 特殊值: IEEE 754 还包含 +0+0、−0-0、+∞+\infty、−∞-\infty 和 NaN;它们应按特殊规则处理,例如 +∞+(−∞)+\infty+(-\infty) 得到 NaN。

以上五步是理解普通有限数加减的主线;实际实现还会在运算前识别特殊值。

完整例子:1.5 + 0.75

先将十进制操作数写成二进制规格化形式:

1.5=1.12×20,0.75=1.12×2−11.5=1.1_2\times2^0,\qquad 0.75=1.1_2\times2^{-1}
  1. 对阶: 将 0.750.75 的尾数右移一位,得到 0.112×200.11_2\times2^0。
  2. 尾数相加: 1.102+0.112=10.0121.10_2+0.11_2=10.01_2。
  3. 规格化: 10.012×20=1.0012×2110.01_2\times2^0=1.001_2\times2^1。
  4. 舍入与检查: 结果可以精确表示,无需改变尾数,也没有溢出。

因此:

1.5+0.75=1.0012×21=2.25\boxed{1.5+0.75=1.001_2\times2^1=2.25}

常见精度问题

大数吃小数

较小数的尾数在对阶时右移;若其贡献不足以改变最终舍入结果,就可能出现:

很大的数 + 很小的数 = 很大的数

这与有限精度和舍入有关,并非数学上的相等。

灾难性消减

两个非常接近的数相减,高位有效数字会大量抵消。例如,1.234567−1.2345661.234567-1.234566 的精确差值只有 0.0000010.000001。

若操作数已带有表示误差或先前运算的舍入误差,这些误差可能在差值中占据很大比例,使结果的相对误差显著放大。 因此,相近数相减并不一定产生新的舍入误差,关键是抵消后暴露了已有误差。消减误差说明

加法不一定满足结合律

每一步运算都可能舍入,因此,浮点运算中的 (a+b)+c(a+b)+c 与 a+(b+c)a+(b+c) 不保证相等,改变计算顺序可能改变最终结果。

补充:数组对齐

常规数组的自然对齐要求由数组元素的类型决定,不由数组长度决定。