J.3 浮点数
很多应用需要表示非整数。方法不止一种:可以采用定点表示,用整数算术运算,只需把二进制小数点想象成位于最低有效位右侧以外的某个位置。两个这样的数相加可用整数加法,乘法则需要额外移位。也有人提出存储一个数的对数,以对数相加代替乘法,或者用整数对 表示分数 。然而,得到广泛采用的非整数表示只有浮点表示。它把计算机字分为指数和有效数(significand)两部分。例如,指数为 −3、有效数为 1.5,可以表示 。统一一种表示的好处很明显:数值分析人员可以建立高质量的软件库,计算机设计者可以发展高性能硬件实现技术,硬件厂商也可以构建标准化加速器。浮点表示已经占据主导地位,其他表示似乎不大可能再得到广泛采用。
浮点指令的语义不像指令集其他部分那样明确。过去,不同计算机系列的浮点行为差别很大,涉及指数与有效数的位数、指数范围、舍入方式,以及下溢和上溢等异常情况的处理。计算机体系结构教材曾经需要就这些细节逐项提供建议,幸运的是,如今已无此必要:业界正迅速趋同于 IEEE 754-1985 标准规定的格式,它同时也是国际标准 IEC 559。采用统一浮点形式的好处,与统一采用浮点而非其他非整数表示的好处类似。
IEEE 算术与此前许多算术系统的主要区别如下:
- 把恰好处于两个浮点数中间的结果舍入到最近浮点数时,选择末位为偶数的那个。
- 包含特殊值 NaN、 和 。
- 用非规格化数(denormal)表示小于 的计算结果。
- 默认舍入到最近值,但还提供另外三种舍入模式。
- 提供完善的异常处理机制。
第一点需要解释。两个浮点数运算的结果,通常不能由另一个浮点数精确表示。例如,在十进制、两位有效数字的系统中,,必须舍入到两位:应该是 3.0 还是 3.1?IEEE 标准把这种中点情况舍入到末位为偶数的数,因此 3.05 舍入为 3.0。标准共有四种舍入模式:默认的舍入到最近值,在中点时取偶数;另外三种分别向 0、向 、向 舍入。
其余区别将在后续各节展开。进一步阅读可参考 IEEE [1985]、Cody 等 [1984] 和 Goldberg [1991]。
特殊值与非规格化数
标准最醒目的特点,或许是在除以 0、对负数开平方等异常情况下,默认仍允许计算继续。例如,负数平方根的结果是 NaN(Not a Number,非数),它是一种不表示普通数值的位模式。设一个求零点程序接收函数 F,并在若干位置求值,以确定其零点。
如果程序不慎探测到 F 的定义域外,F 很可能引发异常。要处理这种情况,传统的求零点程序高度依赖语言和操作系统,因为它必须考虑操作系统如何响应异常,以及这种响应如何映射回程序设计语言。在 IEEE 算术中,很容易写出能处理这种情况、又可跨系统运行的求零点程序:每次计算 F 后检查返回值是否为 NaN;若是,就知道探测到了定义域外。
IEEE 算术规定,运算输入若为 NaN,输出也是 NaN,例如 。因此,编写能够接受 NaN 参数的浮点子程序,通常无需增加特殊检查。比如用
计算反余弦,只要 arctan 能正确处理 NaN,arccos 也会自动正确处理。因为若 为 NaN,那么 、、,以及 也都是 NaN,无需逐一检查。
译注:此处列举的商在原文中写成 ,与公式根号内的分子、分母次序相反;两者都会传播 NaN,所以不影响本段说明。
虽然 是 NaN, 却不是,而是另一个特殊值 。标准定义了正、负无穷的算术规则,例如 。上述反余弦公式也说明了它的作用:当 时,,因此自然可定义 。这样, 自动得到正确结果 。
最后一类特殊值是非规格化数。很多浮点系统中,若最小指数为 ,则小于 的数无法表示,运算产生这种结果时会直接置零。IEEE 标准则用小于 1 的有效数表示它们,称为渐进下溢(gradual underflow):数值降到 以下时,精度逐渐减少,直到所有有效位都被移出才变为 0。例如,在四位有效数字的十进制系统中,令 ,则 舍入为 ,损失一位精度; 舍入为 , 为 ,直到 才足够小而舍入为 0。
非规格化数保留了 这样的熟悉性质,使小数值运算更可预测。例如,仍用四位有效数字的十进制系统,若 、,则 。直接置零的系统会把它变成 0,于是 ,计算结果却是 。渐进下溢不会如此:这个差是可表示的非规格化数,所以减法结果精确。
浮点数的表示
IEEE 单精度用 32 位存储:1 位符号、8 位指数、23 位小数部分。指数是有符号数,采用 J.2 节介绍的移码表示,偏置为 127。偏置指数指第 1 至第 8 位的指数域中存储的无符号数;无偏置指数,或简称指数,指 2 的实际幂次。小数域表示小于 1 的数,而浮点数的有效数是 1 加上这部分。若偏置指数为 ,小数域为 ,则其数值大小为 。
例题 下列 32 位字表示什么单精度数?
解答 指数域作为无符号数为 129,因此实际指数是 。小数部分为 ,有效数为 1.25。符号位为 1,所以表示 。
不要把小数部分(本例为 .25)与有效数混淆;有效数是 1 加小数部分。 的首位 1 不出现在存储表示中,是隐含位。对 IEEE 格式数运算时,通常先把小数部分解包(unpack),也就是将隐含的 1 显式补出。
| 参数 | 单精度 | 扩展单精度 | 双精度 | 扩展双精度 |
|---|---|---|---|---|
| ,精度位数 | 24 | ≥32 | 53 | ≥64 |
| 127 | ≥1023 | 1023 | ≥16383 | |
| −126 | ≤−1022 | −1022 | ≤−16382 | |
| 指数偏置 | 127 | 1023 |
单精度指数范围为 −126 到 127,对应偏置指数 1 到 254。偏置指数 0 和 255 用于特殊值,如图 J.8。偏置指数为 255 时,小数域为 0 表示无穷,非零表示 NaN,因此 NaN 并不只有一种位模式。
| 指数 | 小数域 | 表示的值 |
|---|---|---|
| 任意 | ||
| NaN |
偏置指数与小数域都为 0 时,表示 0。由于普通数含有隐含首位 1,有效数总是不小于 1,因此必须另作约定来表示 0。非规格化数则用指数域为 0 的字表示 。
IEEE 标准与多数浮点格式采用偏置指数,主要因为这能使非负浮点数的编码顺序与整数顺序一致,从而可用整数比较器比较浮点数大小。另一个相关好处是,全 0 的字表示数值 0。缺点是指数相加稍显麻烦:必须从偏置指数之和中减去一次偏置。