CS:APP这本书真的可以说是计算机的内容 给你从头讲到尾了,虽然每个领域方面的深度不够,但是已经足够了,因为每一个领域都不是这么简单就能够说完的,这本书能把这么多东西讲得很清楚真的不容易,所以看完建议挑战一下lab。
要求:
不允许使用条件语句和循环语句,只允许使用8个运算符:! ˜ & ˆ | + >来完成,某些题目会额外限制运算符数量,最大只能使用8位整数。
PART1
bitAnd
只能使用~和|运算符完成&运算,运算符最多只能操作8次。
简单,对两个操作数分别取反再|就是他们两个都没有的位,然后再取反就是他们两个都有的位了。
int bitAnd(int x, int y) {
return ~((~x)|(~y));
}
getByte
获得x的第n个字节,运算符最多操作6次,只能使用!~&^|+<<>>。
也很容易,首先很容易想到获得x的第n个字节的方法是x>>(n*8)&0xff。但是不能用*号,所以改用<<运算符来代替*号。
int getByte(int x, int n) {
return x>>(n<<3)&0xff;
}
logicalShift
实现逻辑右移,只能使用~&^|+<<>>运算符,最大操作数是20。
这题颇有难度,首先我们普通的>>运算是算数右移,在这基础上把前n位清0就可以得到逻辑右移的结果,把前n位清0等于构造32-n位的1与之做&运算,构造这个可以用2**x - 1的方法,那么2**x又等于2<<x,-1则等于0取反。但是构造32-n和无法使用long long类型让我头疼,结果我还是去看了别人怎么写这个。。。用左移再右移的方法防止溢出的同时可以利用符号位来让前面填满1。这是个好技巧。
int logicalShift(int x, int n) {
return x>>n&(~(1<<31>>n<<1));
}
bitCount
计算整数里有多少个位是1,只能使用~&^|+<<>>运算符,最大操作数40。
这题直接算的话是肯定超操作数的,那么只能想办法分块然后同时操作了,我们把每8位分成一块,块里面的每一位我们可以枚举用&操作来判定,最后再把所有块的结果加起来就完了。
int bitCount(int x) {
int mask = ((1<<8|1)<<8|1)<<8|1;
int sum = x&mask;
sum += x>>1&mask;
sum += x>>2&mask;
sum += x>>3&mask;
sum += x>>4&mask;
sum += x>>5&mask;
sum += x>>6&mask;
sum += x>>7&mask;
sum += sum>>8&0xff;
sum += sum>>16&0xff;
sum += sum>>24&0xff;
return sum&0xff;
}
bang
做!运算,只能用~&^|+<<>>,最大操作数12。
这个首先得知道几个技巧,假设x的最低位为1的位置是i,x&(~x)+1可以得到结果只保留第i个1的数,即结果是2的i次方,而x|(~x)+1可以得到i及他以上的位置全部为1的数,即得到...111111100000...(i个0,总位数-i个1)。而0是没有1的,所以0做这两个操作结果还是0。而其他数做上述的第二个操作得到的最高位必定是1,那么我们就可以根据最高位来判定。
int bang(int x) {
return ((x|((~x)+1))>>31)&1^1;
}
PART2
在这个部分可以使用条件和循环语句,可以使用int和unsigned类型的整数,不能使用union,struct,数组,不能使用浮点数,浮点数都以unsigned类型传递并返回。
tmin
返回补码整数最小值。最大操作数4。
int tmin(void) {
return 1<<31;
}
fitsBits
判断是否能用n位二进制表示一个数,可以返回1,否则返回0.最大操作数15,只能使用!~&^|+<<>>。
这题描述不太好,怎么才算能用n位来表示一个数呢,他的意思是前缀相同的话是可以用一位来省略的,比如前缀全是0就可以用一个...0来表示,全是1则用...1来表示,那么问题就转换为去掉后缀n位后是否全是1或者全是0,具体思路看下面代码。
int fitsBits(int x, int n) {
int tmp = x>>(n+(~0));
return !tmp|(!(tmp+1));
}
divpwr2
计算x/(2**n)。结果向0取整。也就是负数向上取整,正数向下取整。最大操作数15,只能使用!~&^|+<<>>。
若是向下取整,则就是简单的右移运算,即x>>n就可以得到答案,题目需要向0取整,则只要在这基础上进行判定如果x<0&&x%2**n则让结果+1就可以得到答案。x<0直接判符号位,x%2**n则可以用&符号来解决,因为2**n是2的倍数。
int divpwr2(int x, int n) {
int add_number = (x>>31&1)&(!!(x&(1<<n)+(~0)));
return x>>n+add_number;
}
negate
计算-x。
这题就是送的嘛。。。直接套概念,取反+1完了。
int negate(int x) {
return (~x) + 1;
}
isPositive
判断x是否>0,是返回1,否则返回0。最大操作数5,只能使用!~&^|+<<>>。
首先如果判定是否>=0则只需要看符号位就好,所以只要在这基础上再判定是否为0就可以了,但是由于操作数只能为5次,这里将问题转换为是否为<=0再取反。
int isPositive(int x) {
return !(x>>31&1|(!x));
}
isLessOrEqual
判断x<=y,是返回1,不是返回0。最大操作数24,只能使用!~&^|+<<>>。
这题比较繁琐,首先你只能用位运算,但是x<=y如果不转换问题的话是最终必须要用逻辑运算符的,故第一个问题得先转换位y-x>=0,上面的题已经处理过-x和>=0的思路了,但是这道题还缺一个点是溢出问题,y-x是可能溢出的,所以我们要先解决溢出问题,分几种情况,如果y>0,x<=0那么我们可以直接得到答案,同理y<=0,x>0也是一样,也就是说我们只需要处理x,y同号的情况做y-x的判定即可。
int isLessOrEqual(int x, int y) {
int sign_x = x>>31&1;
int sign_y = y>>31&1;
int res = y+(~x)+1;
return (sign_x&!sign_y)|(!(!sign_x&sign_y)&(!(res>>31&1)));
}
ilog2
求2为底x的对数,并且向下取整。最大操作数90,只能使用!~&^|+<<>>。
说实话这个90真没多大意义,因为一位位算刚好超出几次,那没法一位位算就只能二分了。。
int ilog2(int x) {
int res = !x;
res = res + ((!!(x>>16))<<4);
res = res + ((!!(x>>res>>8))<<3);
res = res + ((!!(x>>res>>4))<<2);
res = res + ((!!(x>>res>>2))<<1);
res = res + ((!!(x>>res>>1)));
return res;
}
float_neg
求浮点数f的负数-f。当参数是NaN时返回参数,可以使用包含整数或者无符号整数的操作,可以使用||,&&还有if和while。最大操作数10。
这题其实也没啥,翻翻书看看float表示1(符号位)+8(指数位)+23(尾数),所以正常情况下直接把符号位取反就好。
但是还有NaN,NaN表示指数位全1尾数非全0,Inf指数位全1尾数全0,特判一下这两种情况就ok了。
unsigned float_neg(unsigned uf) {
if ((uf>>23&0xff) == 0xff)
if (uf&(1<<23)-1)
return uf;
return uf^0x80000000;
}
float_i2f
将int值转为float值,允许的操作同上,最大操作数30。
基本也是考定义和几种浮点数特殊表示法,还有向偶数舍入,舍入后如果尾数需要+1溢出直接+的话可以可以得到结果的,因为+1溢出后阶数就会+1,尾数会清0,刚好就是我们想要的结果。
浮点数的几种特殊表示:
1. 0的表示,如果阶码为0,尾数是0,那么根据符号位来表示这个数是+0还是-0。
2. 阶码为0跟阶码为255的情况都是特殊情况,其余都是正常情况,正常情况为规范编码。下面讨论非规范编码。
阶码全1:NaN或者Inf,情况上面的题目已经说了。
阶码全0,非规范编码,尾数默认非1.xxx开头,默认为2的-126次方,即可能为0.xxx*2^-126。
unsigned float_i2f(int x) {
unsigned number1, number2;
unsigned lefttop = 1<<31;
unsigned sign = x&lefttop;
unsigned leftshift = 0;
unsigned abs = sign?-x:x;
if(!x) return 0;
while(abs){
if(abs&lefttop){
abs <<= 1;
leftshift++;
break;
}
abs <<= 1;
leftshift++;
}
number1 = (abs & 0xFFFFFE00) >> 9;
number2 = abs & 0x000001FF;
if(number2>0x100)
number1 += 1;
else if(0x100 == number2 && number1 & 1)
number1 += 1;
return sign|(127+32-leftshift<<23)+number1;
}
float_twice
计算2*f,允许的操作同上,最大操作数30。
上题我已经说明的浮点数的所有情况,这题只需要根据情况来判定返回结果就好。送分题。
unsigned float_twice(unsigned uf) {
unsigned S = uf&0x80000000;
unsigned E = uf&0x7F800000;
unsigned M = uf&0x007FFFFF;
if(0x7F800000==E)
return uf;
if(E)
return S|E+0x00800000|M;
return S|E|(M<<1);
}