《深入理解计算机系统》这本书每章后面都有配套的CMU的课后lab,难度还是很高的。做这些lab一方面能够让自己更加深入理解本章的内容,看不如做。
本章的Data Lab限制了我们的编程方式,比如不让使用if,while等,让我们能够从不同的角度去思考问题,解法不唯一,但是这些解法带给我们的灵感是非常珍贵的。
第一部分是关于整数的题目,这部分限制只能使用0x00 - 0xff大小的常数,一般只允许使用!, &, |, <<, <<. ~等运算符。
- 使用~和&实现异或操作。
这个方法有很多,比较简单。我的思路是从异或的概念入手,只需要实现相同位异或为0,不同位异或为1。x&y会将01和10变为0,00是0,11是1,那么只需要取反再按位与一次即可。
/*
* bitXor - x^y using only ~ and &
* Example: bitXor(4, 5) = 1
* Legal ops: ~ &
* Max ops: 14
* Rating: 1
*/
int bitXor(int x, int y) {
//let 11 and 00 be 1&0 and 0&1, let 10 and 01 be 1&1
return (~(x&y)) & (~((~x)&(~y)));
}
- 求出32位有符号数的最小值。
这里考察了补码的概念,因为只有正0,没有负0,所以负数表示范围比正数大一。
/*
* tmin - return minimum two's complement integer
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 4
* Rating: 1
*/
int tmin(void) {
return 1<<31;
}
- 如果是有符号数的最大值返回1,否则返回0。
这里利用了最大正数的一个特点,由于溢出(x+1)+(x+1)= 0,但是符合这一条件还有一个数是-1,所以再将-1排除即可。
/*
* isTmax - returns 1 if x is the maximum, two's complement number,
* and 0 otherwise
* Legal ops: ! ~ & ^ | +
* Max ops: 10
* Rating: 1
*/
int isTmax(int x) {
//x+1 left shift 1 equal to 0, but x+1 don't equal to 0
return !(x+x+2) & !!(x+1);
}
- 如果一个数的所有奇数位都被置1则返回1,否则返回0(从0开始计数)。
利用对称性,因为所有的奇数位都是1,那么每次将x的高一半位和低一半位按位与的结果奇数位必然还是1,知道x只剩最后两位,然后看高位是不是1即可,奇数位只有一个不是1,最后必然是0。
/*
* allOddBits - return 1 if all odd-numbered bits in word set to 1
* where bits are numbered from 0 (least significant) to 31 (most significant)
* Examples allOddBits(0xFFFFFFFD) = 0, allOddBits(0xAAAAAAAA) = 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 12
* Rating: 2
*/
int allOddBits(int x) {
x = x & (x>>16);
x = x & (x>>8);
x = x & (x>>4);
x = x & (x>>2);
return (x>>1)&1;
}
- 求x的加法逆元。
注意最大负数的逆元是他本身。这个题比较简单,但是后面会大量使用,将减法转化成加法。
/*
* negate - return -x
* Example: negate(1) = -1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 5
* Rating: 2
*/
int negate(int x) {
return ~x+1;
}
- 如果x的值是字符‘0’-‘9’的ASCII码值之间,即0x30<= x <= 0x39返回1,否则返回0
只需要说明x-0x30和0x39-x都是正数就行,即使计算溢出也可以给出正确结果。
/*
* isAsciiDigit - return 1 if 0x30 <= x <= 0x39 (ASCII codes for characters '0' to '9')
* Example: isAsciiDigit(0x35) = 1.
* isAsciiDigit(0x3a) = 0.
* isAsciiDigit(0x05) = 0.
* Legal ops: ! ~ & ^ | +i << >>
* Max ops: 15
* Rating: 3
*/
int isAsciiDigit(int x) {
int a = 0x30, b = 0x39;
int moreThan = !((x+(~a+1))&(1<<31));
int lessThan = !((b+(~x+1))&(1<<31));
return moreThan & lessThan;
}
- 实现三目运算法?:。
首先考虑这样一个表达式:a&y | b&z,这样只需要实现当x非0的时候,a为全1,b为全0;当x为0的时候,a为全0,b为全1,通过构造得出a = !x-1,b = ~!x +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) {
int f = !x;
return ((f+~1+1)&y) | ((~f+1)&z);
}
- 如果x<=y返回1,否则返回0
仍然是做差,不过是要考虑溢出问题,所以分情况讨论
当x为正数,y为负数,返回0
当x为负数,y为正数,返回1
当x与y同号,此时做差不会溢出,判断差的符号位即可。
/*
* isLessOrEqual - if x <= y then return 1, else return 0
* Example: isLessOrEqual(4,5) = 1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 24
* Rating: 3
*/
int isLessOrEqual(int x, int y) {
int a = x>>31;
int b = y>>31;
int c = a ^ b;
int diffSymbol = c&(a&1);
int sameSymbol = !c&!(((y+~x+1)>>31)&1);
return sameSymbol | diffSymbol;
}
- 实现!
换一个角度来看,其实就是对于0返回1,对于非0返回0,所以只需要识别出0即可, 0具有一个特殊性质即0的逆元是本身,但是0xffffffff也是,此外0和0逆元的符号位都是0,其他的至少有一个是1,所以只需要对所有的符号位进行按位或,这样只有0的结果是0,然后取反即可。
/*
* logicalNeg - implement the ! operator, using all of
* the legal operators except !
* Examples: logicalNeg(3) = 0, logicalNeg(0) = 1
* Legal ops: ~ & ^ | + << >>
* Max ops: 12
* Rating: 4
*/
int logicalNeg(int x) {
return ((~(x|(~x+1)))>>31)&1; // 最后一个&1不能删,因为最高位为1的时候,算术右移补的都是1,不是0
}
- 统计x最少需要多少位才能用补码表示出来
这个题比较难,首先考虑正数情况,可以进行二分,每次看一半,然后加起来加上符号位。
但是负数就比较麻烦,因为用的是补码,经过分析得出一个规律,负数只要直接取反,需要的位数是相同的。
注意只能对负数取反,正数保持不变。
/* howManyBits - return the minimum number of bits required to represent x in
* two's complement
* Examples: howManyBits(12) = 5
* howManyBits(298) = 10
* howManyBits(-5) = 4
* howManyBits(0) = 1
* howManyBits(-1) = 1
* howManyBits(0x80000000) = 32
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 90
* Rating: 4
*/
int howManyBits(int x) {
int shift16, shift8, shift4, shift2, shift1, shift0;
int s = x>>31;//负数s=1,正数s=0
x = x ^ ((s<<31)>>31);//正数^ 0 ,负数 ^ 0xffffffff,这里用到了算术右移的性质
shift16 = (!!(x>>16))<<4;
x = x>>shift16;
shift8 = (!!(x>>8))<<3;
x = x>>shift8;
shift4 = (!!(x>>4))<<2;
x = x>>shift4;
shift2 = (!!(x>>2))<<1;
x = x>>shift2;
shift1 = (!!(x>>1));
shift0 = !!x;
return shift16 + shift8 + shift4 + shift2 + shift1 + shift0 + 1;
}
第二部分是关于浮点数的题目,这部分基本可以使用所有的常量以及运算符,if,while等关键字
- 求浮点数x*2的结果
这里考察的是浮点数存储方式和乘法,需要分情况讨论
如果浮点数是特殊值,即阶码全1的时候,直接返回结果
如果浮点数是非规格化的值,即阶码全0的时候,只需要对小数域左移一位即可(注意小数域到达最后,再左移就会使阶码值+1,因为这两个域是连续的)
如果浮点数是规格化的值,需要对阶码+1,然后检测是不是达到无穷大,如果是要将小数域置为0,不要出现NaN值。
最后保持符号位不变即可。
//float
/*
* floatScale2 - Return bit-level equivalent of expression 2*f for
* floating point argument f.
* Both the argument and result are passed as unsigned int's, but
* they are to be interpreted as the bit-level representation of
* single-precision floating point values.
* When argument is NaN, return argument
* Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
* Max ops: 30
* Rating: 4
*/
unsigned floatScale2(unsigned uf) {
unsigned int s = uf & 0x80000000;
unsigned int exp = uf & 0x7f800000;
unsigned int frac = uf & 0x007fffff;
if(exp == 0x7f800000){
return uf;
}else if(!exp){
frac <<= 1;
}else {
exp = exp + (1<<23);
if(exp == 0x7f800000){
frac = 0;
}
}
return (s|exp|frac);
}
- 将浮点数值转化为int值,如果过大则返回1<<31
同样是要对浮点数进行分类讨论,但是这次的分类标准与上次有所不同
如果浮点数与0直接判==是true则说明,浮点数接近0,或者阶码是小于0的都直接返回0即可
如果阶码大于31,则说明这个浮点数一定会溢出,直接返回最大值
其他情况对浮点数的小数域进行正常移位操作即可,注意规格数的尾数是小数域+1
最后注意符号位保持不变
/*
* floatFloat2Int - Return bit-level equivalent of expression (int) f
* for floating point argument f.
* Argument is passed as unsigned int, but
* it is to be interpreted as the bit-level representation of a
* single-precision floating point value.
* Anything out of range (including NaN and infinity) should return
* 0x80000000u.
* Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
* Max ops: 30
* Rating: 4
*/
int floatFloat2Int(unsigned uf){
int INF, s, exp, formatFrac;
INF = 1<<31;
s = uf>>31;
exp =(uf & 0x7f800000) >> 23;
formatFrac = uf & 0x00ffffff;
exp -= 127;
if(0 == uf || exp<0)
return 0;
if(exp > 31){
return INF;
}
if(exp > 23){
uf = formatFrac<<(exp-23);
}else{
uf = formatFrac>>(23-exp);
}
if(s){
uf = ~uf+1;
}
return uf;
}
- 计算pow(2.0, x)
计算2.0的x次幂,其实只与浮点数的阶码有关,因为浮点数的阶码就是表示2的多少次幂,分类讨论
如果阶码值小于0则返回0,
如果阶码值大于255则说明阶码达到最大,返回最大值即可
其他情况只需要将阶码左移23位,即小数域直接置为0即可,对于规格数来说,小数域全0则是1
/*
* floatPower2 - Return bit-level equivalent of the expression 2.0^x
* (2.0 raised to the power x) for any 32-bit integer x.
*
* The unsigned value that is returned should have the identical bit
* representation as the single-precision floating-point number 2.0^x.
* If the result is too small to be represented as a denorm, return
* 0. If too large, return +INF.
*
* Legal ops: Any integer/unsigned operations incl. ||, &&. Also if, while
* Max ops: 30
* Rating: 4
*/
unsigned floatPower2(int x) {
unsigned INF = 0xff << 23;
int e = 127 + x;
if (x < 0) return 0;
if (e >= 255) return INF;
return e << 23;
}
总体来说这个Lab的题还是有一定挑战的,不仅能够让我们在读书过程中对书中的知识理解的更加深入,而且也能启发我们从不同的角度进行编程,体会位运算的神奇之处,毕竟对于计算机来说只有位运算才是最快的。
参考文献:曾参考此篇博客