跳转到主要内容

J.3 浮点数

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

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

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

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

第一点需要解释。两个浮点数运算的结果,通常不能由另一个浮点数精确表示。例如,在十进制、两位有效数字的系统中,6.1×0.5=3.056.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,并在若干位置求值,以确定其零点。

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

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

arccos⁡x=2arctan⁡1−x1+x \arccos x=2\arctan\sqrt{\frac{1-x}{1+x}}

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

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

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

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

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

浮点数的表示

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

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

1 10000001 01000000000000000000000

解答 指数域作为无符号数为 129,因此实际指数是 129−127=2129-127=2。小数部分为 .012=.25.01_2=.25,有效数为 1.25。符号位为 1,所以表示 −1.25×22=−5-1.25\times2^2=-5。

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

参数单精度扩展单精度双精度扩展双精度
pp,精度位数24≥3253≥64
Emax⁡E_{\max}127≥10231023≥16383
Emin⁡E_{\min}−126≤−1022−1022≤−16382
指数偏置1271023

图 J.7 IEEE 754 浮点标准的格式参数。第一行给出有效数的位数;空白项为标准未指定的参数。

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

指数小数域表示的值
e=Emin⁡−1e=E_{\min}-1f=0f=0±0\pm0
e=Emin⁡−1e=E_{\min}-1f≠0f\ne00.f×2Emin⁡0.f\times2^{E_{\min}}
Emin⁡≤e≤Emax⁡E_{\min}\le e\le E_{\max}任意1.f×2e1.f\times2^e
e=Emax⁡+1e=E_{\max}+1f=0f=0±∞\pm\infty
e=Emax⁡+1e=E_{\max}+1f≠0f\ne0NaN

图 J.8 特殊值的表示。指数域编码对应的指数落在 Emin⁡≤e≤Emax⁡E_{\min}\le e\le E_{\max} 之外时,按表中规则作特殊解释。数值的正负另由符号位给出。

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

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