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);
}