华盛顿大学的 CSE 351 课程 The Hardware / Software Interface https://courses.cs.washington.edu/courses/cse351/17wi/schedule.html ,该课程原来有一个 Coursera 版本,后来 Coursera 平台大升级,导致这门课在 Coursera 看不了。不过还是可以华盛顿大学的网站上找得到对应的课程视频,地址在这里 https://courses.cs.washington.edu/courses/cse351/17wi/videos.html。
该课程的动手操作实验移植于计算机经典书籍《深入理解计算机系统》的实验。这篇文章是第一个实验 Lab1 的解答。
规则限制
简单介绍一下代码实现的规则
- 不允许使用 if,else,以及其他条件语句
- 不能使用宏定义
- 不能添加额外的函数
- 不能调用其他函数
- 能使用的操作符会在每题题目中说明,除此之外其他的都不能使用
- 不能使用数据类型转换
- 不能使用 int 之外的其他数据类型
- 使用课程实验提供的虚拟机
注意:文章中提到的 “位置” 都是从 int 的二进制角度来描述的,位置是 int 的二进制表示中某个 bit 的所在排列位置。
第一题
题目:
/*
* bitAnd - x&y using only ~ and |
* Example: bitAnd(6, 5) = 4
* Legal ops: ~ |Xor
* Max ops: 8
* Rating: 1
*/
int bitAnd(int x, int y) {
return 2;
}
解题思路:
- x & y 的操作结果是,x 和 y 中所有对应位置的 bit 同时为 1 的时候结果为 1,否则结果为 0
- 使用 ~(x) | (~y) 操作将 x 和 y 中同时为 1 的 bit 的值置为 0,其他 bit 位置的值置为 1
- 执行 ((x)|(~y)) ,在 2 的结果上取反,得到解题结果
解题方案:
int bitAnd(int x, int y) {
return ~((~x)|(~y));
}
第二题
题目:
/*
* bitXor - x^y using only ~ and &
* Example: bitXor(4, 5) = 1
* Legal ops: ~ &
* Max ops: 14
* Rating: 1
*/
int bitXor(int x, int y) {
return 2;
}
解题思路:
- (x&y) 操作将 x 和 y 中 对应位置的 bit 值同时为 1 的 位置置
1 保留下来,其余位置置 0 - (x)&(y) 操作将 x 和 y 中 对应位置的 bit 值中同时为 0 的位置置 1 保留下来,其余位置置0
- (~(x&y)) 把 1 步骤的结果做 ~ 操作,将 x 和 y 中 对应位置的 bit 值中同时为 1 的位置置0,其余位置置1,保留下来
- (((x)&(~y))) 把 2 步骤的结果做 ~ 操作,将 x 和 y 中 对应位置的 bit 值中同时为 0 的位置置 0 ,其余位置置 1,保留下来
- ((x&y))&(((x)&(y))) 将 3 步骤和 4 步骤的结果做 & 操作,将 x 和 y 中对应位置同时为 0 和同时为 1 的位置置为 0 ,其他位置置为 1,得到解题结果
解题方案:
int bitXor(int x, int y) {
return (~(x&y))&(~((~x)&(~y)));
}
第三题
题目:
/*
* thirdBits - return word with every third bit (starting from the LSB) set to 1
* and the rest set to 0
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 8
* Rating: 1
*/
int thirdBits(void) {
return 2;
}
解题思路:
题目是要让我们构造一个这样的数据 0100 1001 0010 0100 1001 0010 0100 1001 。
- 我们设定一个初始数据 0100 1001,转成 16 进制表示 int x = 0x49
- 在 1 步骤上做 x << 9 操作得到数据 1001 0010 0000 0000,int y = x << 9
- 在 1 步骤和 2 步骤的基础上执行 (x + y) << 18 操作得到数据 0100 1001 0010 0100
- 在 3 步骤的基础上执行 ((x + y) << 18) + (x + y) 操作得到数据 0100 1001 0010 0100 1001 0010 0100 1001,得到解题结果
解题方案:
int thirdBits(void) {
int x = 0x49;
int y = x << 9;
return ((x + y) << 18) + (x + y);
}
第四题
题目:
/*
* fitsBits - return 1 if x can be represented as an
* n-bit, two's complement integer.
* 1 <= n <= 32
* Examples: fitsBits(5,3) = 0, fitsBits(-4,3) = 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 15
* Rating: 2
*/
int fitsBits(int x, int n) {
return 2;
}
解题思路:
这个题目是要判断一个数字能否被 n 个 bit 位表示
- 假定一个数字可以被 n 个 bit 位表示,那么也可以认为左边的 (32-n) 位是没用的位置,所以做 ((x << (32 - n)) 操作
- 在 1 步骤的基础上做 ((x << (32 - n)) >> (32 - n)) 操作,假定一个数字可以被 n 个 bit 位表示,那么在经过这个操作之后,这个数字还是不变的
- 在 2 步骤的基础上做 !(((x << (32 - n)) >> (32 - n)) ^ x ) 操作,判断 ((x << (32 - n)) >> (32 - n)) 和 x 是否是同一个数字,得到解题结果
解题方案:
int fitsBits(int x, int n) {
return (!(((x << (32 - n)) >> (32 - n)) ^ x ));
}
第五题
题目:
/*
* sign - return 1 if positive, 0 if zero, and -1 if negative
* Examples: sign(130) = 1
* sign(-23) = -1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 10
* Rating: 2
*/
int sign(int x) {
return 2;
}
解题思路:
- 取出 x 的符号位 (x >> 31) x为负数结果为 0x11111111 x为正数结果为 0x00000000 x为零结果为 0x00000000
- 取出 -x 的符号位 ((~x +1) >> 31) x为负数结果为 0x00000000 x为正数结果为 0x11111111 x为零结果为 0x00000000
- 在 2 步骤 的基础上进行 & 0x01 操作 (((~x +1) >> 31) & 0x01)) ,取出最后一位 x为负数结果为 0 x为正数结果为 1 x为零结果为 0
- 在 1步骤 和 3步骤 的基础上做 | 操作,x为负数结果为 0x11111111 ,x为正数结果为 0x00000001 , x为零结果为 0x00000000,得到解题结果
解题方案:
int sign(int x) {
return ((x >> 31) | (((~x +1) >> 31) & 0x01));
}
第六题
题目:
/*
* getByte - Extract byte n from word x
* Bytes numbered from 0 (LSB) to 3 (MSB)
* Examples: getByte(0x12345678,1) = 0x56
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 6
* Rating: 2
*/
int getByte(int x, int n) {
return 2;
}
解题思路:
1.一个 byte 等于 8(2的3次方) 个 bit,将 n 乘以 8 也就是 n = n << 3 得到想要取出的 byte 在 x 的二进制表示的开始位置
- x = x >> n,将想要取出的 byte 移动到最低 8 位
- (x & 0xff),将 x 与 0xff 进行 & 操作,取出最低 8 位,得到解题结果
解题方案:
int getByte(int x, int n) {
n = n << 3;
x = x >> n;
return (x & 0xff);
}
第七题
题目:
/*
* logicalShift - shift x to the right by n, using a logical shift
* Can assume that 0 <= n <= 31
* Examples: logicalShift(0x87654321,4) = 0x08765432
* Legal ops: ~ & ^ | + << >>
* Max ops: 20
* Rating: 3
*/
int logicalShift(int x, int n) {
return 2;
}
解题思路:
- 实现逻辑左移操作,需要一个 mask ,该 mask 可以将左到右的 n 个位置清零
- (0x1 << 31) 的操作结果为 0x10000000
- (0x1 << 31) >> n 将 2 步骤的结果右移 n 位 实现从左边到右边的 n+1 位为 1,其余位置为 0 的效果
- ((0x1 << 31) >> n << 1) 将 3 步骤的结果左移 1 位,实现从左边到右边的 n 位为 1,其余位置为 0 的效果
- (~((0x1 << 31) >> n << 1)) 的操作,实现从左边到右边的 n 位为 0,其余位置为 1 的效果
- (x >> n) & (~((0x1 << 31) >> n << 1)) 实现逻辑左移的效果,得到解题答案
解题方案:
int logicalShift(int x, int n) {
return (x >> n) & (~((0x1 << 31) >> n << 1));
}
第八题
题目:
* addOK - Determine if can compute x+y without overflow
* Example: addOK(0x80000000,0x80000000) = 0,
* addOK(0x80000000,0x70000000) = 1,
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 20
* Rating: 3
*/
int addOK(int x, int y) {
return 2;
}
解题思路:
- overflow 是指数据的表示超出了整数的表示范围,两个正整数或者两个负整数相加就有可能超过这个整型变量所能表示的范围,有可能产生 overflow
- x > 0, y > 0, (x + y) < 0 ,x < 0, y < 0, (x + y) > 0 这 2 种情况都是 overflow
- int a = x >> 31,取出 x 的符号位
- int b = y >> 31, 取出 y 的符号位
- int c = (x + y) >> 31,取出 x + y 的符号位
- !((~(a ^ b)) && (a ^ c)),判断 x,y,x+y 符号是否一致,得到解题答案
解题方案:
int addOK(int x, int y) {
int a = x >> 31;
int b = y >> 31;
int c = (x + y) >> 31;
return !((~(a ^ b)) && (a ^ c));
}
第九题
题目:
/*
* bang - Compute !x without using !
* Examples: bang(3) = 0, bang(0) = 1
* Legal ops: ~ & ^ | + << >>
* Max ops: 12
* Rating: 4
*/
int bang(int x) {
return 2;
}
解题思路:
- 在 c 语言中, 0 是 False,其他非 0 的都是 True
- 在 int 中,只有 x = 0 的时候才存在 -x 和 +x 相等的情况
- (((~x +1) | x) >> 31) 可以区分出 x 是否是 0,若是 x = 0 则结果 0,若是 x != 0 则结果为 -1
- 在 3 步骤的基础上执行 (((~x +1) | x) >> 31) +1,得到解题结果
解题方案:
int bang(int x) {
return (((~x +1) | x) >> 31) +1;
}
第十题
题目:
/*
* conditional - same as x ? y : z
* Example: conditional(2,4,5) = 4
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 16
* Rating: 3
*/
int conditional(int x, int y, int z) {
return 2;
}
解题思路:
- 解题的核心点在于如何利用 x 构造 False,如何利用 x 构造 True
- int a =(((!x) << 31) >> 31),利用结果来表示 x 等于 False 的情况
- 在 2步骤的基础上,int b = (~a), 利用结果来表示 x 等于 True 的情况
- (b&y) + (a&z),得到解题答案
解题方案:
int conditional(int x, int y, int z) {
int a =(((!x) << 31) >> 31);
int b = (~a);
return (b&y) + (a&z);
}
第十一题
题目:
/*
* isPower2 - returns 1 if x is a power of 2, and 0 otherwise
* Examples: isPower2(5) = 0, isPower2(8) = 1, isPower2(0) = 0
* Note that no negative number is a power of 2.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 20
* Rating: 4
*/
int isPower2(int x) {
return 2;
}
解题思路:
- 2的次幂数对应二进制表示应该只有 1 位 1,其余位置全部都是0,重点是如何判断一个数的二进制表示只有一个 1,然后再处理好负数和 0 的情况
- x 的二进制表示只有一个 1 那么会有这个规律 x &(x-1) = 0x0,由于题目不能使用 - 号,所以转换成 !(x & (x + (~0) ))
- 在 2 步骤的基础上,!(x &(x-1)) 的结果可以判断 x 是否为 2 的幂
- (!(x >> 31)) 可以判断 x 是否为负数
- 在 4 步骤的基础上执行 (!(x >> 31)) & (~(!x))) 可以判断 x 是否为 0
- ( !! ( x ^ 0x0 ) ) 可以判断 x 是否 0
- (!(x & (x + (~0))) & (!(x >> 31)) & (!!(x ^ 0x0))) ,将 x 是否为 2 的幂,判断 x 是否为负数 和 x 是否 0 ,3个判断进行 & 操作,得到解题答案
解题方案:
int isPower2(int x) {
return (!(x & (x + (~0))) & (!(x >> 31)) & (!!(x ^ 0x0)));
}
参考
- 华盛顿大学的 CSE 351 课程 The Hardware / Software Interface
https://courses.cs.washington.edu/courses/cse351/17wi/videos.html