# J.3 浮点数
<!-- source: p1236 -->

很多应用需要表示非整数。方法不止一种：可以采用定点表示，用整数算术运算，只需把二进制小数点想象成位于最低有效位右侧以外的某个位置。两个这样的数相加可用整数加法，乘法则需要额外移位。也有人提出存储一个数的对数，以对数相加代替乘法，或者用整数对 \((a,b)\) 表示分数 \(a/b\)。然而，得到广泛采用的非整数表示只有**浮点表示**。它把计算机字分为指数和有效数（significand）两部分。例如，指数为 −3、有效数为 1.5，可以表示 \(1.5\times2^{-3}=0.1875\)。统一一种表示的好处很明显：数值分析人员可以建立高质量的软件库，计算机设计者可以发展高性能硬件实现技术，硬件厂商也可以构建标准化加速器。浮点表示已经占据主导地位，其他表示似乎不大可能再得到广泛采用。

<!-- source: p1237 -->

浮点指令的语义不像指令集其他部分那样明确。过去，不同计算机系列的浮点行为差别很大，涉及指数与有效数的位数、指数范围、舍入方式，以及下溢和上溢等异常情况的处理。计算机体系结构教材曾经需要就这些细节逐项提供建议，幸运的是，如今已无此必要：业界正迅速趋同于 IEEE 754-1985 标准规定的格式，它同时也是国际标准 IEC 559。采用统一浮点形式的好处，与统一采用浮点而非其他非整数表示的好处类似。

IEEE 算术与此前许多算术系统的主要区别如下：

1. 把恰好处于两个浮点数中间的结果舍入到最近浮点数时，选择末位为偶数的那个。
2. 包含特殊值 NaN、\(+\infty\) 和 \(-\infty\)。
3. 用非规格化数（denormal）表示小于 \(1.0\times2^{E_{\min}}\) 的计算结果。
4. 默认舍入到最近值，但还提供另外三种舍入模式。
5. 提供完善的异常处理机制。

第一点需要解释。两个浮点数运算的结果，通常不能由另一个浮点数精确表示。例如，在十进制、两位有效数字的系统中，\(6.1\times0.5=3.05\)，必须舍入到两位：应该是 3.0 还是 3.1？IEEE 标准把这种中点情况舍入到末位为偶数的数，因此 3.05 舍入为 3.0。标准共有四种舍入模式：默认的舍入到最近值，在中点时取偶数；另外三种分别向 0、向 \(+\infty\)、向 \(-\infty\) 舍入。

其余区别将在后续各节展开。进一步阅读可参考 IEEE [1985]、Cody 等 [1984] 和 Goldberg [1991]。

## 特殊值与非规格化数

标准最醒目的特点，或许是在除以 0、对负数开平方等异常情况下，默认仍允许计算继续。例如，负数平方根的结果是 NaN（Not a Number，非数），它是一种不表示普通数值的位模式。设一个求零点程序接收函数 F，并在若干位置求值，以确定其零点。

<!-- source: p1238 -->

如果程序不慎探测到 F 的定义域外，F 很可能引发异常。要处理这种情况，传统的求零点程序高度依赖语言和操作系统，因为它必须考虑操作系统如何响应异常，以及这种响应如何映射回程序设计语言。在 IEEE 算术中，很容易写出能处理这种情况、又可跨系统运行的求零点程序：每次计算 F 后检查返回值是否为 NaN；若是，就知道探测到了定义域外。

IEEE 算术规定，运算输入若为 NaN，输出也是 NaN，例如 \(3+\mathrm{NaN}=\mathrm{NaN}\)。因此，编写能够接受 NaN 参数的浮点子程序，通常无需增加特殊检查。比如用

$$
\arccos x=2\arctan\sqrt{\frac{1-x}{1+x}}
$$

计算反余弦，只要 arctan 能正确处理 NaN，arccos 也会自动正确处理。因为若 \(x\) 为 NaN，那么 \(1+x\)、\(1-x\)、\((1+x)/(1-x)\)，以及 \(\sqrt{(1-x)/(1+x)}\) 也都是 NaN，无需逐一检查。

> 译注：此处列举的商在原文中写成 \((1+x)/(1-x)\)，与公式根号内的分子、分母次序相反；两者都会传播 NaN，所以不影响本段说明。

虽然 \(\sqrt{-1}\) 是 NaN，\(1/0\) 却不是，而是另一个特殊值 \(+\infty\)。标准定义了正、负无穷的算术规则，例如 \(1/\infty=0\)。上述反余弦公式也说明了它的作用：当 \(x\to\infty\) 时，\(\arctan x\to\pi/2\)，因此自然可定义 \(\arctan(\infty)=\pi/2\)。这样，\(\arccos(-1)\) 自动得到正确结果 \(2\arctan(\infty)=\pi\)。

最后一类特殊值是非规格化数。很多浮点系统中，若最小指数为 \(E_{\min}\)，则小于 \(1.0\times2^{E_{\min}}\) 的数无法表示，运算产生这种结果时会直接置零。IEEE 标准则用小于 1 的有效数表示它们，称为**渐进下溢**（gradual underflow）：数值降到 \(2^{E_{\min}}\) 以下时，精度逐渐减少，直到所有有效位都被移出才变为 0。例如，在四位有效数字的十进制系统中，令 \(x=1.234\times10^{E_{\min}}\)，则 \(x/10\) 舍入为 \(0.123\times10^{E_{\min}}\)，损失一位精度；\(x/100\) 舍入为 \(0.012\times10^{E_{\min}}\)，\(x/1000\) 为 \(0.001\times10^{E_{\min}}\)，直到 \(x/10000\) 才足够小而舍入为 0。

非规格化数保留了 \(x=y\iff x-y=0\) 这样的熟悉性质，使小数值运算更可预测。例如，仍用四位有效数字的十进制系统，若 \(x=1.256\times10^{E_{\min}}\)、\(y=1.234\times10^{E_{\min}}\)，则 \(x-y=0.022\times10^{E_{\min}}\)。直接置零的系统会把它变成 0，于是 \(x\ne y\)，计算结果却是 \(x-y=0\)。渐进下溢不会如此：这个差是可表示的非规格化数，所以减法结果精确。

<!-- source: p1239 -->

## 浮点数的表示

IEEE 单精度用 32 位存储：1 位符号、8 位指数、23 位小数部分。指数是有符号数，采用 J.2 节介绍的移码表示，偏置为 127。**偏置指数**指第 1 至第 8 位的指数域中存储的无符号数；**无偏置指数**，或简称指数，指 2 的实际幂次。小数域表示小于 1 的数，而浮点数的有效数是 1 加上这部分。若偏置指数为 \(e\)，小数域为 \(f\)，则其数值大小为 \(1.f\times2^{e-127}\)。

**例题**　下列 32 位字表示什么单精度数？

```text
1 10000001 01000000000000000000000
```

**解答**　指数域作为无符号数为 129，因此实际指数是 \(129-127=2\)。小数部分为 \(.01_2=.25\)，有效数为 1.25。符号位为 1，所以表示 \(-1.25\times2^2=-5\)。

不要把小数部分（本例为 .25）与有效数混淆；有效数是 1 加小数部分。\(1.f\) 的首位 1 不出现在存储表示中，是**隐含位**。对 IEEE 格式数运算时，通常先把小数部分**解包**（unpack），也就是将隐含的 1 显式补出。

| 参数 | 单精度 | 扩展单精度 | 双精度 | 扩展双精度 |
|---|---:|---:|---:|---:|
| \(p\)，精度位数 | 24 | ≥32 | 53 | ≥64 |
| \(E_{\max}\) | 127 | ≥1023 | 1023 | ≥16383 |
| \(E_{\min}\) | −126 | ≤−1022 | −1022 | ≤−16382 |
| 指数偏置 | 127 |  | 1023 |  |

图 J.7　IEEE 754 浮点标准的格式参数。第一行给出有效数的位数；空白项为标准未指定的参数。
{#fig-J.7 .wide .expand num="J.7"}

单精度指数范围为 −126 到 127，对应偏置指数 1 到 254。偏置指数 0 和 255 用于特殊值，如图 J.8。偏置指数为 255 时，小数域为 0 表示无穷，非零表示 NaN，因此 NaN 并不只有一种位模式。

<!-- source: p1240 -->

| 指数 | 小数域 | 表示的值 |
|---|---|---|
| \(e=E_{\min}-1\) | \(f=0\) | \(\pm0\) |
| \(e=E_{\min}-1\) | \(f\ne0\) | \(0.f\times2^{E_{\min}}\) |
| \(E_{\min}\le e\le E_{\max}\) | 任意 | \(1.f\times2^e\) |
| \(e=E_{\max}+1\) | \(f=0\) | \(\pm\infty\) |
| \(e=E_{\max}+1\) | \(f\ne0\) | NaN |

图 J.8　特殊值的表示。指数域编码对应的指数落在 \(E_{\min}\le e\le E_{\max}\) 之外时，按表中规则作特殊解释。数值的正负另由符号位给出。
{#fig-J.8 .wide .expand num="J.8"}

偏置指数与小数域都为 0 时，表示 0。由于普通数含有隐含首位 1，有效数总是不小于 1，因此必须另作约定来表示 0。非规格化数则用指数域为 0 的字表示 \(0.f\times2^{E_{\min}}\)。

IEEE 标准与多数浮点格式采用偏置指数，主要因为这能使非负浮点数的编码顺序与整数顺序一致，从而可用整数比较器比较浮点数大小。另一个相关好处是，全 0 的字表示数值 0。缺点是指数相加稍显麻烦：必须从偏置指数之和中减去一次偏置。
