数值计算方法:浮点数表示和浮点运算

Floating Point Arithmetic

Posted by R1NG on February 10, 2023 Viewed Times

浮点数表示和浮点运算、

本节介绍一个重要的浮点数表示和运算标准: IEEE754, 并讨论关于浮点数表示, 运算, 精度与误差分析的一系列问题.

IEEE754标准

在上一节中, 我们介绍了定点实数表示法. 定点实数表示虽然简单, 但存在表示范围和表示精度极为有限的缺陷. 同时, 定点实数表示法无法表示大小为 无穷 的数, 进而无法用于描述 相对误差. 为了解决这个缺陷, 我们就需要一种可靠的, 能够同时兼顾高精度和广表示范围并具有表示 “无穷数” 能力的实数表示标准, 这就是 IEEE754.

任何数字在计算机中均使用二进制数串表示, 因此 IEEE754 标准中将描述实数的二进制数串切分为了三个 固定长度 的部分: 符号位, 指数表示尾数表示.

要理解 IEEE754 如此定义的原因, 就需要首先理解含符号二进制实数的结构. 不难得知: 含符号二进制实数由符号, 整数位和小数位组成, 类比科学记数法, 可知它能被转化为: 符号 ($+1 ~ /-1$), 整数部分, 与小数点偏移量的乘积. 本质上, 指数表示部分所存储的就是 (经过偏移的) 小数点偏移量, 尾数表示部分存储的是 (经过另一种偏移后的) 整数位. 我们将在下面解释, 为什么要将小数点偏移量和整数位都进行不同的偏移.

浮点数的基本表示法

结合上面的描述, 我们可知浮点数在计算机内存中的实际表示结构基本上如下图所示:

20230211103550

符号位的表示原理不言自明, 我们下面首先讨论指数位 (指数表示部分, 也就是上面所说的小数点偏移量) 的表示方式.

指数部分的定义和表示

首先可知, 指数位被有限的二进制位所表示. 对于给定的 $n$ 位空间, 若使用二进制补码表示法, 可表示的范围是 $-2^{n-1}$ 到 $2^{n-1}-1$, 若使用无符号二进制整数表示法, 表示范围在 $0$ 到 $2^n-1$. 由于如果使用二进制补码表示法, 在后续计算时比较不同浮点数的指数位大小时会存在困难, 同时我们还希望 让指数位可以表示正数和负数, 使浮点数的表示范围可以包括极小和极大的数, 因此规定, 实际上指数位可以表示的小数点偏移量范围是 $-2^{n-1}$ 到 $2^{n-1}-1$, 但在存储时首先需要加上大小为 $2^{n-1}-1$ 的 offset 确保它们永远在 $0$ 到 $2^{n}-1$ 的范围内, 也就是说永远保存为非负的形式.

需要注意的是, IEEE754 标准中指数位的 实际表示范围 是 $-2^{n-1}+1$ 到 $2^{n-1}$, 为了确保所存储的, 经过偏移的指数位恒为非负数而加上的偏移量大小为 $2^{n-1}-1$.

同时, 为了表示一些 特别的数, 指数位中实际可表示的 最小和最大的数: $-2^{n-1}+1$ 和 $2^{n-1}$ 分别被划分给了 $0$ 和 无穷与 NaN 的表示. 因此实际上:

对于指数位只有 $n$ 个二进制位表示的情况, 去除划分给特殊数 ($0$, NaN 和无穷)及偏移量后, 对于正常数, 实际可表示的指数范围在 $-(2^{n-1}-1) + 1 = -2^{n-1}+2$ 到 $2^{n-1}-1$ 范围内.

如有不信, 可以基于下列的表自行核查:

20230211112858

20230211112908

尾数部分的定义和表示

为尾数部分分配的二进制位数量 $n$ 再加上 $1$ 就是表示尾数的 精度. 为了 避免相同的数存在多种不同的表示方式, 任何浮点数都需以 Normalized Form 存储: 规定尾数 $m$ 的范围必须满足

\[2^{p-1} \leqslant m \leqslant 2^{p}-1\]

由此可知, $m$ 的 首位 (也就是从左往右数的第一位) 要么是 $1$, 要么是 $0$. 若它是 $1$, 则为最常见的 $1$ 则称表示的数为 normal 的, 反之记为 subnormal. 由于 normal 最为常见, 因此首位一般被略去不记. 因为 首位被略去, 剩下的就是 “尾”, 所以被称为 尾数 (Mantissa).

因此, 有下列结果:

20230211121154

特殊数的定义和表示

浮点表示法需要表示的特殊数分为三类: $0$, $+\infty \backslash -\infty$ 和非法数 NaN.

这三类不同的特殊浮点数在二进制的表示中是通过不同的 尾数部分数值和指数部分数值组合 区分的:

20230211121413

简单的浮点数表示例子

下面以一个极为简单的, 不考虑符号位, 规定精度为 $3$, 分配 $3$ 个二进制位表示指数部分的浮点数表示系统:

20230211121508

可知, 该浮点数表示系统的基本参数为:

  1. 指数部分转换后表示范围 $[0, 2^{3}-1]$, 偏移量 $2^{2}-1$, 实际表示范围 $[-2^{2}+1, 2^{2}]$. 除去对特殊浮点数的表示占位, 指数 $e$ 的合理取值上限与下限为: $e_{\text{min}} = -2^{2}+2 = -2$, $e_{\text{max}} = 2^{2} - 1 = 3$.

  2. 尾数部分表示范围 $[0, 2^{2}-1]$, 被省去的最高位为 $2^3$.

  3. 对特殊浮点数的表示: $00000$ 表示十进制中的 $0$, $00001$ 和 $00011$ 表示 subnormal 数, $11100$ 表示无穷大, $11101$ 到 $11111$ 表示非法数.

  4. 对正常浮点数的表示: 从 $00100$ 到 $11011$ 表示 normalized numbers.

  5. 该浮点数表示系统的 machine epsilon 为 $2^{1-p} = 2^{-2}$, 记为 $\epsilon$.

总结如下:

20230211123806

浮点数与十进制实数的转换

下面是从十进制实数转换到 IEEE754 浮点表示的基本步骤.

20230211124542

反之相似, 只需注意在转换二进制表示的尾数时, 要记得根据数的类型加回被隐藏的高位数, 同时还需注意基于指数部分计算的值, 对小数点进行的移位操作是关于二进制数而非十进制数的.

浮点数的取整策略

下面讨论 IEEE754 规定的数种浮点数取整策略.

很显然, 以浮点数作为被操作数的运算自然地有可能产生精度比任何参与运算的数精度更高的结果, 因此为了在有限的精度下存储和表示这样的计算结果, 就需要对得到的精度更高的结果进行 取整. 为了简化问题, 我们规定浮点数之间可以执行的运算包括 加法, 减法, 乘法, 除法和开平方, 并考虑下面四种取整策略: RN, RZ, RU, RD.

20230211162712

RD: Round toward $-\infty$

无限向下取整, 在任何情况下均返回一个 不大于输入 $x$ 的, 位于给定精度内的 最大 浮点值.

RU: Round toward $+\infty$

无限向上取整: 在任何情况下均返回一个 不小于输入 $x$ 的, 位于给定精度内的 最小 浮点值.

RZ: 向零取整

返回 距离输入 $x$ 最近绝对值不超过 $\vert x \vert$ 的浮点数.

RN: Ties to Even 的向近取整

返回距离输入 $x$ 最近的浮点数. 若存在两个距离相同的浮点数, 则选择 significand 为偶数 (也就是尾数的最后一位为 $0$) 的那个浮点数.

其他定义

上面介绍的四种取整逻辑的行为大概可以总结为下图:

20230211163740

关于取整, 还有下列的定义:

Correct Rounding: 考虑某个关于浮点数的函数或运算. 若对于 任意给定的输入, 这个运算或函数所得到的结果和 “以无限精度执行计算然后对结果进行取整” 的表现一致, 则称该运算或函数是 给定取整模式下Correct Rounded Operation.

Faithful Rounding: 对于任何浮点运算, 若返回的, 经过 取整后 的运算结果恰好是 位于无法被精确表示的浮点运算结果之间的两个 (近似的) 浮点表示中的任何一个, 则称所定义的运算为 Faithful 的. 这一定义 不要求指定任何特定的取整策略.

同时自然有: Any arithmetic that provides the correctly rounded results is faithful.

Table Maker‘s Dilemma 和取整的其他性质

需要注意: 在 IEEE754 中, 只要求 addition/subtraction, mulitplication/division, square root, fised miltiply-add 和进制转换等运算为 Correct Rounded 的.

其余的复杂函数, 如 sin, log, cos, exp, 则不被强制要求实现为 Correct Rounded. 这是由于实现这一目的极为困难. 困难的原因被 Table Maker's Dilemma 所描述:

sin, log, cos 等函数, 它们的计算结果可能是包含 无限多个小数 的超越数, 因此就需要大量的位数存储它们的计算结果以确保计算结果的精确度.

而若我们希望对所有这类函数的运算结果都能确保准确度, 如: 找到一个最小的整数 $m$, 使计算任意给定函数 $y = f(x)$ 时, 真实值 $y$ 和近似值 $y^{*}$ 的差别足够小, 即

\[\vert y = y^{*} \vert < 2^{-m}\]

则可以证明, 我们无法找到 一致 的整数 $m$, 对任何一种近似方法, 均满足 Faithful Rounding 的条件.

浮点数的基本运算

和定点实数的运算不同, 浮点数无法直接使用整数运算单元, 如加法器和乘法器参与运算. 为了实现浮点数的运算, 要么运算单元需要在整数运算单元的基础上加入一些额外的组件, 要么单独构造具有 独立寄存器池和指令集浮点运算单元 (FPU).

下面我们介绍数种用于实现浮点数加法和乘法的算法.

浮点数加法

首先将运算简化为: 只考虑精度为 $p$ 的, 已经被 Normalized 的正浮点数:

\[x = e^{e_x} \cdot m_x, ~~ y = e^{e_y} \cdot m_y.\]

同时为了少打点字, 我们将指数部分统一的偏移量 $2^{-(p-1)}$ 也全部略去.

浮点数加法的执行步骤如下:

20230211170432

(也就是统一精度为更大的, 然后将尾数的表示精度也归一化, 随后直接累加尾数)

20230211170640

20230211170649

20230211170816

浮点数乘法

20230211170825

20230211170841

衡量浮点数的精度

对取整误差的分析