CSAPP3 2.4 浮点数

| 分类 Programming  CSAPP3  | 标签 Programming  《深入理解计算机系统》 

浮点表示用来对形如 $V=x \times 2^n$ 的有理数进行编码。这里讨论的是 IEEE 标准 754 制订的浮点数及其运算标准。

二进制小数

例如这样的一个二进制小数 $b$: $b_mb_{m-1}\cdots b_1b_0.b_{-1}b_{-2}\cdots b_{-n-1}b_{-n}$(小数点右边有 m 个数,左边有 n 个数),数 b 定义为

二进制小数

例如 $101.11_2$ 表示数字 $1 \times 2^2 + 0 \times 2^1 + 1 \times 2^0 + 1 \times 2^{-1} + 1 \times 2^{-2} = 5 \frac {3} {4}$

可见,二进制小数点向左移动一位相当于这个数被 2 除(相当于整数的右移),而小数点向右移动一位相当于该数乘以 2。

而开始 $0.11\cdots1_2$ 的数表示的是刚好小于 1 的数。例如 $0.111111_2$ 表示 $\frac {63} {64}$(当小数点右边有 n 个 1时,分母是 $2^n$,分子比分母小 1),这个数值可用简单的表达式 $1.0-\varepsilon$ 表示。

当用有限长度编码时,十进制表示法不同准确表达像 $\frac 1 3$ 这样的数,类似地,小数的二进制表示法只能表示那些能被写成 $x \times 2^y$ 的数,其它的只能进行近似表示。例如 $\frac 1 5$ 可用十进制小数 0.2 精确表示,但只能用十进制小数近似表示。

0.2 的二进制小数表示

练习题 2.45 p113

填写下表中缺失的信息:

小数值 十进制表示 十进制表示
$\frac 1 8$ 0.001 0.125
$\frac 3 4$ 0.11 0.75
$\frac {25} {16}$ 1.1001 1.5625
$\frac {43} {16}$ 10.1011 2.6875
$\frac 9 8$ 1.001 1.125
$\frac {47} 8$ 101.111 5.875
$\frac {51} {16}$ 11.0011 3.1875

当将十进制小数转成分数时,可进用减法分解,例如 3.1875 = 3 + 0.125 + 0.0625 = 3 + 1/8 + 1/16。

其中各 $2^{-n}$ 表示的十进制数如下表:

$2^{-n}$ 十进制值
$\frac 1 2$ 0.5
$\frac 1 4$ 0.25
$\frac 1 8$ 0.125
$\frac 1 {16}$ 0.0625
$\frac 1 {32}$ 0.03125
$\frac 1 {64}$ 0.015625

** 练习题 2.46 p113**

浮点运算的不精确性能够产生空难性的后果.

1/10 的十进制表达式为一个无穷序列 $0.000110011[0011]\cdots_2$, 当用有限位来表示时, 只能截断为近似值. 例如, 当用 x 值来近似表示 0.1 时, x 只考虑这个序列的二进制小数点右边的前 23 位: x=0.00011001100110011001100.

A. 0.1-x 的二进制表示是什么? 为 $0.000110011[0011]\cdots_2$ - 0.00011001100110011001100 = $0.00\cdots 0_{共23个}[1100]\cdots$.

B. 0.1-x 的近似的十进制数值是多少? 由于 0.1 的二进制为 $0.000[1100]\cdots$, 故 0.1-x 相当于 0.1 的小数点左移 20 位,即值为 $0.1 * \frac 1 {2^{20}}$

C. 100 小时为 $100 \times 3600 = 360000$ 秒, 时间差为 $36000 * 10 * 0.1 \frac 1 {2^{20}}$ =0.3433 秒.

D. 当速度为 2000m/s 时, 偏差为 2000 * 0.3433=687m

IEEE 浮点表示

该标准用 $V = (-1)^s \times M \times 2^E$ 的形式来表示一个数:

  • 符号(sign): $s$ 决定是正数(s=0) 还是负数(s=1),而对于数值 0 的符号解释又作特殊处理.
  • 尾数(significand): $M$ 是一个二进制小数,范围为 [1, $2-\varepsilon$) 或 [0, $1-\varepsilon$).
  • 阶码(exponent): $E$ 的作为是对浮点数加权,权重是 2 的 E 次幂(可能为负数).

因此,浮点数的位表示分为了三个字段, 分别对 s, M, E 进行编码:

  • 最高位直接用来编码符号位 $s$
  • $k$ 位阶码字段 $exp=e_{k-1}\cdots e_1e_0$ 编码阶码 $E$
  • $n$ 位小数字段 $frac=f_{n-1}\cdots f_1f_0$ 编码尾数 $M$,但是编码出来的值还要依赖于阶码字段的值是否为 0 进行不同解释.

IEEE 小点数的 2 个字段表示

在 C 语言中, 单精度 float 中, s, exp, frac 字段分别为 s=1 位, k=8 位, n=23 位 (共 32 位). 而 double 中, s=1 位, k=11 位, n=52 位(共 64 位).

给定位表示, 根据 exp 值, 被编码的值可以分成三种不同的情况(最后一种情况有两个变种).

单精度浮点数值的分类, 规格化, 非规格化和特殊值

情况1 : 规格化的值(用于表示普通的数)

此时 exp 的位模式即不全为 0 (数值 0), 也不全为 1(单精度数值为 $2^8-1=255$,双精度数值为 $2^{11}-1=2047$). 在这种情况中,阶码字段被解释为以偏置(biased) 形式表示的有符号数, 即阶码的值为 $E=e-Bias$, 其中 $e$ 是无符号数,其位表示为 $e_{k-1}\cdots e_1e_0$, 而 Bias 是一个等于 $2^{k-1}-1$(单精度是 127, 双精度是 1023) 的偏置值. 由此产生指数的取值范围是: 单精度 [1-127, 254-127] 即为 [-126, 127],双精度为 [1-1023, 2046-1023] 即为 [-1022, 1023].

小数字段 frac 被解释为描述小数值 f,其中 $0 \leq f < 1$,其二进制表示为 $0.f_{n-1}\cdots f_1f_0$,也就是二进制小数点在最高有效位的左边. 而尾数定义为 $M=1+f$, 这种方式也叫做隐含的以 1 开头的 (implied leading 1) 表示, 因此可以把 $M$ 看成一个二进制表达式为 $1.f_{n-1}\cdots f_1f_0$ 的数字. 用这种方式可以额外地获得一个精度位.

情况2: 非规格化的值 (用于表示很小的数)

此时阶码域为全 0. 这时, 阶码值为 $E=1-Bias$, 这里阶码值不直接定义为 -Bias 是为了从非规格化值能平滑转换到规格化的值. 而尾数的值为 M=f, 也就是小数字段的值,不包含隐含的开头的 1.

非规格化数的 2 个用途:

  1. 表示数值 0: 因为规格化数时, M 隐含有 1, 故总大于等于 1, 不能表示 0. 实际上, +0.0 的浮点表示的位模式为全 0 : 符号位为 0, 阶码字段全 0(表示是非规格化值), 小数域也全 M = f = 0. 而当符号位为 1, 其它位都为 0 时, 得到 -0.0.

  2. 表示那些非常接近于 0.0 的数.

情况3: 特殊值(无穷大和 NaN)

此时,阶码为全 1.

  • 小数域全 0 时, 当 s=0 时表示 $+\infty$, s = 1 时表示 $-\infty$. 无穷能够表示溢出的结果.
  • 小数域非全 0 时, 表示 NaN

数字示例

下图展示了一组数值,它们可以用假定的 6 位格式来表示, 有 k=3 的阶码位和 n=2 的尾数位. 偏置量 Bias = $2^{k-1}-1$ = 3. 图中的 a 部分显示了所有可表示的值(除了 NaN), 两个无穷值在两个末端. 最大数量值的规格化数是 $\pm 14$. 非规格化数聚焦在 0 的附近. 图的 b 部分, 只展示了介于 -1.0 和 1.0 之间的数值. 两个 0 是特殊的非规格化数.

可以观察到, 可表示的数并不是均匀分布的: 越靠近原点处越稠密.

浮点数的范围与分布

下图展示了假定的 8 位浮点格式的示例, 其中阶码位 k=4, 小数位 n=3, 偏置量 Bias = $2^{k-1}-1$=7. 图被分成了三个区域, 用来描述三类数字.

第一部分的阶码部分都为 0, 表示的是非规格化数, 这些数的指数 E = 1-Bias=-6, 得到权 $2^E$=1/64. 小数 $f$ 的范围是 $0, \frac 1 8, \cdots, \frac 7 8$, 从而得到非规格化数 V 的范围是 $[0 \times \frac 1 {64}, \frac 7 8 \times \frac 1 {64}]$, 即为 $[0, \frac 7 {512}]$. 这些非规格化数表示的是 0.0 即原点附近的数.

第二部分表示规格化数. 最小规格化数的阶码 e = 1, 指数 E = e-Bias = -6, 从而与非规格化数平滑过渡. 小数取值范围两样是 $0, \frac 1 8, \cdots, \frac 7 8$, 但是尾数根据定义都还要加 1, 即范围为$[1+0, 1+\frac 7 8]$ 即 $[0, \frac {15} 8]$ 之间. 指数的范围是 [1-7, 14-7] 即 [-6, 7] 之间. 从而数 V 的范围在 $2^{-6} \times 1 = \frac 1 {64}$ 和 $2^7 \times 1\frac 7 8 = 240$ 之间. 超过 240 这个值就会溢出到 $+\infty$.

从图中可看出这样的属性, 假如将上图中的值的位表达式解释为无符号整数, 它们就是按升序排序的. IEEE 进行这样设计, 使得浮点数能使用整数的排序函数.

8位浮点数示例

练习题 2.47 p117

一种基于 IEEE 浮点格式的 5 位浮点表示, 1 位 符号位, 2 位阶码位(k=2), 2 小数位(n=2), 阶码偏置量 B = $2^{k-1}-1$=1, 补全下表.

类型 e E $2^E$ f M $2^E \times M$ V 十进制
0 00 00 非规格化数 0 0 1 0/4 0/4 0 0 0.0
0 00 01 非规格化数 0 0 1 1/4 1/4 1/4 1/4 0.25
0 00 10 非规格化数 0 0 1 2/4 1/2 1/2 1/2 0.5
0 00 11 非规格化数 0 0 1 3/4 3/4 3/4 3/4 0.75
0 01 00 规格化数 1 0 1 0/4 4/4 4/4 1 1.0
0 01 01 规格化数 1 0 1 1/4 5/4 5/4 5/4 1.25
0 01 10 规格化数 1 0 1 2/4 6/4 6/4 3/2 1.5
0 01 11 规格化数 1 0 1 3/4 7/4 7/4 7/4 1.75
0 10 00 规格化数 2 1 2 0/4 4/4 8/4 2 2.0
0 10 01 规格化数 2 1 2 1/4 5/4 10/4 5/2 2.5
0 10 10 规格化数 2 1 2 2/4 6/4 12/4 3 3.0
0 10 11 规格化数 2 1 2 3/4 7/4 14/4 7/2 3.5
0 11 00 正无穷大 - - - - - - $+\infty$ -
0 11 01 NaN - - - - - - NaN -
0 11 10 NaN - - - - - - NaN -
0 11 11 NaN - - - - - - NaN -

下图是非负浮点数的示例:

非负浮点数的示例

  • 值 +0.0 总有一个全为 0 的位表示.
  • 最小的正非规格化值的位表示, 是由最低有效位为 1 而其它所有位为 0 构成的. 它的小数(和尾数)值 $M=f=2^{-n}$, 阶码值 $E=1-(2^{k-1}-1)=-2^{k-1}+2$. 因此它的数字值为 $V=M \times 2^E = 2^{-n-2^{k-1}+2}$.
  • 最大的正非规格化值的位表示,是由全为 1 的小数字段和全为 0 的阶码字段组成的. 它的小数(和尾数)值 $M=f=1-2^{-n}=1-\varepsilon$, 阶码值 $E=1-(2^{k-1}-1)=-2^{k-1}+2$. 因此它的数字值为 $V=M \times 2^E = (1-2^{-n}) \times 2^{-n-2^{k-1}+2}$, 它只比最小规格化值小一点.
  • 最小的正规格化值的位模式的阶码字段的最低有效位为 1, 其它位全为 0. 它的尾数 M=1+0=1, 阶码值 $E=e-Bias=1-Bias=1-(2^{k-1}-1)=-2^{k-1}+2$. 因此它的数字值为 $V=2^{-n-2^{k-1}+2}$.
  • 值 1.0 位表示的阶码字段除了最高有效位为 1 外其它位于都为 0, 其它位也都为 0. 它的尾数值 M=1+0=1, 阶码值 $E=e-Bias=0$. 数值超过 1.0 后, 数值分布间隔将越来越大.
  • 最大的规格化值的位表示的符号位为 0, 阶码的最低有效位为 0, 其它位全为 1. 它的小数值 $f=1-2^{-n}$, 尾数值 $M=1+f=2-2^{-n}=2 - \varepsilon$. 它的阶码值 $E=e-Bias=(2^k-1)-(2^{k-1}-1)=2^{k-1}-1$, 数值为 $V=(2-2^{-n}) \times 2^{2^{k-1}-1}=(1-2^{-n-1}) \times 2^{2^{k-1}}$.

练习题 2.48 p119

整数 3 510 593 的十六进制表示为 0x00359141, 而单精度浮点数 3510593.0 的十六进制表示为 0x4A564504. 推导出这个浮点数表示, 并解释整数和浮点数表示的位之间的关系.

整数 0x00359141 = 0000 0000 0011 0101 1001 0001 0100 0001, 转换成浮点表示时, 先将小数点放在值为 1 的最高有效位的右边, 得到 1.1 0101 1001 0001 0100 0001, 因此, 尾数值就是 $1.1 0101 1001 0001 0100 0001_2$, 而由于规格化数的尾数值是隐含 1 的, 故小数部分就是 $0.1 0101 1001 0001 0100 0001_2$.

单精度浮点数中, 符号位 1 位, 阶码位数 k=8, 小数位 n=23 位. 偏置量 Bias = $2^{k-1}-1=2^7-1=127$

小数部分右边补 0, 得到 23 位的小数部分为 1 0101 1001 0001 0100 0001 00, 定位小数点时共向左移位了 21 位, 从而得知指数值 E = 21, E = e-B, 从而 e= 21+127=148, 从而阶码的位表示为 10010100, 加上符号位 0, 从而得出 3510593.0 的浮点表示为 0 10010100 101011001000101000001 00,

而 0x4a564504 的位模式刚好为 0 10010100 101 0110 0100 0101 0000 0100

练习题 2.49 p120

A. 对于一种具有 n 位小数的浮点格式, 给出不能准确描述的最小正整数的公式(因为要想准确表示它它需要 n+1位小数). 假设阶码字段长度 k 足够大, 可以表示的阶码范围不会限制这个问题.

该练习有助于我们思考什么数不能用浮点数精确表示,因为阶码范围不限制, 故只需考虑小数部分最小能表示的数即可. 小数最多 n 位, 故最小能表示的小数 f 是 n 个 0 后面再加一个 1, 这个小数需要 n+1 个位来表示, 那么尾数 M = f+1 = $1.0000\cdots 01_2$, 即要示的数的二进制表示为:1 后面跟 n 个 0, 再跟一个 1, 值为 $2^{n+1} + 1$

B. 对于单精度格式 n=23, 这个数为 $2^24+1=16777217

舍入

浮点数不同表示所有的数, 故浮点运算只能近似地表示实数运算.

IEEE 浮点数共定义有 4 种舍入方式.

  • 向偶数舍入(round-to-even), 也被称为向最接近的值舍入(round-to-nearest), 这是默认的方式. 这种方式是: 它将数字向上或向下舍入, 使得结果的最低有效数字是偶数, 从而计算平均数时不引入统计偏差.
  • 向零舍入: 正数向下舍入, 负数向上舍入, 它使结果的绝对值变小.
  • 向下舍入: 正数和负数都向下舍入, 从而计算的平均值会偏小.
  • 向上舍入: 正数和负数都向上舍入,从而计算的平均值会偏大.

对于要舍入为整数的十进制来说,向偶数舍入只有当形如 XYZ.500…的才会进行(XYZ是任意数),因为只有这时才表示在两个可能的结果的正中间的值,此时向偶数舍入偏向于使舍入位为偶数,例如 1.5 舍入为 2, 2.5舍入为 2, -1.5 舍入到 -2.

下面是不同舍入方式下舍入到整数的示例:

方式 1.40 1.60 1.50 2.50 -1.50
向偶数舍入 1 2 2 2 -2
向零舍入 1 1 1 2 -1
向下舍入 1 1 1 2 -2
向上舍入 2 2 2 3 -1

类似地, 向偶数舍入法运用在二进制小数上时, 可认为最低有效位的值 0 为偶数, 1 为奇数, 同样只对形如 $XX\cdots X.YY\cdots Y100\cdots$ 的二进制位模板的数才起作用.

练习题 2.50 p120

用向偶数舍入法对下面各数舍入到小数点右边 1 位, 并给出舍入前后的数字值.

A. $10.010_2$, 值为 2.25, 舍入到 $10.0_2$, 值为 2.0

B. $10.011_2$, 值为 2.375,舍入到 $10.1_2$, 值为 2.5

C. $10.110_2$, 值为 2.75, 舍入到 $11.0$, 值为 3.0

D. $11.001_2$, 值为 3.125, 舍入到 $11.0$, 值为 3.0

练习题 2.51 p120

练习题 2.46 中导弹软件将 0.1 近似表示为 $x= 0.000 1100 1100 1100 1100 1100_2$. 假设使用 IEEE 舍入到偶数方式来确定 0.1 的二进制小数点右边 23 位的近似表示 $x’$

A. $x’$ 的二进制表示是什么? 由于 0.1 的二进制为 $0.000 [1100]\cdots_2], 舍入后$x’= 0.000 1100 1100 1100 1100 1101_2$, 它比 0.1 大一点.

B. $x’-0.1$ 的十进制表示的近似值为什么? 二进制为 $0.000 1100 1100 1100 1100 1101_2 - 0.000 1100 1100 1100 1100 1100 [1100]\cdots$ = $0.0\cdots _{加前面共22个0} 000[1100]\cdots$, 值约为 $0.1 * \frac 1 {2^{22}}$

C. 运行 1000 小时后,时针值偏差: $100 * 3600 * 10 * 0.1 * \frac 1 {2^{22}}, 约为 0.0858

D. 导弹位置预测偏差:0.0858 * 2000 = 171

练习题 2.52 p120

考虑下面基于 IEEE 浮点格式的 7 位浮点表示. 两个格式都没有符号位, 它们只能表示非负的数字.

  1. 格式 A: 
    • 阶码位 k=3, 阶码偏置值 Bias = $2^{k-1}-1$ = 3
    • 小数位 n=4
  2. 格式 B:
    • 阶码位 k=4, 阶码偏置值 Bias = $2^{k-1}-1$ = 7
    • 小数位 n=3

将 A 格式的值转换到最接近的 B 格式的值, 必要时用舍入到偶数的方式.

转换时, 阶码和小数分别进行转换, 小数转换时要进行舍入. 当从非规格化转换到规格化数时, 要特殊处理.

格式 A 位 格式 A 值 格式 B 位 格式 B 值
011 0000 1 0111 000 1
101 1110 15/2 1001 111 15/2
010 1001 25/32 0110 100 3/4 向下舍入
110 1111 31/2 1010 000 16 向上舍入
000 0001 1/64 0001 000 1/64 非规格化到规格化

浮点运算

浮点运算结果都是舍入后的结果.

  1. 浮点加法是可交换的: 即 $x +^f y = y +^f x$
  2. 浮点加法不可结合: 例如 (3.14+1e10)-1e10 的值舍入到 0, 而 3.14+(1e10-1e10)的值为 3.14
  3. 浮点加法满足单调性: 即若 $a \geq b$, 则对于任何 $a, b, x$ 的值, 除了 NaN, 都有 $x+a \geq x+b$, 无符号数和补码加法不具有这种属性.
  4. 浮点乘法也是可交换的, 即 $x \times y = y \times x$
  5. 浮点乘法也不可结合: 这些都是由于溢出和舍入引起的.
  6. 浮点乘法在加法上也不具备分配性, 即 1e20(1e20-1e20) 为 0.0, 而 1e201e20-1e20*1e20 为 NaN
  7. 浮点乘法也满足单调性: 对于任何 $a, b, c$ 都不等于 NaN 时, $a \geq b 且 c \geq 0 时,有 a \times c \geq b \times c$; $a \geq b 且 c \leq 0 时,有 a \times c \leq b \times c$; 此外,当 $a \neq NaN$ 时, 有 $a \times a \geq 0$, 无符号数和补码都没有这些单调性

C 中的浮点数

C 标准未规定使用 IEEE 浮点格式, 因此在 C 中无法改变舍入方式, 或者得到诸如 -0, $+\infty$ , $-\infty$ 或者 NaN 之类的特殊值. 大多数系统通过头文件来定义这些常量, 如 GNU 会在 math.h 中定义程序常量 INFINITY(表示 $+\infty$) 和 NAN(表示 NaN).

练习题 2.53 p123

完成下列宏定义, 生成双精度值 $+\infty$, $-\infty$ 和 0:

已知双精度能够表示的最大的有限数大约是 1.8e308, 则下面的代码似乎可以在多种机器上工作, 假设值 1e400 溢出为无穷, 则:

#define POS_INFINITY 1e400
#define NEG_INFINITY (-POS_INFINITY)
#define NEG_ZERO (-1.0/POS_INFINITY)

当在 int, float, double 格式间进行强制类型转换时,程序改变数值和位模式的原则如下(设 int 32 位):

  • 从 int 转换到 float: 不溢出, 可能舍入
  • int 或 float 转换到 double: 能精确转换
  • double 到 float: 可能溢出,可能舍入
  • float 或 double 到 int, 值将会向 0 舍入, 例如 1.99 转换为 1, -1.99 转换为 -1, 也可能溢出. 溢出时(即找不到一个合理的整数近似值), C 标准没有指定固定的结果, 但 Intel 兼容机指定位模式 $[10\cdots 00]$ (即 $TMin_w$) 作为整数不确定值(integer indefinite). 因此, 表达式 (int)+1e10 会得到 -21483648, 即从一个正值变成了一个负值.

练习题 2.54 p123

假设变量 x, f, d 类型分别是 int, float, double, 它们除了不是 $+\infty$, $-\infty$ 和 NaN 外,可以是任意值, 对于下面的每个 C 表达式, 证明它们总是真,或者给出一个使其不为真的值.

float 的最大规格化数是 3.4e38, 小于 1e40, double 的最大规格化数是 1.8e308, 小于 1e309.

A. x == (int)(double) x : int 转换到 double 时能精确转换, 故总为真

B. x == (int)(float) x: int 到 float, 不溢出, 可能舍入, 根据练习题 2.49, 最小不能表示的正数是 $2^{23}+1$, 此时会舍入到$2^{23}$

C. d == (double)(float) d: double 转换到 float 会溢出,也会舍入, d = 1e40 时右边会是正无穷

D. f == (float)(double)f: 都为真

E. f == -(-f): 都为真, 因为浮点数的正负转换只需转换符号位即可.

F. 1.0/2 == 1/2.0: 都为真, 因为分子和分母在运算前都会先转换成浮点数.

G. d*d >= 0.0:实数乘法符合单调性, 故都为真, 不过可能溢出为 $+\infty$

H. (f+d)-f == d: 当 f+d 溢出时, 不为真, 例如 f=1e20, d=1.0 时, 左边会 0,右边 为 1.0

参考


上一篇     下一篇