CS:APP datalab 記錄

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章