《深入理解計算機系統》CSAPP_DataLab


Data Lab

Link: csapp lab(該鏈接被限制訪問)
也可自行在github中搜索csapplab,以找到實驗原文件。

操作系統:linux

one

bitXor

  • bitXor - x^y using only ~ and &
  • Example: bitXor(4, 5) = 1
  • Legal ops: ~ &
  • Max ops: 14
  • Rating: 1

bitXor:實現位級異或,限制操作: ~ &

int bitXor(int x, int y) {
  return ~((~(x&~y)) & (~(~x&y))); 
  //XOR的與或非實現、OR的與非實現,兩者組合,達成xor的與非實現
}

思路:
XOR的與或非實現: (x & ~ y)|( ~ x & y); OR的與非實現: ~ (~ a & ~ b)



time

  • tmin - return minimum two's complement integer
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 4
  • Rating: 1

tmin:位級實現輸出Tmin

一開始沒認真看INTEGER CODING RULES的要求,后來才發現,僅允許使用0 ~ 255之間的數值,-1 << 31 雖然是對的,但不符合要求

int tmin(void) {
  return 1 << 31; //0x80000000 
}




two

isTmax

  • isTmax - returns 1 if x is the maximum, two's complement number,
  • and 0 otherwise
  • Legal ops: ! ~ & ^ | +
  • Max ops: 10
  • Rating: 1

isTmax:判斷輸入的數值是否為Tmax0x7FFFFFFF,是,輸出1,否則,輸出0

不知道為啥上面題目是tmin,這里就是Tmax,為啥大小寫不統一呢?絕對是出題老師偷懶了。

int isTmax(int x) {
  return !((x^~(x+1))|(!(~x))); //或者!(x^~(x+1)) & !(!(x+1));
}

思路:
由於Tmax == ~(Tmax+1)|左邊利用異或^充當判斷==,相等其值為0, |右邊排除-10xffffffff (因為-1 == ~(-1+1));tmax按位取反再按數值取反后,為0, -1按位取反再按數值取反后,為1


allOddBits

  • 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: ! ~ & ^ | + << >>T
  • Max ops: 12
  • Rating: 2

allOddBits:判斷一個數的奇數位(odd)是否全為1,是,輸出1,否則,輸出0

int allOddBits(int x) {
  int mask = 0xaa | 0xaa << 8;
  mask = mask | mask << 16;
  x = x & mask;
  return !(x^mask);
}

思路:
先構造0xAAAAAAAA,利用 <<、|即可,再用x XOR x的必為0的性質,邏輯取反即可


negate

  • negate - return -x
  • Example: negate(1) = -1.
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 5
  • Rating: 2

negate:取相反數
不知道的時候是真的不知道= =

int negate(int x) {
  return (~x)+1; //按位取反,再加1即可
}




three

isAsciiDigit

  • 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: ! ~ & ^ | + << >>
  • Max ops: 15
  • Rating: 3

isAsciiDigit:判斷一個數是否在0x30 <= x <= 0x39之間,是,輸出1,否則,輸出0

自己的思路把前半部的信息給清除了,不能用。還是大佬強,還具有擴展性,改變上下限數值就是改變范圍了。

int isAsciiDigit(int x) {
    int sign = 1<<31;
    return !(((sign&((~(sign|0x39))+x))>>31) | ((sign&(~0x30+1+x))>>31)); 
}

思路:
上限(左邊),目的是使輸入的數大於0x39時,真值為1;下限(右邊),目的是使輸入的數小於0x30時,真值為1
當兩邊的真值為0時,才居於范圍之間,輸出1
先取一個符號位sign0x80000000

左邊:
0x39按位或sign后再取反,目的是得到一個低8位為0xc6,符號位為0,其它位為1的位級。
該位級+x后,若x大於0x39則其符號位變為1,反之為0
&sign取符號位后,再右移31。若其值大於0x39則為-1,反之為0

右邊:
0x30按位取反+1,目的是得到一個低8位為0xd0,符號位為1,其它位為1的位級。
該位級加x后,若x小於0x30則其符號位仍為1,反之為0
&sign取符號位后,再右移31。若其值小於0x30-1,反之為0


conditional

  • conditional - same as x ? y : z
  • Example: conditional(2,4,5) = 4
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 16
  • Rating: 3

conditional:用位級運算實現三目運算符(x ? y : z)

思路往往可以更簡潔

//法一
int conditional(int x, int y, int z) {
  x = (!!x)<<31>>31;//use: overturn( logic); by <<31>>31
  return (x&y)|(~x&z);
}

思路:
用x構造出全1或全0再使全1、全0分別與xy對應
x邏輯取反兩次得真值<<31>>31,使真值1變為全1-1,真值0不變
此時,若x-1,則&y得到y,且按位取反x,並&z清空;若x0,則&y清空,且按位取反x,並&z得到z
而后按位或輸出

//法二
int conditional(int x, int y, int z) {
  x = !!x;
  x = ~x+1;//use: overturn(bits, logic); by -1+1 = 0;0xfffffffe+1 = 0xffffffff = -1
  return (x&y)|(~x&z);
}

思路:
同法一類似,只是構造全1或者全0的方法不同
x構造出全1或全0,再使全1、全0分別與xy對應。
x邏輯取反兩次得真值,再按位取反后+1,使真值1變為全1-1,真值0不變
此時,若x-1,則&y得到y,且按位取反x,並&z清空;若x0,則&y清空,且按位取反x,並&z得到z
而后按位或輸出

//法三
int conditional(int x, int y, int z) {
  int neg_1 = ~0 
  return ((!x)+neg_1)&y | ((!!x)+neg_1)&z;
}

思路:
x不為0時,輸出yx0時,輸出z。想辦法使兩者分別對應,利用非翻轉(x不為0翻轉1次得全0 or x0翻轉2次得全0)使兩者分別對應
再用-1(neg_1)配出我們要的全1,以便&xy()
運算過程:
左邊:若x不為0,則翻轉一次后-1,為全1,可得y值;若x0,則翻轉再-1,為全0,可清空y值;
右邊:若x不為0,則翻轉兩次后-1,為全0,可清空z值;若x為0,則兩次翻轉再-1,為全1,可得到z值;
兩邊取或,可輸出數字(return竟然可以輸出數值!!哭笑)


isLessOrEqual

  • isLessOrEqual - if x <= y then return 1, else return 0
  • Example: isLessOrEqual(4,5) = 1
  • Legal ops: ! ~ & ^ | + << >>
  • Max ops: 24
  • Rating: 3

isLessOrEqual:判斷是否x <= y,是,輸出1,否則,輸出0

一般自己寫的都有點長

  int isLessOrEqual(int x, int y) {
  return !!(!(x^y) ^ ((x>>31)^(y>>31)&(x>>31)) ^ !((x>>31)^(y>>31))&(x+((~y)+1))>>31); // if not use !!()  will return -1
 }

思路:
判斷x<=y,分成三段解決,任意情況成立即可,則用XOR連接,1.兩者相等;兩者不相等時,2.sign不同;3.sign相同。
1.XOR清零再邏輯非即可
2.先限定於sign不同的情況,利用XOR
&(x>>31),保留x的sign位,若sign=1x為負數,則全1輸出;若sign=0x為正數,則全0輸出
3.先限定於sign相同的情況(即排除sign不同的情況),再利用作差,x-y,小於0時,符合題意,且sign位為1,最后>>31,分割出全0和全1
XOR連接完后,為滿足符合return 1, 否則return 0,要取兩次邏輯非



logicalNeg

  • 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

logicalNeg:實現邏輯非,限制操作:~ & ^ | + << >>

//法一
int logicalNeg(int x) {
  int sign = 1<<31;
  int all1 = ~0;
  return ((((~x)|sign)+1)>>31)+1 & (x^sign)>>31 ; //((x^sign)>>31)
}

思路:
實現邏輯非,即實現0輸出1非0輸出0
首先要分割0非0部分,辦法是0將保持為0非0全部轉換為負數使其符號位為1。,
~ x 翻轉,~ 0 = -1,非0保持不變 ;
(~ x) | sign),使-1不變,非0數全部轉換為負數且最大值為-2,但注意到x=sign經運算后也為-1,用&(x^sign)>>31剔除,同時,使x=0時,第0位為1
((~ x) | sign)+1),使-10,負數最大值此時為-1
((~ x) | sign)+1)>>310不變,負數全為-1
((((~ x) | sign)+1)>>31)+101-1全變為0
((((~ x) | sign)+1)>>31)+1 & (x^sign)>>31x=0時,(左邊1 & 右邊-1)輸出1x非0數時,(左邊0 &右邊任何數)輸出0

法二:
int logicalNeg(int x) {
  return ((x|(~x+1))>>31)+1;
}

思路:
利用補碼(取反+1)的性質0Tmin的補碼為本身其它數值的補碼為其相反數
0與其補碼按位或之后,其值為全0Tmin與其補碼、其它數值與其補碼,按位或之后,符號位為1
然后>>310不變,Tmin、其它數值為全1-1
而后+10變為1Tmin、其它數值為0


howManyBits

  • 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

howManyBits:求一個數最少要用多少位表示

這道題想了很久,因為允許90個ops,就慢慢找規律,最終想出最高位和次最高位相異時,該數的位數可以確定,但實際只能從1位到32位一個個判斷,總共操作接近300ops,只能翻起答案來= =,知道會用重復判斷的方法,沒想到是二分法這么巧妙

int howManyBits(int x) {
  int b16,b8,b4,b2,b1,b0;
  int sign = x>>31;
  x = (sign&~x)|(~sign&x);//x為正數不變;x為負數按位取反,其符號位變為0,可將負數當作正數來求其所需最少的表達位數

// 二分法,不斷縮小范圍
  b16 = !!(x>>16)<<4;//高十六位是否有1
  x = x>>b16;//如果有(至少需要16位),則將原數右移16位
  b8 = !!(x>>8)<<3;//剩余位高8位是否有1
  x = x>>b8;//如果有(至少需要16+8=24位),則右移8位
  b4 = !!(x>>4)<<2;//剩余位高4位是否有1
  x = x>>b4;//如果有(至少需要16+8+4=28位),則右移4位
  b2 = !!(x>>2)<<1;//剩余位高2位是否有1
  x = x>>b2;//如果有(至少需要16+8+4+2=30位),則右移2位
  b1 = !!(x>>1);//剩余位高1位是否有1
  x = x>>b1;// 如果有(至少需要16+8+4+2+1=31位),則右移位
  b0 = x;//b0為x,數值為1或者0
  return b16+b8+b4+b2+b1+b0+1;//+1表示加上符號位
}

思路:
如果是一個正數,則需要找到它最高為1的是第幾位(假設該位是第n位),再加上符號位0(計數為1,那么它最少需要n+1位來表示;
如果是一個負數,則需要找到它最高為0的是第幾位(假設該位是第m位),那么它最少需要m位來表示



float

一開始是真的懵,想來是浮點數位級表示學得比較模糊

floatScale2

  • 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

floatScale2:用unsigned的位級來表示一個浮點數uf,同時,對浮點數uf乘2

  unsigned floatScale2(unsigned uf) {
  int exp = (uf&0x7f800000)>>23;//取指數exp
  int sign = uf&0x80000000;//取符號位sign
  if(exp==0) return uf<<1|sign;//輸出(非規化數)*2或者0*2
  if(exp==255) return uf;//輸出NaN或者INF(無窮,包括正無窮和負無窮)
  exp++; //計算(規化數)*2,
  if(exp==255) return 0x7f800000|sign;//(規化數)*2后,若指數exp全為1即INF,則輸出0x7f800000|sign
  return (exp<<23)|(uf&0x807fffff);//(規化數)*2后,若指數exp不全為1就仍為規划數,則輸出(exp<<23)|(uf&0x807fffff)
}

思路:
區分規化數非規化數NaNINF,注意區分的時候,都要乘2
第5行,不用exp<<1的原因是:雖然能達到*2的目的,但可能會使exp越出255,突破exp限定的8


floatFloat2Int

  • 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

floatFloat2Int:用unsigned位級表示浮點數uf強制轉換為int整型數

int floatFloat2Int(unsigned uf)
{
  int sign = (uf >> 31) & 1;//取符號位sign
  int bias = 127;//偏置值為(2 ^ (8-1)) - 1
  int exp = (uf >> 23) & 0xFF;//取指數exp
  int E = exp - bias; //指數exp的真實值,即階碼E
  int frac = uf & 0x007FFFFF;//取小數字段frac,注意符號位被剔除了
  int M = frac | 0x00800000; //將第23位置1,即階碼的最后1位置1,因為:規化數之中尾數M的范圍是(1 ~ 2-ξ)
  int tar;
  if (sign) sign = -1;
  else sign = 1;//符合位取(-1) ^ 0=1 或者 (-1) ^ 1=-1 ,用以保持正數,或者轉為負數
  if (E >= 31)
    return 0x80000000;
  if (E < 0)
    return 0;
  if (E >= 23)
    tar = sign * (M << (E - 23));//E大於23時,則M左移(E - 23),同時考慮正負數
  else if (E < 23)
    tar = sign * (M >> (23 - E));//E小於23時,則M右移(23 - E),同時考慮正負數
  return tar;
}

思路:
區分float型的規化數非規化數INFNaN
INFNaN:其E >= 31 計算階碼值2^E,超出intTmax == 0x7fffffff,直接輸出0x80000000
非規化數:其E < 0 計算階碼值2^E,為小數,直接輸出0
規化數: 其31> E >=0 計算階碼值2^E,居於1 ~ 2^30之間,需要左右移
左右移原理見 CSAPP P82
其結論是:階碼E大於尾數位數(float型取frac字段位數,共23位)時,則M左移(23 - E)(最多移8位);階碼E小於尾數位數,則M右移(23 - E)(最多移23位)

原解法對於E=23時,直接輸出tar=0;對於E=31時,將其看作仍可以做M左移運算,事實上2 ^ E = 2 ^ 31,已經超過intTmax = (2 ^ 31) - 1
但檢測可以通過 糾正1:雖然超過了Tmax,但沒有超過Tmin = - 2 ^ 31,故,對於E=31時,將其看作仍可以做M左移運算。糾正2:只要E31時,左移時,必然移動8位到符號位且置1,因為:規化數的階碼E最后1位為1。那么,符號位為0frac字段全0,其值為2 ^ 31,左移8位后為0x80000000符號位為0frac字段非0,其值超過2 ^ 31,要置為INF(0x80000000)符號位為1frac字段全0,其值為-2 ^ 31,左移8位后為0x80000000符號位為1frac字段非0,其值為超過-2 ^ 31,要置為INF。綜合下來,E = 31時,可以直接置為INF

筆者做出修正
對於E=23時,輸出tar = sign*M;對於E=31時,直接輸出0x80000000(本題Anything out of range return 0x80000000


floatPower2

  • 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

floatPower2:輸入一個int x,用unsigned位級表示2.0 ^ x

unsigned floatPower2(int x) {
    unsigned INF = 0xff << 23;
    int exp = x + 127;
    if(exp >= 255) return INF;
    if(exp < -23) return 0;
    if(exp <= 0) return 0x00400000>>(~exp+1);
    return exp << 23;
}

思路:
首先需要定義INF,為exp10xff << 23;其次定義小於2 ^(-126-23)0
x的取值即為Eexp = E + bias,可計算出指數exp,而后
考慮臨界值:
bin(x) = 0 00000000 00000000000000000000001,此數是2 ^ (-126-23),(最小值)
exp = 0時情況;exp小於等於0,我們取 0x00400000>>( ~exp+1)
bin(x) = 0 00000001 00000000000000000000000,此數是2 ^ (-126)
exp = 1時情況;exp居於0 ~ 254時,我們取exp << 23
bin(x) = 0 11111111 00000000000000000000000 ,此數是2 ^ (128),(大於最大值)
exp = 255時情況。exp大於等於exp1,我們取INF
以及,exp小於-23時,超出最小值,我們取0

附圖:

感想:

  • 雄關漫道真如鐵,而今邁步從頭越
  • 從9.4號開始做這個lab,一直到9.12號,一共9天。難度真是比較大,終於感受到很名校同學的差距了(特別是那道ASCII)。平時很少做這樣的題。他們是把CSAPP當作ICS(計算機導論)來上的,我得向這些他們靠近。轉專業並降級的我已經落后了不少,是時候好好學了。
  • 現在看難度1、2覺得很容易,但一開始做的時候還是一頭霧水的。位級運算的不但功能十分強大,而且還極大地減少運算時間。之后還得花點心思鞏固。英文閱讀能力非常需要提高,浮點題那一塊,題目都看不太懂。以后做題前先翻翻書,想想知識點,不要直接憑空硬莽。
  • 如有謬誤,敬請指正。
  • Memory Dot,我的個人博客,歡迎來玩。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM