环境:Linux 虚拟机
实验准备
首先前往 [官网](CS:APP3e, Bryant and O’Hallaron) 下载实验文件。
将实验文件放到实验目录后输入命令
tar xvf datalab-handout.tar
解压。
阅读 README
可知,我们需要按照注释对 bits.c
进行补充。
当有了合法的解答时,就可以用包内的 btest
程序来测试正确性。
btest.c
是测试代码的工具,编译它(使用 make btest
命令),可以获得一个可执行文件,执行它会对你的代码进行测试,注意每次修改完代码后都需要重新编译。
bitXor
使用 ~
与 &
实现两个数的异或。
用 x & y
可以得到全 1
的位置,那么 ~(x & y)
就得到了一1
一0
与全0
的位置。
用 ~x & ~y
可以得到全 0
的位置,那么 ~(~x & ~y)
就得到了一1
一0
与全1
的位置。
把这两个与起来,就得到了一1
一0
的位置,即异或的值。
int bitXor(int x, int y) {
return (~(x & y)) & (~(~x & ~y));
}
make btest
编译后,执行./btest -f bitXor
可以对该函数进行检查,发现结果如下:
Score Rating Errors Function
1 1 0 bitXor
Total points: 1/1
是正确的。
tmin
返回 Tmin
。
简单粗暴。
int tmin(void) {
return 1 << 31;
}
经过 btest
测试,是正确的。
isTmax
检查一个数是否是 Tmax
。
一开始写的是
!((x + 1) ^ (~x))
测试后发现有错误,数据为 x = -1
。
仔细想了想,大概只有这一种情况会出现错误(?),所以加了个特判,测试后发现过了(?)
int isTmax(int x) {
return !((x + 1) ^ (~x)) & !!(x + 1);
}
allOddBits
当所有奇数位都为 1
时返回 1
;否则返回 0
。
比较简单。
int allOddBits(int x) {
int mask = 0xAA;
mask |= mask << 8;
mask |= mask << 16;
return !(mask ^ x & mask);
}
经测试,正确。
negate
给出 x
,返回 -x
。
算是常识?
int negate(int x) {
return ~x + 1;
}
isAsciiDigit
给出 x
判断是否在 0x30
到 0x39
内。
方法比较笨。
在这范围内,那么 x
除了后六位,前面应该都是 0
;此外,其第二个字一定是 3
;另外,若第四位上是 0
则符合条件,若是 1
,则其第三、二位都要是 0
。
按照这个思路写出表达式即可。
int isAsciiDigit(int x) {
int a = x >> 6;
int b = x >> 4;
int c = x >> 3;
return !a & !(b ^ 0x3) & (!(((x >> 1) & 0x7) ^ 0x4) | !(c & 1));
}
conditional
实现三目运算符。
想到了第二章家庭作业有道题的解法。考虑构造掩码 mask=0xffffffff
,利用其值为 -1
的性质。
int conditional(int x, int y, int z) {
int mask = 0xff;
mask |= mask << 8;
mask |= mask << 16;
return ((!x + mask) & y) | ((!!x + mask) & z);
}
isLessOrEqual
给出 x
和 y
,若 x <= y
返回 1
,反之返回 0
。
这道题想了比较长的时间。
上面的 negate
给了我思路。
比较两个数大小常用的方法是作差,但是这题不允许使用运算符 -
,那么就可以用到上面的 negate
了。
做减法的话溢出怎么办?
只考虑返回 1
的情况(x <= y
):
第一种情况是 x
为负,y
为正;第二种情况为 x,y
同号,同号相减的话是不会溢出的,所以可以作差。
int isLessOrEqual(int x, int y) {
int sign_x = x >> 31;
int sign_y = y >> 31;
int negate_y = ~y + 1;
return (sign_x & !sign_y) | (!(sign_x ^ sign_y) & (((x + negate_y) >>31 & 1) | !(x + negate_y)));
}
logicalNeg
实现运算符非 !
。
只要 x
的位级表示中有 1
,就返回 0
。
所以想到了书上家庭作业中奇数个 1
那道题的处理方式,每次移动一半然后或起来,就能求出 x
的位级表示中是否有 1
。
int logicalNeg(int x) {
x |= x >> 16;
x |= x >> 8;
x |= x >> 4;
x |= x >> 2;
x |= x >> 1;
x &= 1;
x ^= 1;
return x;
}
howManyBits
给出 x
,求出其补码最少用多少位。
这题也想了很长时间,有思路但是不知如何实现,看了看别人的解法。
发现了,如果是正数,就是找从左起第一个 1
的位置;负数,就是找从左起第一个 0
的位置。
对于负数,可以考虑取个反,这样转化成了同一个问题。
注意到这是存在着一种“单调性”的,所以可以考虑二分,用位运算实现。这个实现方法是看别人的,感觉比较妙。
int howManyBits(int x) {
int mask = x >> 31;
x = (~mask & x) | (mask & ~x);
int a = !!(x >> 16) << 4;
x >>= a;
int b = !!(x >> 8) << 3;
x >>= b;
int c = !!(x >> 4) << 2;
x >>= c;
int d = !!(x >> 2) << 1;
x >>= d;
int e = !!(x >> 1);
x >>= e;
return a + b + c + d + e + x + 1;
}
floatScale2
给出 f
,返回 2*f
。
unsigned floatScale2(unsigned uf) {
int s = uf >> 31;
int exp = (uf >> 23 & 0xff);
int frac = uf & 0x7fffff;
if(!(exp ^ 0xff)) return uf;
if(!exp) return (s << 31) | (frac << 1);
return (s << 31) | ((exp + 1) << 23) | frac;
}
floatFloat2Int
给出 f
,返回 (int)f
。
int floatFloat2Int(unsigned uf) {
int s = uf >> 31;
int exp = (uf >> 23 & 0xff);
int frac = uf & 0x7fffff;
if(!(exp | frac)) return 0;
exp -= 127;
if(exp < 0) return 0;
if(exp > 30 + (s & !frac)) return 0x80000000u;
frac = ((1 << 23) | frac) >> (23 - exp);
if(s) return -frac;
return frac;
}
floatPower2
给出 x
,给出浮点数下的 2.0^x
。
unsigned floatPower2(int x) {
x += 0x7f;
if(x < 0) return 0;
return x < 0xff ? x << 23 : 0x7f800000u;
}
实验检测
均正确。
实验感想
除了浮点数部分,其他的自己都做了很深入的思考。
浮点数部分基本上是参照别人的。(计组没学好TAT)。
感觉光看书还是很难学到位。第三章开始考虑观看 b 站上搬过来的有翻译的CMU课程了。