深入理解计算机系统(第三版)第二章重要内容摘要
第二章
2.1.3寻址和字节顺序
#include <stdio.h>
typedef unsigned char *byte_pointer;
void show_bytes(byte_pointer start,size_t len){
size_t i;
for(i=0;i<len;++i)
printf("%.2x",start[i]);
printf("\n");
}
void show_int(int x){
show_bytes((byte_pointer)&x,sizeof(int));
}
void show_float(float x){
show_bytes((byte_pointer)&x,sizeof(float));
}
void show_point(void* x){
show_bytes((byte_pointer)&x,sizeof(void*));
}
void test_show_bytes(int val){
int ival=val;
float fval=(float)val;
int *pval=&ival;
show_int(ival);
show_float(fval);
show_point(pval);
}
int main(){
int i=12345;
test_show_bytes(i);
return 0;
}
2.2.3补码编码
有符号数的三种表示方法
补码:
原理:补码编码的定义
对向量\(\vec{x}=[x_{w-1},x_{w-2},\cdots,x_0]\):
反码:
反码(Ones'Complement):除了最高有效位的权是\(-(2^{w-1}-1)\)而不是\(-2^{w-1}\),它和补码是一样的:
原码:
原码(Sign-Magnitude):最高有效位是符号位,用来确定剩下的位应该取负权还是正权:
2.3.3补码的非
原理:补码的非
对满足\(TMin_x\leq x \leq TMax_w\)的\(x\),其补码的非\(-_w^tx\)由下式给出
\(-_w^tx=\begin{cases}
TMin_w,&x = TMin_w\\
-x,&x>TMin_w
\end{cases}\)
执行位级补码非得第一种方法是对每一位求补,再对结果加1。
计算一个数\(x\)的补码非得第二种方法是建立在将位向量分为两部分的基础之上的。假设\(k\)是最右边的1的位置,因而\(x\)的位级表示形如\([x_{w-1},x_{w-2},\cdots,x_{k+1},1,0,\cdots,0]\)。(只要\(x\neq0\)就能够找到这样的\(k_0\)。)这个值的非写成二进制格式就是\([\sim{x}_{w-1},\sim x_{w-2},\cdots,\sim x_{k+1},1,0,\cdots,0]\)。也就是,我们对位\(k\)左边的所有位取反。
2.3.7除以2的幂
回到\(y=2^k\)的情况,C表达式\(x+(1\ll{k})-1\)得到数值\(x+2^k-1\)。将这个值算术右移\(k\)位即产生\(\lfloor{x/2^k}\rfloor\)。
这个分析表明对于使用算术右移的补码机器,C表达式
\((x<0?x+(1\ll{k})-1:x)\gg k\)
2.4.2IEEE浮点表示
IEEE复变标准用\(V=(-1)^s\times M\times 2^E\)的形式来表示一个数:
- 符号(sign) \(s\)决定这个数是负数(\(s=1\))还是正数(\(s=0\)),而对于数值0的符号位解释作为特殊情况处理。
- 尾数(significand) \(M\)是一个二进制小数,它的范围是\(1\sim2-\epsilon\),或者是\(0\sim1-\epsilon\)。
- 阶数(exponent) E的作用是对浮点数加权,这个权重是2的E次幂(可能是负数)。
将浮点数的位表示划分为三个字段,分别对这些值进行编码: - 一个单独的符号位\(s\)直接编码符号\(s\)。
- \(k\)位的阶码字段exp=\(e_{k-1}\cdots e_1e_0\)编码阶码E。
- \(n\)位小数字段frac=\(f_{n-1}\cdots f_1f_0\)编码尾数M,但是编码出来的值也依赖于阶码字段的值是否等于0。
图给出了将这三个字段装进字中两种最常见的格式。在单精度浮点格式(C语言中的float)中,s、exp和frac字段分别为1位、\(k\)=8位和\(n\)=23位,得到一个32位的表示。在双精度浮点格式(C语言中的double)中,s、exp和frac字段分别为1位、\(k\)=11位和\(n\)=52位,得到一个64位的表示。
单精度
s | exp | frac |
s | exp | frac(51:32) |
frac(31:0) |
可以观察到最大非规格化数\(\frac{7}{512}\)和最小非规格化数\(\frac{8}{512}\)之间的平滑转变。这种平滑性归功于我们对非规格化数的\(E\)的定义。通过将\(E\)定义为\(1-Bias\),而不是\(-Bias\),我们可以补偿非规格化数的尾数没有隐含的开头的1。
描述 | exp | frac | 单精度 | 双精度 | ||
---|---|---|---|---|---|---|
值 | 十进制 | 值 | 十进制 | |||
0 | 00...00 | 0...00 | 0 | 0.0 | 0 | 0.0 |
最小非规格化数 | 00...00 | 0...01 | $2^{-23}\times2^{-126}$ | $1.4\times10^{-45}$ | $2^{-52}\times2^{-1022}$ | $4.9\times10^{-324}$ |
最大非规格化数 | 00...00 | 1...11 | $(1-\epsilon)\times2^{-126}$ | $1.2\times10^{-38}$ | $(1-\epsilon)\times2^{-1022}$ | $2.2\times10^{-308}$ |
最小规格化数 | 00...01 | 00...00 | $1\times2^{-126}$ | $1.2\times10^{-38}$ | $1\times2^{-1022}$ | $2.2\times10^{-308}$ |
1 | 01...11 | 0...00 | $1\times2^0$ | $1.0$ | $1\times2^0$ | $1.0$ |
最大规格化数 | 11...10 | 1...11 | $(2-\epsilon)\times2^{127}$ | $3.4\times10^{38}$ | $(2-\epsilon)\times2^{1023}$ | $1.8\times10^{308}$ |
- 值\(+0.0\)总有一个全为0的位表示。
- 最小的正非规格化值的位表示,是由最低有效位为1而其他所有位为0构成的。它具有小数(和尾数)值\(M=f=1-2^{-n}\)和阶码值\(E=-2^{k-1}+2\)。因此它的数字值是\(V=2^{-n-2^{k-1}+2}\)。
- 最大的非规格化值的位模式是由全为0的阶码字段和全为1的小数字段组成的。它有小数(和尾数)值\(M=f=1-2^{-n}\)(我们写成\(1-\epsilon\))和阶码值\(E=-2^{k-1}+2\)。因此,数值\(V=(1-2^{-n})\times2^{-2^{k-1}+2}\),这仅比最小的规格化值小一点。
- 最小的正规格化值的位模式的阶码字段的最低有效位为1,其他位全为0。它的尾数值\(M=1\),而阶码值\(E=-2^{k-1}+2\)。因此,数值\(V=2^{-2^{k-1}+2}\)。
值1.0的位表示的阶码字段除了最高有效位为0之外,其它位都等于1。它的尾数值M=1,而它的阶码值E=\(2^{k-1}-2^{k-1}\)=0
- 最大的规格化值的位表示的符号位为0,阶码的最低有效位等于0,其他位等于1。它的小数值\(f=1-2^{-n}\),尾数\(M=2-2^{-n}\)(我们写作\(2-\epsilon\))。它的阶码值\(E=2^{k-1}-1\),得到数值\(V=(2-2^{-n})\times2^{2^{k-1}-1}=(1-2^{-n-1})\times2^{2^{k-1}}\)。
2.4.4舍入
方式 | 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 |
在我们不想舍入到整数时,也可以使用向偶数舍入。我们只是简单地考虑最低有效数字是奇数还是偶数。例如,假设我们想将十进制数舍入到最接近的百分位。不管用哪种舍入方式,我们都将把1.2349999舍入到1.23,而将1.2350001舍入到1.24,因为它们不是在1.23和1.24的正中间。另一方面我们将把两个数1.2350000和1.2450000都舍入到1.24,因为4是偶数。
相似地,向偶数舍入法能够运用在二进制小数上。我们将最低有效位的值0认为是偶数,值1认为是奇数。一般来说,只有形如\(XX\cdots X.YY\cdots Y100\cdots\)的二进制位模式的数,这种舍入方式才有效,其中\(X\)和\(Y\)表示任意位值,最右边的Y是要被舍入的位置。只有这种位模式表示在两个可能的结果正中间的值。例如,考虑舍入值到最近的四分之一的问题(也就是二进制小数点右边2位)。我们将\(10.00011_2(2\frac{2}{32})\)向下舍入到\(10.00_2(2)\),将\(10.00110_2(2\frac{3}{16})\)向上舍入到\(10.01_2(2\frac{1}{4})\),因为这些值不是两个可能值的正中间值。我们将\(10.11100_2(2\frac{7}{8})\)向上舍入到\(11.00_2(3)\),而\(10.10100_2(2\frac{5}{8})\)向下舍入到\(10.10_2(2\frac{1}{2})\),因为这些值是两个可能值的中间值,并且我们倾向于使最低有效位为零。
2.4.5浮点运算
浮点加法不具有结合性
另一方面,浮点加法满足了单调性属性:如果\(a\geq b\),那么对于任何\(a\)、\(b\)以及\(x\)的值,除了\(NaN\),都有\(x+a\geq x+b\)。无符号或补码加法不具有这个实数(和整数)加法的属性。
浮点乘法不具有结合性和分配性
2.4.6C语言中的浮点数
当在int、float和double格式之间进行强制类型转换时,程序改变数值和位模式的原则如下(假设int是32位的):
- 从int转换成float,数字不会溢,但是可能被舍入。
- 从int或float转换成double,因为double有更大的范围(也就是可表示值的范围),也有更高的精度(也就是有效位数),所以能够保留精确的数值。
- 从double转换成float,因为范围要小一些,所以值可能溢出成 \(+\infty\)或\(-\infty\)。另外,由于精确度较小,它还可以被舍入。
- 从float或者double转换成int,值将会向零舍入。例如,1.999将被转换成1,而-1.999将被转换成-1.进一步来说,值可能会溢出。C语言标准没有对这种情况指定固定的结果。与Intel兼容的微处理器指定位模式\([10\cdots00]\)(字长为\(\omega\)时的\(TMin_w\))为整数不确定(integer indefinite)值。一个从浮点数到整数的转换,如果不能为该浮点数找到一个合理的整数近似值,就会产生这样一个值。因此,表达式(int)+1e10会得到-21483648,即从一个正值变成了一个负值。
第一章课后练习
1.1
A 加速比\(S=T_{old}/T_{new}=\frac{1}{(1-a)+\frac{a}{k}}=\frac{1}{(1-0.6)+\frac{0.6)}{1.5)}}=1.25\)
B \(S=T_{old}/T_{new}=\frac{1}{(1-a)+\frac{a}{k}}\)可推出\(1.67=\frac{1}{(1-0.6)+\frac{0.6}{k}} \approx3\)即3*100=300公里每小时
1.2
\(2=\frac{1}{(1-0.8)+\frac{0.8}{k}} \approx2.66\)
第二章课后练习
练习2.1
A \(0x39A7F8\rightarrow001110011010011111111000\)
B \(1100100101111011\rightarrow0xC97B\)
C \(0xD5E4C\rightarrow11010101111000101100\)
D \(1001101110011110110101\rightarrow0x26E7B5\)
练习2.2
n | \(2^n(十进制)\) | \(2^n(十六进制)\) |
---|---|---|
9 | 512 | 0x200 |
19 | 524288 | 0x80000 |
14 | 16384 | 0x4000 |
16 | 65536 | 0x10000 |
17 | 131072 | 0x20000 |
5 | 32 | 0x20 |
7 | 128 | 0x80 |
练习2.3
十进制 | 二进制 | 十六进制 |
---|---|---|
0 | 00000000 | 0x00 |
167 | 10100111 | 0xA7 |
62 | 00111110 | 0x3E |
188 | 10111100 | 0xBC |
55 | 00110111 | 0x37 |
136 | 10001000 | 0x88 |
243 | 11110011 | 0xF3 |
82 | 01010010 | 0x52 |
172 | 10101100 | 0xAC |
247 | 11100111 | 0xE7 |
练习2.4
A 0x503c+0x8=0x5044
B 0x503c-0x40=0x4ffc
C 0x503c+64=0x503c+0x40=0x507c
D 0x50ea-0x503c=0x50bd
练习2.5
int main(){
int i=0x87654321;
byte_pointer valp=(byte_pointer)&i;
show_bytes(valp,2);
return 0;
}
A
小端法:21
大端法:87
B
小端法:2143
大端法:8765
C
小端法:214365
大端法:876543
练习2.6
A
\(0x00359141\rightarrow001101011001000101000001\)
\(0x4A564504\rightarrow01001010010101100100010100000100\)
B
001101011001000101000001
01001010010101100100010100000100
总共有21位匹配
C
串的头尾不匹配
练习2.7
int main(){
const char *s="abcdef";
show_bytes((byte_pointer)s,strlen(s));
return 0;
}
616263646566
练习2.8
运算 | 结果 |
---|---|
a | [01101001] |
b | [01010101] |
~a | [10010110] |
~b | [10101010] |
a&b | [01000001] |
a|b | [01111101] |
a^b | [00111100] |
练习2.9
R | G | B | 颜色 | 补集 |
---|---|---|---|---|
0 | 0 | 0 | 黑色 | 白色 |
0 | 0 | 1 | 蓝色 | 黄色 |
0 | 1 | 0 | 绿色 | 红紫色 |
0 | 1 | 1 | 蓝绿色 | 红色 |
1 | 0 | 0 | 红色 | 蓝绿色 |
1 | 0 | 1 | 红紫色 | 绿色 |
1 | 1 | 0 | 黄色 | 蓝色 |
1 | 1 | 1 | 白色 | 黑色 |
蓝色|绿色=蓝绿色
黄色&蓝绿色=绿色
红色^红紫色=蓝色
练习2.10
void inplace_space(int *x,int *y){
*y=*x^*y;/* Step1 */
*x=*x^*y;/* Step2 */
*y=*x^*y;/* Step3 */
}
每个元素就是它自身的加法逆元(a^a=0)
步骤 | *x | *y |
---|---|---|
初始 | a | b |
第一步 | a | a^b |
第二步 | b | a^b |
第三步 | b | a |
练习2.11
A 变量first和last都为k+1
B 由上图可知第一步时last值就为0(a^a)了
C 去掉=即可
void reverse_array(int a[],int cnt){
for(int first=0,last=cnt-1;first<last;inplace_space(&a[first++],&a[last--]));
}
练习2.12
A x&0xFF
B x^~0xFF
C x|0xFF
练习2.13
\(bis(x,y)\)
\(bis(bic(x,y),bic(y,x))\rightarrow\) x^y=(x&y)|(x&y)