书很厚,仅记录一些感兴趣的、新学到、或以后可能会用到的东西,以便后续查阅。
第一部分 程序结构和执行
第2章 信息的表示和处理
2.1 信息存储
大多数计算机使用8位的块,或叫做字节(byte),来作为最小的可寻址的存储器单位,而不是访问存储器中单独的位。机器级程序将存储器视为一个非常大的字节数组,称为虚拟存储器(virtualmemory)。存储器的每个字节都由一个惟一的数字来标识,称为它的地址(address),所有可能地址的集合就称为虚拟地址空间(virtual address space)。
字
每台计算机都有一个字长(word size),指明整数和指针数据的标称大小(nominal size)。因为虚拟地址是以这样的字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的最大大小。也就是说,对于一个字长为 n 位的机器而言,虚拟地址的范围为 0 ∼ 2 n − 1 0 \sim 2^n-1 0∼2n−1,程序最多访问2”字节。
大小端
对表示一个对象的字节序列排序,有两个通用的规则。考虑一个 w w w 位的整数,有位表示 [ x w − 1 , x w − 2 , … , x 1 , x 0 ] [x_{w-1}, x_{w-2}, \dots, x_{1}, x_{0}] [xw−1,xw−2,…,x1,x0] ,其中 x w − 1 x_{w-1} xw−1是最高有效位,而 x 0 x_{0} x0 是最低有效位。假设 w w w是8的倍数,这些位就能被分组成为字节,其中最高有效字节包含位 [ x w − 1 , x w − 2 , … , x w − 8 ] [x_{w-1}, x_{w-2}, \dots, x_{w-8}] [xw−1,xw−2,…,xw−8] ,而最低有效字节包含位 [ x 7 , x 6 , … , x 0 ] [x_{7}, x_{6}, \dots, x_{0}] [x7,x6,…,x0] ,其他字节包含中间的位。某些机器选择在存储器中按照从最低有效字节到最高有效字节的顺序存储对象,而另一些机器则按照从最高有效字节到最低有效字节的顺序存储。前一种规则——最低有效字节在最前面的方式被称为小端法(little endian)。大多数源自以前的Digital Equipment公司(现在是Compaq公司的一部分)的机器,以及Intel 的机器都采用这种规则。后一种规则(最高有效字节在最前面的方式)被称为大端法(big endian)。
2.2 整数表示
布尔代数和环
属性 | 整数环 | 布尔代数 |
---|---|---|
交换性 |
a
+
b
=
b
+
a
a + b = b + a
a+b=b+a a × b = b × a a \times b = b \times a a×b=b×a |
a
∣
b
=
b
∣
a
a | b = b | a
a∣b=b∣a a & b = b & a a \& b = b \& a a&b=b&a |
结合性 |
(
a
+
b
)
+
c
=
a
+
(
b
+
c
)
(a + b) + c = a + (b + c)
(a+b)+c=a+(b+c) ( a × b ) × c = a × ( b × c ) (a \times b) \times c = a \times (b \times c) (a×b)×c=a×(b×c) |
(
a
∣
b
)
∣
c
=
a
∣
(
b
∣
c
)
(a | b) | c = a | (b | c)
(a∣b)∣c=a∣(b∣c) ( a & b ) & c = a & ( b & c ) (a \& b) \& c = a \& (b \& c) (a&b)&c=a&(b&c) |
分配性 | a × ( b + c ) = ( a × b ) + ( a × c ) a \times (b + c) = (a \times b) + (a \times c) a×(b+c)=(a×b)+(a×c) | a & ( b ∣ c ) = a & ( b & c ) a \& (b | c) = a \& (b \& c) a&(b∣c)=a&(b&c) |
同一性 |
a
+
0
=
a
a + 0 = a
a+0=a a × 1 = a a \times 1 = a a×1=a |
a
∣
0
=
a
a | 0 = a
a∣0=a a & 1 = a a \& 1 = a a&1=a |
消除性 | a × 0 = 0 a \times 0 = 0 a×0=0 | a & 0 = 0 a \& 0 = 0 a&0=0 |
相消性 | − ( − a ) = a -(-a) = a −(−a)=a | ~ (~a)= a |
相逆性 | a + − a = 0 a + -a = 0 a+−a=0 | —— |
分配性 | —— | a ∣ ( b & a ) = ( a ∣ b ) & ( a ∣ c ) a | (b \& a) = (a | b) \& (a | c) a∣(b&a)=(a∣b)&(a∣c) |
相补性 | —— | a | ~a = 1 a & ~a = 0 |
幂等性 | —— |
a
&
a
=
a
a \& a = a
a&a=a a ∣ a = a a | a = a a∣a=a |
吸收性 | —— |
a
∣
(
a
&
b
)
=
a
a | (a \& b) = a
a∣(a&b)=a a & ( a ∣ b ) = a a \& (a | b) = a a&(a∣b)=a |
DeMorgan定律 | —— | ~ (a & b) = ~a | ~b (a | b) = ~a & ~b |
2.3 整数运算
无符号乘法
范围 $ 0 \leq x,y \leq 2^w -1$ 内的整数 x x x 和 y y y 可以被表示为 w w w 位的无符号数字,但是它们的乘积 x ⋅ y x·y x⋅y 的取值范围为 0 ∼ ( 2 w − 1 ) 2 = 2 2 w − 2 w + 1 + 1 0 \sim (2^w - 1)^2 = 2^{2w} - 2^{w+1} + 1 0∼(2w−1)2=22w−2w+1+1 之间。这可能需要 2 w 2w 2w 位来表示。不过, C 中的无符号乘法被定义为产生 2 w 2w 2w 位的整数乘积的低w位表示的值。根据等式 B 2 U k [ x k , x k − 1 , … , x 0 ] = B 2 U w ( [ x w , x w − 1 , … , x 0 ] ) m o d 2 k B2U_k [x_k, x_{k-1}, \dots, x_0] = B2U_w ([x_w, x_{w-1}, \dots, x_0]) mod 2^k B2Uk[xk,xk−1,…,x0]=B2Uw([xw,xw−1,…,x0])mod2k ,我们可以看出这等价于计算模 2 w 2w 2w 的乘法。因此, w w w 位无符号乘法运算的效果为 x ∗ w u y = ( x ⋅ y ) m o d 2 w x *_w^u y = (x·y) mod 2^w x∗wuy=(x⋅y)mod2w 大家都知道模运算形成了环。因此我们可以推出w位数字上的无符号运算形成了环< { 0 , … , 2 w − 1 } , + w u , ∗ w u , − w u , 0 , 1 \{0, \dots, 2^w-1\},+_w^u,*_w^u,-_w^u,0,1 {0,…,2w−1},+wu,∗wu,−wu,0,1>。
二进制补码乘法
范围 − 2 w − 1 ≤ x , y ≤ 2 w − 1 − 1 -2^{w-1} \leq x, y \leq 2^{w-1} -1 −2w−1≤x,y≤2w−1−1 内的整数 x x x 和 y y y 可以被表示为 w w w 位的二进制补码数字,但是它们的乘积 x ⋅ y x·y x⋅y 的取值范围为 − 2 w − 1 ⋅ ( − 2 w − 1 − 1 ) = − 2 2 w − 2 + w w − 1 ∼ − 2 w − 1 ⋅ − 2 w − 1 = − 2 2 w − 2 -2^{w-1}·(-2^{w-1}-1) = -2^{2w-2} + w^{w-1} \sim -2^{w-1} · -2^{w-1} = -2^{2w-2} −2w−1⋅(−2w−1−1)=−22w−2+ww−1∼−2w−1⋅−2w−1=−22w−2 之间。要想用-进制补码来表示这个乘积,可能需要 2 w 2w 2w 位——大多数情况下只需要 2 w − 1 2w-1 2w−1 位,但是特殊情况 2 2 w − 2 2^{2w-2} 22w−2 需要 2 w 2w 2w 位(包括一个符号位0),然而, C中的有符号乘法是通过将 2 w 2w 2w 位的乘积截断为 w w w 位来实现的。根据等式 B 2 T k [ x k , x k − 1 , … , x 0 ] = U 2 T w ( B 2 T w [ x w , x w − 1 , … , x 0 ] ) m o d 2 k B2T_k [x_k, x_{k-1}, \dots, x_0] = U2T_w (B2T_w[x_w, x_{w-1}, \dots, x_0]) mod 2^k B2Tk[xk,xk−1,…,x0]=U2Tw(B2Tw[xw,xw−1,…,x0])mod2k w w w 位的二进制补码乘法运算 ∗ w t *_w^t ∗wt,的效果为: x ∗ w t y = U 2 T w ( ( x ⋅ y ) m o d 2 w ) x *_w^t y = U2Tw((x·y) mod 2^w) x∗wty=U2Tw((x⋅y)mod2w)
我们认为对于无符号和二进制补码乘法来说,乘法运算的位级表示都是一样的。也就是,给定长度为 w w w 的位向量 x → \overrightarrow{x} x 和 y → \overrightarrow{y} y,无符号乘积 B 2 U w ( x → ) ∗ w t B 2 U w ( x → ) B2U_w (\overrightarrow{x}) *_w^t B2U_w(\overrightarrow{x}) B2Uw(x)∗wtB2Uw(x) 的位级表示与二进制补码乘积 B 2 T w ( x → ) ∗ w t B 2 T w ( x → ) B2T_w (\overrightarrow{x}) *_w^t B2T_w(\overrightarrow{x}) B2Tw(x)∗wtB2Tw(x) 的位级表示相同。这表明机器可以用一种乘法指令来进行有符号和无符号整数的乘法。
为了看清这一点,设 x = B 2 T w ( x → ) x = B2T_w(\overrightarrow{x}) x=B2Tw(x) 和 y = B 2 T w ( y → ) y = B2T_w(\overrightarrow{y}) y=B2Tw(y) 是这些位模式表示的二进制补码值,而 x ′ = B 2 U w ( x → ) x' = B2U_w(\overrightarrow{x}) x′=B2Uw(x) 和 y ′ = B 2 U w ( y → ) y' = B2U_w(\overrightarrow{y}) y′=B2Uw(y) 是这些位模式表示的无符号值。根据等式 B 2 U w ( T 2 B w ( x ) ) = T 2 U w ( x ) = x w − 1 2 w + x B2U_w(T2B_w(x)) = T2U_w(x) = x_{w-1} 2^w + x B2Uw(T2Bw(x))=T2Uw(x)=xw−12w+x ,我们有 x ′ = x + x w − 1 2 w x'= x + x_{w-1} 2^w x′=x+xw−12w 和 y ′ = y + y w − 1 2 w y' = y + y_{w-1} 2^w y′=y+yw−12w 。计算这些值的模 2 w 2^w 2w 乘积得到以下结果:
( x ′ ⋅ y ′ ) m o d 2 w = [ ( x + x w − 1 2 w ) ⋅ ( y + y w − 1 2 w ) ] m o d 2 w = [ x ⋅ y + ( x w − 1 y + y w − 1 x ) 2 w + x w − 1 y w − 1 2 2 w ] m o d 2 w = ( x ⋅ y ) m o d 2 w \begin{aligned} (x'·y') mod 2^w & = [(x + x_{w-1} 2^w) · (y + y_{w-1} 2^w)] mod 2^w & \\ & = [x·y + (x_{w-1} y + y_{w-1} x) 2^w + x_{w-1} y_{w-1} 2^{2w}] mod 2^w & \\ & = (x·y) mod 2^w \end{aligned} (x′⋅y′)mod2w=[(x+xw−12w)⋅(y+yw−12w)]mod2w=[x⋅y+(xw−1y+yw−1x)2w+xw−1yw−122w]mod2w=(x⋅y)mod2w
因此, x ⋅ y x·y x⋅y 和 x ⋅ y x·y x⋅y 的低 w w w 位是相同的。
正如说明的那样,图2.21展示了不同的三位数字乘法的结果。对于每对位级运算数,我们既执行无符号乘法,也执行有符号乘法。注意,无符号已截断乘积总是等于 x ⋅ y m o d 8 x·y mod 8 x⋅ymod8,而且两个已截断乘积的位级表示是相同的。
模式 | x x x | y y y | x ⋅ y x·y x⋅y | 截断的 x ⋅ y x·y x⋅y |
---|---|---|---|---|
无符号 二进制补码 | 5 [101] -3 [101] | 3 [011] 3 [011] | 15 [001111] -9 [110111] | 7 [111] -1 [111] |
无符号 二进制补码 | 4 [100] -4 [100] | 7 [111] -1 [111] | 28 [011100] 4 [000100] | 4 [100] -4 [100] |
无符号 二进制补码 | 3 [011] 3 [011] | 3 [011] 3 [011] | 9 [001001] 9 [001001] | 1 [001] 1 [001] |
虽然完整的乘积的位级表示可能会不同,但是已截断乘积的位级表示是相同的。
乘以2的幂
在大多数机器上,整数乘法指令相当地慢,需要12或者更多的时钟周期,然而其他整数运算——例如加法、减法、位级运算和移位—只需要1个时钟周期。因此,编译器使用的一项重要的优化就是试着用移位和加法运算的组合来代替乘以常数因子的乘法。
设x为位模式
[
x
w
−
1
,
x
w
−
2
,
…
,
x
0
]
[x_{w-1}, x_{w-2},\dots,x_0 ]
[xw−1,xw−2,…,x0] 表示的无符号整数。那么, 对于任何
k
≥
0
k \geq 0
k≥0 ,我们都认为
x
2
k
x2^k
x2k 的位级来:表示是由
[
x
w
−
1
,
x
w
−
2
,
…
,
x
0
,
0
,
…
,
0
]
[x_{w-1}, x_{w-2}, \dots, x_0, 0, \dots, 0]
[xw−1,xw−2,…,x0,0,…,0] 给出的,这里右边增加了
k
k
k 个 0 。这个属性可以通过等式
B
2
U
w
(
x
→
)
=
˙
∑
i
=
0
w
−
1
x
i
2
i
B2U_w(\overrightarrow{x}) \dot{=} \sum_{i=0}^{w-1} x_i 2^i
B2Uw(x)=˙i=0∑w−1xi2i 推导出来:
B
2
U
w
+
k
(
[
x
w
−
1
,
x
w
−
2
,
…
,
x
0
,
0
,
…
,
0
]
)
=
∑
i
=
0
w
−
1
x
i
2
i
+
k
=
[
∑
i
=
0
w
−
1
x
i
2
i
]
⋅
2
k
=
x
2
k
\begin{aligned} B2U_{w+k}([x_{w-1}, x_{w-2}, \dots, x_0, 0, \dots, 0]) & = \sum_{i=0}^{w-1}{x_i 2^{i+k}} &\\ & = \begin{bmatrix} \sum_{i=0}^{w-1}{x_i 2^i} \end{bmatrix} ·2^k &\\ & = x2^k \end{aligned}
B2Uw+k([xw−1,xw−2,…,x0,0,…,0])=i=0∑w−1xi2i+k=[∑i=0w−1xi2i]⋅2k=x2k
对于 $ k \leq w$ ,我们可以将移位了的位向量截断到长度
w
w
w ,得到
[
x
w
−
k
−
1
,
x
w
−
k
−
2
,
…
,
x
0
,
0
,
…
,
0
]
[x_{w-k-1}, x_{w-k-2}, \dots, x_0, 0, \dots, 0]
[xw−k−1,xw−k−2,…,x0,0,…,0] 。根据等式:
B
2
U
k
[
x
k
,
x
k
−
1
,
…
,
x
0
]
=
B
2
U
w
(
[
x
w
,
x
w
−
1
,
…
,
x
0
]
)
m
o
d
2
k
B2U_k [x_k, x_{k-1}, \dots, x_0] = B2U_w ([x_w, x_{w-1}, \dots, x_0]) mod 2^k
B2Uk[xk,xk−1,…,x0]=B2Uw([xw,xw−1,…,x0])mod2k ,这个位向量的数值为
x
2
k
m
o
d
2
w
=
x
∗
w
u
2
k
x2^k mod 2^w = x *_w^u 2^k
x2kmod2w=x∗wu2k 。因此,对于无符号变量
x
x
x , C 表达式 x<<k
等价于
x
∗
p
w
r
2
k
x * pwr 2k
x∗pwr2k ,这里
p
w
r
2
k
pwr2k
pwr2k 等于
2
k
2^k
2k。特别地,我们可以用
1
U
≪
k
1U \ll k
1U≪k 来计算
p
w
r
2
k
pwr2k
pwr2k 。
通过类似的推理,我们可以给出,对于一个位模式为
[
x
w
−
1
,
x
w
−
2
,
…
,
x
0
]
[x_{w-1}, x_{w-2}, \dots, x_0]
[xw−1,xw−2,…,x0] 的二进制补码数
x
x
x ,以及范围$ 0 \leq k < w $ 内任意的
k
k
k ,位模式
[
x
w
−
k
−
1
,
x
w
−
k
−
2
,
…
,
x
0
,
0
,
…
,
0
]
[x_{w-k-1}, x_{w-k-2}, \dots, x_0, 0, \dots, 0]
[xw−k−1,xw−k−2,…,x0,0,…,0] 就是
x
∗
w
t
2
k
x*_w^t 2^k
x∗wt2k 的二进制补码表示。因此,对于有符号变量
x
x
x , C 表达式 x<<k
等价于
x
∗
p
w
r
2
k
x*pwr2k
x∗pwr2k ,这里
p
w
r
2
k
等于
2
k
pwr2k等于2^k
pwr2k等于2k
注意,无论是无符号运算还是二进制补码运算,乘以 2 的幂都可能会导致溢出。我们的结果表明,即使溢出的时候,我们通过移位得到的结果也是一样的。
除以2的幂
在大多数机器上,整数除法要比整数乘法更慢–需要30或者更多的时钟周期。除以2的幂也可以用移位运算来实现,只不过我们用的是右移,而不是左移。对于无符号和二进制补码数,分别使用逻辑移位和算术移位来达到目的。
整数除法总是舍入到0的。对于 x ≥ 0 x \geq 0 x≥0 和 y > 0 y > 0 y>0 ,结果会是 ⌊ x / y ⌋ \lfloor x / y \rfloor ⌊x/y⌋ ,这里对于任何实数 a a a , ⌊ a ⌋ \lfloor a \rfloor ⌊a⌋ 定义为惟一的整数 a ′ a' a′ ,使得 a ′ ≤ a < a ′ + 1 a' \leq a < a' + 1 a′≤a<a′+1 。例如, ⌊ 3.14 ⌋ = 3 \lfloor 3.14 \rfloor = 3 ⌊3.14⌋=3 , ⌊ − 3.14 ⌋ = − 4 \lfloor -3.14 \rfloor = -4 ⌊−3.14⌋=−4 和 ⌊ 3 ⌋ = 3 \lfloor 3 \rfloor = 3 ⌊3⌋=3 。
考虑在一个无符号数上执行逻辑右移的效果。设x为位模式 [ x w − 1 , x w − 2 , … , x 0 ] [x_{w-1}, x_{w-2}, \dots, x_0] [xw−1,xw−2,…,x0] 表示的无符号整数,而 k k k 的取值范围为 $ 0 \leq k < w$ 。设 x ′ x' x′ 为 w − k w-k w−k 位表示 [ x w − 1 , x w − 2 , … , x k ] [x_{w-1}, x_{w-2}, \dots, x_k] [xw−1,xw−2,…,xk] 的无符号数,而 x ′ ′ x'' x′′ 为 k k k 位表示 [ x k − 1 , … , x 0 ] [x_{k-1}, \dots, x_0] [xk−1,…,x0] 的无符号数。我们有 x ′ = ⌊ x / 2 k ⌋ x' = \lfloor x / 2^k \rfloor x′=⌊x/2k⌋。证明如下:根据等式 B 2 U w ( x → ) ≐ ∑ i = 0 w − 1 x i 2 i B2U_w(\overrightarrow{x}) \doteq \sum_{i=0}^{w-1} x_i 2^i B2Uw(x)≐i=0∑w−1xi2i ,我们有 x = ∑ i = 0 w − 1 x i 2 i x = \sum_{i=0}^{w-1}{x_i 2^i} x=∑i=0w−1xi2i , x ′ = ∑ i = k w − k − 1 x i 2 i − k x' = \sum_{i=k}^{w-k-1}{x_i 2^{i-k}} x′=∑i=kw−k−1xi2i−k 和 x ′ ′ = ∑ i = 0 k − 1 x i 2 i x'' = \sum_{i=0}^{k-1}{x_i 2^i} x′′=∑i=0k−1xi2i , 因此,我们可以把 x x x 写为 x = 2 k x ′ + x ′ ′ x = 2^k x' + x'' x=2kx′+x′′ 。可以观察到 $0 \leq x’’ \leq = \sum_{i=0}{k-1}{2i} = 2^k-1 $ ,因此 0 ≤ x ′ ′ < 2 k 0 \leq x'' < 2^k 0≤x′′<2k,这意味着 ⌊ x ′ ′ / 2 k ⌋ \lfloor x'' / 2^k \rfloor ⌊x′′/2k⌋ 。因此, ⌊ x / 2 ⌋ = ⌊ x ′ + x ′ ′ / 2 k ⌋ = x ′ ⌊ x ′ ′ / 2 k ⌋ = x ′ \lfloor x / 2 \rfloor = \lfloor x' + x'' /2^k \rfloor = x' \lfloor x'' / 2^k \rfloor = x' ⌊x/2⌋=⌊x′+x′′/2k⌋=x′⌊x′′/2k⌋=x′ ,
可以观察到,对位向量 [ x w − 1 , x w − 2 , … , x k ] [x_{w-1}, x_{w-2}, \dots, x_k] [xw−1,xw−2,…,xk] 逻辑右移k位会得到位向量 [ 0 , … , 0 , x w − 1 , x w − 2 , … , x k ] [0, \dots, 0, x_{w-1}, x_{w-2}, \dots, x_k] [0,…,0,xw−1,xw−2,…,xk]
这个位向量有数值 x ′ x' x′ 。也就是,将一个无符号数逻辑右移 k k k 位等价于把它除以 2 k 2^k 2k 。因此,对于无符号变量 x x x , C C C 表达式 x ≫ k x \gg k x≫k 等价于 x / p w r 2 k x/pwr2k x/pwr2k,这里 p w r 2 k pwr2k pwr2k 等价于 2 k 2^k 2k。
现在考虑对一个二进制补码数进行算术右移的结果。设 x x x 为位模式 [ x w − 1 , x w − 2 , … , x 0 ] [x_{w-1}, x_{w-2}, \dots, x_0] [xw−1,xw−2,…,x0] 表示的二进制补码整数,而 k k k 的取值范围为 0 ≤ k < w 0 \leq k < w 0≤k<w 。设 x ′ x' x′ 为 w − k w-k w−k 位 [ x w − 1 , x w − 2 , … , x k ] [x_{w-1}, x_{w-2}, \dots, x_k] [xw−1,xw−2,…,xk] 表示的二进制补码数,而 x x x 为低 k k k 位 [ x k − 1 , … , x 0 ] [x_{k-1}, \dots, x_0] [xk−1,…,x0] 表示的无符号数。通过对无符号情况的类似分析,我们有 x = 2 k x ′ + x ′ ′ x = 2^k x' + x'' x=2kx′+x′′,而 0 ≤ x ′ ′ < 2 0 \leq x'' < 2 0≤x′′<2,得到 x ′ = ⌊ x / 2 k ⌋ x' = \lfloor x/2^k \rfloor x′=⌊x/2k⌋ 。此外,我们可以观察到,算术右移位向量 [ x w − 1 , x w − 2 , … , x 0 ] [x_{w-1},x_{w-2}, \dots, x_0] [xw−1,xw−2,…,x0] k k k 位,得到位向量 [ x w − 1 , … , x w − 1 , x w − 1 , x w − 2 , … , x k ] [x_{w-1}, \dots, x_{w-1}, x_{w-1}, x_{w-2}, \dots, x_k] [xw−1,…,xw−1,xw−1,xw−2,…,xk]
它刚好就是将 [ x w − 1 , x w − 2 , … , x k ] [x_{w-1}, x_{w-2}, \dots, x_k] [xw−1,xw−2,…,xk] 从 w − k w-k w−k 位符号扩展到 w w w 位。因此,这个移位了的位向量就是 ⌊ x / y ⌋ \lfloor x/y \rfloor ⌊x/y⌋ 的二进制补码表示。
对于
x
≥
0
x \geq 0
x≥0,我们的分析表明这个移位的结果就是所期望的值。不过,对于
x
<
0
x<0
x<0 和
y
>
0
y>0
y>0 ,整数除法的结果应该是
⌈
x
/
y
⌉
\lceil x/y \rceil
⌈x/y⌉ ,这里,对于任何实数
a
a
a ,
⌈
a
⌉
\lceil a \rceil
⌈a⌉ 被定义为使得
a
′
−
1
<
a
≤
a
′
a'-1 < a \leq a'
a′−1<a≤a′ 的惟一整数
a
′
a'
a′。也就是说,整数除法应该将为负的结果向上朝零舍入。例如, C 表达式 -5/2
得到 -2 。因此,当有舍入发生时,将一个负数右移
k
k
k 位不等价于把它除以
2
k
2^k
2k 。例如,
−
5
-5
−5 的四位表示为 [1011] 。如果我们将它算术右移一位,我们得到 [1101] ,这是
−
3
-3
−3 的二进制补码表示。
我们可以通过在移位之前“偏置(biasing)”这个值,修正这种不合适的舍入。这种技术利用的是这样一个属性:对于整数
x
x
x 和有
y
>
0
y>0
y>0 的
y
y
y,
⌈
x
/
y
⌉
=
⌈
(
x
+
y
−
1
)
/
y
⌉
\lceil x/y \rceil = \lceil (x+y-1)/y\rceil
⌈x/y⌉=⌈(x+y−1)/y⌉。因此,对于
x
<
0
x<0
x<0 ,如果我们在右移之前,先将
x
x
x 加上
2
k
−
1
2^k - 1
2k−1,那么我们就会得到正确舍入的结果了。这个分析表明对于使用算术右移的二进制补码机器, C 表达式 (x < 0 ? (x + (1<<k) - 1) : x >> k)
等价于
x
/
p
w
r
2
k
x/pwr2k
x/pwr2k,这里
p
w
r
2
k
pwr2k
pwr2k 等于
2
k
2^k
2k 。例如,
−
5
-5
−5 除以
2
2
2 ,我们先加上偏置数
2
−
1
=
1
2-1=1
2−1=1,得到位模式 [1100] 。将这个值算术右移 1 位得到位模式 [1110] ,这是
−
2
-2
−2 的二进制补码表示。
2.4 浮点
IEEE 浮点表示
IEEE 浮点标准用 V = ( − 1 ) 5 × M × 2 E V=(-1)^5 \times M \times 2^E V=(−1)5×M×2E 的形式来表示一个数:
- 符号(sign) s s s 决定数是负数 ( s = 1 ) ( s=1 ) (s=1) 还是正数 ( s = 0 ) (s=0) (s=0) ,而对于数值 0 0 0 的符号位解释作为特殊情况处理。
- 有效数(significand) M M M 是一个二进制小数,它的范围在 1 ∼ 2 − ε 1 \sim 2 - \varepsilon 1∼2−ε 之间,或者在 0 ∼ 1 − ε 0 \sim 1 - \varepsilon 0∼1−ε 之间。
- 指数(exponent) E E E 是 2 2 2 的幂(可能是负数),它的作用是对浮点数加权。
浮点数的位表示被划分为三个域,以编码这些值:
- 一个单独的符号位 s s s 直接编码符号 s s s。
- k k k 位的指数域 e x p = e k − 1 … e 1 e 0 exp=e_{k-1} \dots e_1 e_0 exp=ek−1…e1e0 。编码指数 E E E 。
- n n n 位小数域 f r a c = f n − 1 … f 1 f 0 frac = f_{n-1} \dots f_1 f_0 frac=fn−1…f1f0 编码有效数 M M M ,但是被编码的值也依赖于指数域的值是否等于零。在单精度浮点格式(C语言中的float)中, s s s、 e x p exp exp 和 f r a c frac frac 域分别为 1 位、 k = 8 k=8 k=8 位和 n = 23 n=23 n=23 位,产生一个 32 位的表示。在双精度浮点格式(C语言中的double)中, s s s 、 e x p exp exp 和 f r a c frac frac 域分别为 1 位、 k = 11 k=11 k=11 位和 n = 52 n=52 n=52 位,产生一个 64 位的表示。给定位表示,根据 e x p exp exp 的值,被编码的值可以分成三种不同的情况。
转换算法:
- 确定整数和浮点数的表示
- 整数(int):在大多数现代系统中,int是32位(4字节)的,并使用二进制补码来表示有符号整数。
- 浮点数(float):在IEEE 754标准中,float是32位的,并分为以下三个部分:
- 1位符号位(S)
- 8位指数位(E),表示偏移的指数(通常偏移量为127)
- 23位尾数位(M),表示尾数(或分数部分)
- 将整数转换为浮点数
- 处理符号
- 首先,我们需要找到整数的二进制表示。这通常可以通过不断除以2并记录余数(从下往上)来完成。 例如,对于整数12345,其二进制表示为11000000111001
- 规范化
- 对于整数,我们不需要进行规范化,因为整数没有小数部分。但是,为了符合IEEE 754浮点数标准,我们假设整数有一个隐含的最高有效位(即二进制点左侧的1),这样整数就可以表示为1.M的形式,其中M是整数二进制表示去掉最高位1之后的部分。
- 对于12345的二进制表示11000000111001,隐含的二进制点是放在最左侧1的右侧的,所以规范化的表示是1.1000000111001(注意这里只是为了说明,实际的IEEE 754表示不会显式存储这个1)
- 现在,我们需要计算从隐含的二进制点到最右边的位数。在这个例子中,从隐含的二进制点到最右边有13个位(包括隐含的1和尾数部分之前的0)。这个13就是无偏指数。
- 然后,我们将无偏指数13加上偏移量127,得到的就是有偏指数140。
- 但是,由于指数位只有8位,我们需要取140的二进制表示的前8位。140的二进制表示是10001100。
- 所以,存储的指数是10001100。
- 填充浮点数的位
- 尾数是整数二进制表示中去掉最高位1之后的部分。
- 在这个例子中,尾数就是12345的二进制表示去掉最高位的部分,即000000111001(但注意我们只取前23位,因为float的尾数只有23位;如果不足23位,则在高位补0)。
- 处理符号
- 总结
- 对于整数12345,按照IEEE 754标准转换为float表示时:
- 符号位(S)是0(因为12345是正数)。
- 存储的指数(E)是10001100(即140的二进制表示的前8位)。
- 尾数(M)是10000001110010000000000(前导零用于填充到23位)。
- 所以,整数12345的IEEE 754 float表示(以二进制表示)的大致结构是: S = 0 S=0 S=0, E = 10001100 E=10001100 E=10001100, M = 1.10000001110010000000000 M=1.10000001110010000000000 M=1.10000001110010000000000
- 对于整数12345,按照IEEE 754标准转换为float表示时:
简单代码查看:
void displayBits32(unsigned value) {
unsigned i;
unsigned displayMask = 1 << 31;
for (i = 1; i <= 32; i++) {
if (i == 1) {
printf(" S=");
}
else if (i == 2) {
printf(" E=");
}
else if (i == 10) {
printf(" M=1."); // M里开头的1是不会存储的, 这里补上
}
putchar(value & displayMask ? '1' : '0');
value <<= 1;
}
}
int main(int argc, char** argv){
float f = 0.0;
unsigned* u = (unsigned*)&f;
f = 12345.0;
printf("%f:",f);
displayBits32(*u);
printf("\r\n");
return 0;
}
舍入
- 向偶数舍入方式采用的方法是:它将数字向上或者向下舍入,使得结果的最低有效数字是偶数。
- 向零舍入
- 向下舍入
- 向上舍入
浮点运算
当在 int 、 float 和 double 格式之间进行强制类型转换时,程序按照如下原则来转换数值和位模式(假设 int 是 32位的):
- 从 int 转换成 float,数字不会溢出,但是可能被舍入。
- 从 int 或 float 转换成 double ,因为 double 有更大的范围(也就是可表示值的范围),也有更高的精度(也就是有效位数),所以能够保留精确的数值。
- 从 double 转换成 float,因为范围要小一些,所以值可能溢出成 $ +\infty $ 或 $ -\infty $ 。另外,由于精确度较小,它还可能被舍入。
- 从 float 或者 double 转换成 int ,值将会向 0 截断。例如,1.999 将被转换成 1,而 -1.999 将被转换成 -1 。注意这种行为与舍入是非常不同的。进一步来说,值可能会溢出。C 标准没有对这种情况指定固定的结果,但是在大部分机器上,结果将是 T M a x w TMax_w TMaxw,或 T M i n w TMin_w TMinw ,其中 w w w 是 int 中的位数。
小结
计算机将信息编码为位(比特),通常组织成字节序列。有不同的编码方式用来表示整数、实数和字符串。不同的计算机模型在编码数字和多字节数据中的字节顺序上使用不同的约定。
C 语言被设计成包容多种不同字长和数字编码的实现。虽然高端机器逐渐开始使用 64 位字长,但是目前大多数机器仍使用32位字长。大多数机器对整数使用二进制补码编码,而对浮点数使用 IEEE 编码。在位级上理解这些编码,并且理解算术运算的数学特性,对于编写能在全部数值范围上正确运算的程序来说,是很重要的。
C 语言的标准规定在无符号和有符号整数之间进行强制类型转换时,基本的位模式不应该改变。在二进制补码机器上,对于一个
w
w
w 位的值,这种行为是由函数
T
2
U
w
T2U_w
T2Uw,和
U
2
T
w
U2T_w
U2Tw ,来描述的。C 语言隐式的强制类型转换会得到许多程序员无法预计的结果,常常导致程序错误。
由于编码的长度有限,计算机运算与传统整数和实数运算相比,具有非常不同的属性。当超出表示范围时,有限长度能够引起数值溢出。当浮点数非常接近于 0.0,从而转换成零时,浮点数也会下溢。
和大多数其他程序语言一样,C 语言实现的有限整数运算和真实的整数运算相比有一些特殊的属性。例如,由于溢出,表达式
x
∗
x
x*x
x∗x 能够得出负数。但是,无符号数和二进制补码的运算都满足环的属性。这就允许编译器做很多的优化。例如,用 (x<<3)-x
取代表达式
7
∗
x
7*x
7∗x 时,我们就利用了结合性、交换性和分配性,还利用了移位和乘以 2 的幂之间的关系。
我们已经看到了几种使用位级运算和算术运算组合的聪明方法。例如,我们看到,使用二进制补码运算,~x+1 是等价于 -x 的。另外一个例子,假设我们想要一个形如
[
0
,
…
,
0
,
1
,
…
,
1
]
[0, \dots, 0, 1, \dots, 1]
[0,…,0,1,…,1] 的位模式,由
w
−
k
w-k
w−k 个 0 后面紧跟着
k
k
k 个 1 组成。这些位模式对于掩码运算是很有用的。这种模式能够通过 C 表达式 (1<<k)-1
生成,利用的是这样一个属性,即我们想要的位模式的数值为
2
k
−
1
2^k-1
2k−1 。例如,表达式 (1<<8)-1
将产生位模式 OxFF。
浮点表示通过将数字编码为
x
×
2
y
x \times 2^y
x×2y 的形式来近似地表示实数。最常见的浮点表示方式是由 IEEE 标准 754 定义的。它提供了几种不同的精度,最常见的是单精度(32位)和双精度(64位)。IEEE浮点也能够表示特殊值
∞
\infty
∞ 和
N
a
N
NaN
NaN 。
必须非常小心地使用浮点运算,因为浮点运算的范围和精度有限,而且浮点运算并不遵守普遍的算术属性,比如结合性。