最近看完了 CSAPP 整本书,发现官网上还有 11 次实验可以做。
UPD:好像只有 9 个,因为有两个是旧版本的,可以被新版的替代掉。
UPD:好像只有 8 个,performance
也算是旧的实验了,但是没有明确指出。
Lab 地址:
bitXor(x,y)
题意
任务: 异或 (x ^ y
)
允许使用的运算符:~ &
操作数限制:
Solution
对于每一位分别考虑。假设 分别是 的某一个对应位,那么这一位上的异或和就是
Code
1 | int bitXor(int x, int y) { |
共 次操作。
tmin()
题意
任务:二进制补码下最小的 int
类型的数
允许使用的运算符:! ~ & ^ | + << >>
操作数限制:
Solution
这个题目非常简单,答案就是 ,在 int
补码下是 后面接 个零,只有最高位为 ,也就是 1 << 31
。
Code
1 | int tmin(void) { |
共 次操作。
isTmax(x)
题意
任务:判断 是不是补码表示下最大的 int
类型数。
允许使用的运算符:! ~ & ^ | +
操作数限制:
Solution
我的做法比较奇怪吧。
最大的 int
类型数也就是 (0x7fffffff
)。
如果 满足要求,那么 就是 (0x80000000
),等于 ~x
。可以使用 ~((x + 1) ^ x) = 0
来判断。
但是需要注意的是,(0xffffffff
)也满足这个性质,因此需要特判这种情况。可以使用 !(x + 1) = 1
来判断。
因此综合起来,用 表示 ,最后的答案就是 !((~(a ^ x)) | !a)
。
Code
1 | int isTmax(int x) { |
共 次操作。
allOddBits(x)
题意
任务:判断 的奇数位是否都是 。
允许使用的运算符:! ~ & ^ | + << >>
操作数限制:
Solution
这题难住了我不短的时间。
其实是一开始没有想到直接构造出所有奇数位的掩码。
题目限制只能使用 和 之间的整数,所以可以使用 0xAA
(10101010
)扩展两次得到 位的奇数位掩码。
假设 a = 0xAA
,那么使用 (a << 8) + a
即可得到 位奇数位掩码,将结果存进 中,再使用 msk = (a << 16) + a
即可得到完整 位的掩码。
使用 x & msk
就是 所有奇数位,使用 ^
与 进行对比,异或结果为 说明相同,再取非即可得到答案。
Code
1 | int allOddBits(int x) { |
共 次操作。
negate(x)
题意
任务:返回 的补码。
允许使用的运算符:! ~ & ^ | + << >>
操作数限制:
Solution
经典问题,答案就是取反再加 ,也就是 ,不解释。
Code
1 | int negate(int x) { |
共 次操作。
isAsciDigit(x)
题意
任务:判断 对应的 ASCII 码是不是数字字符(也就是 0x30 到
0x39` 之间)。
允许使用的运算符:! ~ & ^ | + << >>
操作数限制:
题解
首先判断高 位是不是 0x3
。使用 !((x >> 4) ^ 3
判断。
然后判断低 位是不是 1001
及以下。考虑到不合法的分别有 1010, 1011, 1100, 1110, 1111
这些数,满足第 位(最低位为第 位)为 ,且第 位至少有一位为 。只要这两个条件有一个不满足就可以认为合法。
第 位不为 可以用 !(x & 8)
判断。
第 位不存在一位为 可以用 !(x & 6)
判断(6 = 0110
)。
Code
1 | int isAsciiDigit(int x) { |
共 次操作。
conditional(x, y, z)
题意
任务:返回 C 语言 x ? y : z
的结果。
允许使用的运算符:! ~ & ^ | + << >>
操作数限制:
Solution
假设我们可以把 扩展成一个 ,满足如果 为真,那么 k = 111...1
,否则 ,那么我们可以把答案表示成 (k & y) | ((~k) & z)
。
考虑怎么扩展。我们可以用 得到 的逻辑值的非,只有末位可能为 ,高位全部为 ,作为初始的 ,然后倍增构造。
使用 k = (k << 1) | k
构造两位版本,然后 k = (k << 2) | k
构造四位,以此类推,即可得到完整的 。
当然,最后得到的 与我们之前假设的相反,为 !x
的扩展,因此答案表达式也需要稍作改变。
Code
1 | int conditional(int x, int y, int z) { |
共 次操作。
isLessOrEqual(x, y)
题意
任务:判断 int
类型下 是否小于等于 。
允许使用的运算符:! ~ & ^ | + << >>
操作数限制:
Solution
一般来说,只需要判断 的正负性即可。 可以使用 加上 的补码得到,也就是 x + (~y) + 1
。如果 需要特殊判断。
但是,如果 发生溢出,那么这样判断就会不准确了。因此我们可以先判断 各自的符号,只有在同号的时候我们才判断 。如果 符号位不同,可以直接判断结果。
一个数的符号位可以通过 (x >> 31) & 1
获得。
具体地,假设 ,nx, ny, nz
分别为 的符号位,f1
表示 的逻辑值。那么如果 为真,或者 负 正(nx & !ny
)答案就是 1
,此外,除非 nx
和 ny
相同(nx ^ !ny
)才会需要 的符号位。
结果就是 f1 | (nx & !ny) | ((nx ^ !ny) & z)
,其中 !ny
重复使用,可以增加一个中间变量来节省操作数。
Code
1 | int isLessOrEqual(int x, int y) { |
共 次操作。
logicalNeg(x)
题意
任务:使用逻辑非,即 !x
。
允许使用的运算符:~ & ^ | + << >>
操作数限制:
Solution
和之前 conditional
这个题中构造 的过程很相似,不是要反过来做。
每次我们把 高位的一半和低位的一半做按位或(|
),然后假装 少了一半位,那么当 只剩一位的时候, 就是所有位的或了。
那么只需要将末位取出,然后 ^ 1
构造出非。
Code
1 | int logicalNeg(int x) { |
共 次操作。
howManyBits(x)
题意
任务:返回补码形式下,表达 最少需要多少位。
允许使用的运算符:! ~ & ^ | + << >>
操作数限制:
Solution
愣是读题读了半天才理解……这里说的补码形式是,一个二进制串最高位为符号位,剩下的使用补码构造。
那么如果 是负数,我们将之取反,得到的结果不变。
然后我们只需要找到最高的一个 的位置,倍增统计即可。
具体地,假设 现在是 位,我们取出最高的 位,判断是否全是 ,如果是的话就将 设置为低 位,否则将答案加上 然后将 设置为高 位。
位数折半,重复以上过程直到最后一位做完。
答案再上加 的符号位即可。
Code
1 | int howManyBits(int x) { |
共 次操作。
floatScale2(uf)
题意
任务:以 unsigned
类型给出一个 位浮点数 的位级表示,求出 的位级表示。
允许使用的运算符:所有整数操作符,包括 &&, ||
和 if, while
。
操作数限制:
Solution
首先用下面的代码求出符号、尾数和阶码。
1 | int s = uf & (1 << 31); |
如果为特殊值(无穷大和 NaN
),那么保持不变返回即可。
如果为非规格数,uf << 1
即可得到符号位之外的正确结果(如果尾数部分溢出了会刚好溢出到阶码中),接上符号位即可得到答案。
如果为规格数,那么将阶码 ,如果阶码变成了 0xFF
,答案就是无穷大,否则将符号、新的阶码和尾数拼接即可。
Code
1 | unsigned floatScale2(unsigned uf) { |
共 次操作。
floatFloat2Int(uf)
题意
任务:以 unsigned
类型给出一个 位浮点数 的位级表示,求出 (int)uf
的结果。
允许使用的运算符:所有整数操作符,包括 &&, ||
和 if, while
。
操作数限制:
Solution
将阶码 得到真正的指数 e
。
如果 ,那么说明 ,答案是 。
如果 ,那么说明 ,超过了 int
的表示范围,返回题目要求的 0x80000000u
。(ps:不用特判 的情况,因为 0x80000000u
刚好就是对应的值)
否则,我们取出尾数,再在最前面加上一个 (浮点表示法中被省略的 ),就是尾数表示的值乘上 。根据浮点数的结构,依据 和 的大小将尾数左右移即可。如果 ,那么将尾数左移 位,否则右移 位。
Code
1 | int floatFloat2Int(unsigned uf) { |
共 次操作。
floatPower2(x)
题意
任务: 的浮点数位级表示。
允许使用的运算符:所有整数操作符,包括 &&, ||
和 if, while
。
操作数限制:
Solution
浮点数阶码的表示范围是 到 。
如果 ,那么答案就是无穷大。
否则,如果 ,那么可以用规格数表示,将 嵌进 到 位上即可((x + 127) << 23
)。
否则,如果 ,那么 可以用非规格数表示,因为非规格数尾数部分第 位表示 ,因此答案就是 1 << (x + 149)
。
否则无法表示,返回 。
Code
1 | unsigned floatPower2(int x) { |
共 次操作。
ps:这个问题测试数据好像特别多,在时间限制内评测不完,需要在 btest.c
中将 TIMEOUT_LIMIT
宏改大一些,比如 。