声明:此习题集的题目来源是《CSAPP》英文第二版,与《CSAPP》中文第二版题号和主要题干是对应的,但是子问题的数据可能会有些许出入。
本习题集的目的在于对《CSAPP》2、3、5、6、8章的内容进行一次比较大致的复习。
限于个人水平,答案难免有错误之处,请各位在评论区指出错误,谢谢!
由于时间因素,不能一次将30道题做完发布,只能以连载形式发布,以下是第2章的6道题
2.6
Using show_int and show_float, we determine that the integer 3510593 has hexadecimal representation 0x00359141, while the floating-point number 3510593.0 has hexadecimal representation 0x4A564504.
A. Write the binary representations of these two hexadecimal values.
B. Shift these two strings relative to one another to maximize the number of matching bits. How many bits match?
C. What parts of the strings do not match?
ANSWER
//这题书上有答案,但还是要结合浮点数一节来看才行
A:
0x00359141 : 0000 0000 0011 0101 1001 0001 0100 0001
0x4A564504 : 0100 1010 0101 0110 0100 0101 0000 0100
B:
00000000001101011001000101000001
01001010010101100100010100000100
有21个bits相匹配
C:
浮点型尾数的高位和整型的高位相匹配。
下面是这两个数之间的关联:
整型->浮点型:
对于整型,我们可以将它表示成:1.101011001000101000001 * 2^21 (二进制)
除去起始位的1,并在末位增加两个0,就可得到浮点型的尾数位完全匹配的结果f = [10101100100010100000100]
而浮点型的表示是 (-1)^s * M * 2^E, 所以E = e - bias 且 E = 21, 所以 e = 21 + bias = 21 + 127 = 148 = 10010100。
然后这个整型数是正数,s = 0
按照s、e、f组合起来就得到了[01001010010101100100010100000100]
浮点型->整型:
把上面的步骤反过来
V = (-1)^0 * (0.10101100100010100000100 + 1) + 2^(10010100 - 01111111) = 3510593.0
如此,虽然这两个二进制形式不同,但是在各自的数据类型中代表了数学意义上的同一个数:3510593.0
2.63
Fill in code for the following C functions. Function srl performs a logical right shift using an arithmetic right shift (given by value
xsra
), followed by other operations not including right shifts or division. Function sra performs an arithmetic right shift using a logical right shift (given by valuexsrl
), followed by other operations not including right shifts or division. You may use the computation8*sizeof(int)
to determine (w), the number of bits in data typeint
. The shift amountk
can range from (0) to (w − 1).
unsigned srl(unsigned x, int k) {
/* Perform shift arithmetically */
unsigned xsra = (int) x >> k;
.
.
.
}
int sra(int x, int k) {
/* Perform shift logically */
int xsrl = (unsigned) x >> k;
.
.
.
}
ANSWER
/*
8 * sizeof(int) == bits of a byte * bytes of an integer
*/
/*
1 << (4 - 1) - 1 = 1000 - 1 = 0111
(1 << (len-k))-1 的结果后len-k位都是1, 前位是0, 刚好对应需要保留后len-k位有效数字的xsra
例:
x = 1100, k = 1
xsra = 1110
mask = 0111
xsra & mask == 0110 == x >> k
就算1 << (len-k))溢出仍能返回正确结果
*/
unsigned srl(unsigned x, int k) {
/* Perform shift arithmetically */
unsigned xsra = (int) x >> k;
unsigned len = sizeof(int) * 8;
unsigned mask = (1 << (len-k))-1;
return mask & xsra;
}
/*
1000 - 0010 = 0110
-0010 = 10000 - 0010 = 1110
*/
/*
mask = 1110
xsrl = 0010
xsrl最高有效位是1,刚好d对应mask最后一个1
*/
int sra(int x, int k) {
/* Perform shift logically */
int xsrl = (unsigned) x >> k;
unsigned len = sizeof(int) * 8;
unsigned mask = -(1 << (len-k));
return mask | xsrl;
}
2.76
Suppose we are given the task of generating code to multiply integer variable x by various different constant factors K. To be efficient, we want to use only the operations +, -, and <<. For the following values of K, write C expressions to perform the multiplication using at most three operations per expression.
A. K = 17:
B. K = −7:
C. K = 60:
D. K = −112:
ANSWER
/*中文版的K取值有所不同,但是原理都是一样的*/
//A :
x = (x << 4) + x;
//B :
x = x - (x << 3);
//C :
x = (x << 6) - (x << 2);
//D :
x = (x << 4) - (x << 7);
2.81
We are running programs on a machine where values of type int are 32 bits. They are represented in two’s complement, and they are right shifted arithmetically. Values of type unsigned are also 32 bits.
We generate arbitrary values x and y, and convert them to unsigned values as follows:
/* Create some arbitrary values */
int x = random();
int y = random();
/* Convert to unsigned */
unsigned ux = (unsigned) x;
unsigned uy = (unsigned) y;
For each of the following C expressions, you are to indicate whether or not the expression always yields 1. If it always yields 1, describe the underlying mathematical principles. Otherwise, give an example of arguments that make it yield 0.
A. (x<y) == (-x>-y)
B. ((x+y)<<4) + y-x == 17y+15x
C. x+y+1 == ~(x+y)
D. (ux-uy) == -(unsigned)(y-x)
E. ((x >> 2) << 2) <= x
ANSWER
/*INT_MIN == -INT_MIN,看到-x不妨用INT_MIN(边界)代入判断*/
A: FALSE, 反例:1 < INT_MIN != -1 > -INT_MIN
/*
乘法分配律
<<(>>)可以与*2^k(/2^k)完全对应
*/
B: TRUE.
/*
~x = -1 - x, ~x + ~y + 1 = -1 - (x+y) = ~(x+y)
注:中文版中需要判断的是 ~x + ~y == ~(x+y):由上面的推导过程可知表达式不正确
*/
C: TRUE.
/*
unsigned 和 int 之间的转换不会改变内部的二进制(01)表示,因为某种意义上说二者只是对01串的解读方法不同。
而加减运算是在二进制层面进行的,x和ux、y和uy的二进制表示是一样的, 也就是说,等式两边都是对同样的01串进行了数学意义上相同的运算操作。从而得到的运算结果的二进制表示是一样的。则这两个数转换成unsigned再进行等价比较就能得到true的结果。
*/
D: TRUE.
/*
令 y = (x >> 2) << 2
则 y 与 x 不同的地方在于最后两位,无论x的最后两位是00、01、10还是11,y的最后两位都是00
int last_x = x最后两位(0,1,2或3)
显然 last_x >= 0
所以 x = y + last_x >= y
*/
E: TRUE.
2.83
Fill in the return value for the following procedure, which tests whether its first argument is less than or equal to its second. Assume the function f2u returns an unsigned 32-bit number having the same bit representation as its floating-point argument. You can assume that neither argument is NaN. The two flavors of zero, +0 and −0, are considered equal.
int float_le(float x, float y) {
unsigned ux = f2u(x);
unsigned uy = f2u(y);
/* Get the sign bits */
unsigned sx = ux >> 31;
unsigned sy = uy >> 31;
/* Give an expression using only ux, uy, sx, and sy */
return ______;
}
ANSWER
int float_le(float x, float y) {
unsigned ux = f2u(x);
unsigned uy = f2u(y);
/* Get the sign bits */
unsigned sx = ux >> 31;
unsigned sy = uy >> 31;
/* Give an expression using only ux, uy, sx, and sy */
return (sx > sy) ||
// x < 0 && y > 0。
// x == -0 && y == +0 时也会在这一步就return true,结果正确(虽然原理不正确...)
// x == +0 && y == -0,会进入下一个判断
(ux << 1 == 0 && uy << 1 == 0) ||
// x == +0 && y == -0
// << 1 用于去掉符号位;还要判断后面是否为0
(sx == 0 && sy == 0 && ux <= uy) ||
// x > 0 && y > 0
// 判断sx == 0 && y == 0,并需要ux <= uy
(sx == 1 && sy == 1 && ux >= uy);
// x < 0 && y < 0
// 判断sx == 1 && y == 1,并由负数的性质需要满足ux >= uy
}
2.89
You have been assigned the task of writing a C function to compute a floatingpoint representation of (2^{x}). You decide that the best way to do this is to directly construct the IEEE single-precision representation of the result. When (x) is too small, your routine will return 0.0. When (x) is too large, it will return (-infty). Fill in the blank portions of the code that follows to compute the correct result. Assume the function
u2f
returns a floating-point value having an identical bit representation as its unsigned argument.
float fpwr2(int x)
{
/* Result exponent and fraction */
unsigned exp, frac;
unsigned u;
if (x < _____) {
/* Too small. Return 0.0 */
exp = _____;
frac = _____;
} else if (x < _____) {
/* Denormalized result */
exp = _____;
frac = _____;
} else if (x < _____) {
/* Normalized result. */
exp = _____;
frac = _____;
} else {
/* Too big. Return +oo */
exp = _____;
frac = _____;
}
/* Pack exp and frac into 32 bits */
u = exp << 23 | frac;
/* Return as float */
return u2f(u);
}
ANSWER
float fpwr2(int x)
{
/* Result exponent and fraction */
unsigned exp, frac;
unsigned u;
if (x < -149) {
/* Too small. Return 0.0 */
/*
* 判断条件 x: k = 8,故 bias = 2^(k-1)-1 = 127, E_min = 1 - bias = -126。 又因为 n = 23,因此 2 ^ E * M 能表示的最接近于0的数是 2 ^ (-126 + -23) = 2 ^ (-149)
*/
/* 0.0 对应 exp = 0, frac = 0*/
exp = 0;
frac = 0;
} else if (x < -126) {
/* Denormalized result */
/*
* 判断条件 x:最小的规格化数:exp = 1 && frac = 0,
* ∴ E = exp - bias = 1 - 126 && M = 1 + f = 1, V = 2 ^ E * M = 2 ^ (-126)
* 又∵ 最大的非规格化数 < 最小的规格化数
* ∴ x < -126
*/
/*
* 非规格化数:exp == 0
*
* ∵ 要表达的是2^x,且 2 ^ x = V = 2 ^ E * M
* ∴ f = M = 2 ^ (x - E) = 2 ^ (x + 126)
* ∵ f = frac / 2 ^ (23)
* ∴ frac = f * 2 ^ (23) = 2 ^ (x + 149) = 1 << (x + 149)
*/
exp = 0;
frac = 1 << (x + 149);
} else if (x < 128) {
/* Normalized result. */
/*
* 判断条件 x:最大的规格化数:exp = 11111110, frac = 2^(23) - 2
* E = exp - bias = 254 - 127 = 127 && M = 1 + f = 2 - 2 ^ (-22)
* 对应的浮点数 2 ^ x = V = 2 ^ E * M = 2 ^ (127) * (2 - 2 ^ (-22)) = 2 ^ (128) - 2 ^ (105) < 2 ^ (128)
* ∴ x < 128
*/
/*
* ∵ 2 ^ x = V = 2 ^ E * M = 2 ^ (exp - bias) * (f + 1)
* ∴ f = 2 ^ (x + bias - exp) - 1
* ∴ f 是整数
* 又∵ f ∈ {0/n, 1/n, ..., n-1/n}, 其中 n = 2^(23)
* ∴ f = 0 / 2^(23) = 0
* ∵ f = frac / 2^(23)
* ∴ frac = 0
*
* ∴ M = f + 1 = 1
* ∴ 2 ^ x = 2 ^ (exp - bias)
* ∴ exp = x + bias = x + 127
*/
exp = x + 127;
frac = 0;
} else {
/* Too big. Return +oo */
// 无穷大:必定为 e = 11111110, M = 2^(23) - 1
exp = (1 << 8) - 2;
frac = (1 << 23) - 1;
}
/* Pack exp and frac into 32 bits */
u = exp << 23 | frac;
/* Return as float */
return u2f(u);
}
2.90
Around 250 B.C., the Greek mathematician Archimedes proved that (frac{223}{71} lt pi < frac{22}{7}). Had he had access to a computer and the standard library
<math.h>
, he would have been able to determine that the single-precision floating-point approximation of π has the hexadecimal representation 0x40490FDB. Of course, all of these are just approximations, since π is not rational.
A. What is the fractional binary number denoted by this floating-point value?
B. What is the fractional binary representation of (frac{22}{7})? Hint: See Problem 2.82.
C. At what bit position (relative to the binary point) do these two approximations to (pi) diverge?
ANSWER
A:
0100 0000 0100 1001 0000 1111 1101 1010
B:
/**
* 1/7 - 2 ^ (-3) = 1/(7*8),
* 1/(7*8) - 2 ^ (-6) = 1/(7*8^2),
* 1/(7*8^2) - 2 ^ (-9) = 1/(7*8^3),
* ...
*/
22/7 = 3 + 1/7 = 11.001001001...
C:
/**
* 10/71 - 2 ^ (-3) = 9/(71 * 8),
* 9/(71 * 8) - 2 ^ (-6) = 8/(71 * 8 ^ 2),
* 8/(71 * 8 ^ 2) - 2 ^ (-9) < 0
* 8/(71 * 8 ^ 2) - 2 ^ (-10) > 0
*/
223/71 = 3 + 10/71 = 11.0010010001...
所以这两个π的近似值是从小数点后第9位开始不同的
2.91
Following the bit-level floating-point coding rules, implement the function with the following prototype:
/* Compute -f. If f is NaN, then return f. */
float_bits float_negate(float_bits f);
For floating-point number (f), this function computes (−f). If (f) is (NaN), your function should simply return (f). Test your function by evaluating it for all (2^{32}) values of argument (f) and comparing the result to what would be obtained using your machine’s floating-point operations.
Note by Khunkin : float_bits type is unsigned type.
ANSWER
/* Compute -f. If f is NaN, then return f. */
/* 为了可读性我写得稍微不那么简洁 */
float_bits float_negate(float_bits f){
/* 判断是否NaN */
float_bits temp = f & ((1 << 31) - 1); //把符号位置0
float_bits exp = temp >> 23;
if(exp == (1 << 8) - 1){
float_bits frac = (temp << 9) >> 9;
if(frac != 0){
return f;
}
}
/* 利用溢出实现只对符号位取反 */
return f + (1 << 31);
}
/*
// 简洁版(调皮一下)
float_bits float_negate(float_bits f){
return ((f & ((1 << 31) - 1)) >> 23 == (1 << 8) - 1 && ((f & ((1 << 31) - 1)) << 9) >> 9 != 0) ? f : f + (1 << 31);
}
*/
/* 测试程序 */
/*
* 我发现 bin_to_float(0x7f800001) != bin_to_float(0x7f800001), 也就是NaN之间无法直接比较相等...所以就用i和 float_negate 函数的返回结果进行比较了
* 函数的效果是如果float_negate函数返回结果不对则输出对应的unsigned数。
* 验证的结果是这个float_negate函数没有问题
* 如果测试程序有BUG请帮忙指出,谢谢!
*/
#include <stdio.h>
#define float_bits unsigned
float_bits float_negate(float_bits f){
/* 判断是否NaN */
float_bits temp = f & ((1 << 31) - 1); //把符号位置0
float_bits exp = temp >> 23;
if(exp == (1 << 8) - 1){
float_bits frac = (temp << 9) >> 9;
if(frac != 0){
return f;
}
}
/* 利用溢出实现只对符号位取反 */
return f + (1 << 31);
}
float bin_to_float(unsigned int x) {
union {
unsigned int x;
float f;
} temp;
temp.x = x;
return temp.f;
}
int is_INF(unsigned f){
return (f >= 0x7f800001 && f <= 0x7fffffff) || (f >= 0xff800001 && f <= 0xffffffff);
}
void judge(unsigned i){
if(is_INF(i)){
if(i != float_negate(i)){
printf("%x
", i);
return;
}
}
else if(-bin_to_float(i) != bin_to_float(float_negate(i))){
printf("%x
", i);
return;
}
}
int main(){
/* 这个printf函数用来验证即使二进制表示相同、但是NaN==NaN仍然返回0 */
printf("(NAN == NAN) == %d
", bin_to_float(0x7f800001) == bin_to_float(0x7f800001));
for(unsigned i = 0x00000000; i < 0xffffffff; i++){
judge(i);
}
judge(0xffffffff);
return 0;
}