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:判斷輸入的數值是否為Tmax
即0x7FFFFFFF
,是,輸出1
,否則,輸出0
不知道為啥上面題目是tmin
,這里就是Tmax
,為啥大小寫不統一呢?絕對是出題老師偷懶了。
int isTmax(int x) {
return !((x^~(x+1))|(!(~x))); //或者!(x^~(x+1)) & !(!(x+1));
}
思路:
由於Tmax == ~(Tmax+1)
,|
左邊利用異或^
充當判斷==,
相等其值為0
, |
右邊排除-1
即0xffffffff
(因為-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
先取一個符號位sign
即0x80000000
左邊:
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
分別與x
、y
對應。
x
邏輯取反兩次得真值,再<<31>>31
,使真值1
變為全1
即-1
,真值0
不變
此時,若x
為-1
,則&y
得到y
,且按位取反x
,並&z
清空;若x
為0
,則&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
分別與x
、y
對應。
x
邏輯取反兩次得真值,再按位取反后+1
,使真值1
變為全1
即-1
,真值0不變
此時,若x
為-1
,則&y
得到y
,且按位取反x
,並&z
清空;若x
為0
,則&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
時,輸出y
;x
為0
時,輸出z
。想辦法使兩者分別對應,利用非!
翻轉(x
不為0
翻轉1
次得全0
or x
為0
翻轉2
次得全0
)使兩者分別對應
再用-1(neg_1)
配出我們要的全1
,以便&x
或y()
。
運算過程:
左邊:若x
不為0
,則翻轉一次后-1
,為全1
,可得y
值;若x
為0
,則翻轉再-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=1
,x
為負數,則全1
輸出;若sign=0
,x
為正數,則全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)
,使-1
變0
,負數最大值此時為-1
;
((~ x) | sign)+1)>>31
,0
不變,負數全為-1
;
((((~ x) | sign)+1)>>31)+1
,0
變1
,-1
全變為0
;
((((~ x) | sign)+1)>>31)+1 & (x^sign)>>31
,x=0
時,(左邊1
&
右邊-1
)輸出1
;x
為非0
數時,(左邊0
&
右邊任何數)輸出0
;
法二:
int logicalNeg(int x) {
return ((x|(~x+1))>>31)+1;
}
思路:
利用補碼(取反+1
)的性質,0
和Tmin
的補碼為本身,其它數值的補碼為其相反數;
0
與其補碼按位或之后,其值為全0
,Tmin
與其補碼、其它數值與其補碼,按位或之后,符號位為1
;
然后>>31
,0
不變,Tmin
、其它數值為全1
即-1
;
而后+1
,0
變為1
,Tmin
、其它數值為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
位一個個判斷,總共操作接近300
ops,只能翻起答案來= =,知道會用重復判斷的方法,沒想到是二分法這么巧妙
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)
}
思路:
區分規化數
、非規化數
、NaN
、INF
,注意區分的時候,都要乘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
型的規化數
、非規化數
、INF
、NaN
,
INF
、NaN
:其E >= 31
計算階碼值2^E
,超出int
型Tmax == 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
,已經超過int
型Tmax = (2 ^ 31) - 1
但檢測可以通過 糾正1:雖然超過了Tmax
,但沒有超過Tmin = - 2 ^ 31
,故,對於E=31
時,將其看作仍可以做M
左移運算。糾正2:只要E
取31
時,左移時,必然移動8
位到符號位且置1
,因為:規化數的階碼E
最后1
位為1
。那么,符號位為0
,frac
字段全0
時,其值為2 ^ 31
,左移8
位后為0x80000000
;符號位為0
,frac
字段非0
時,其值超過2 ^ 31,要置為INF(0x80000000)
;符號位為1
,frac
字段全0
時,其值為-2 ^ 31
,左移8
位后為0x80000000
;符號位為1
,frac
字段非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
,為exp
全1
即0xff << 23
;其次定義小於2 ^(-126-23)
為0
x
的取值即為E
,exp = 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
大於等於exp
全1
,我們取INF
以及,exp
小於-23
時,超出最小值,我們取0
。
附圖:
感想:
- 雄關漫道真如鐵,而今邁步從頭越
- 從9.4號開始做這個lab,一直到9.12號,一共9天。難度真是比較大,終於感受到很名校同學的差距了(特別是那道ASCII)。平時很少做這樣的題。他們是把CSAPP當作ICS(計算機導論)來上的,我得向這些他們靠近。轉專業並降級的我已經落后了不少,是時候好好學了。
- 現在看難度1、2覺得很容易,但一開始做的時候還是一頭霧水的。位級運算的不但功能十分強大,而且還極大地減少運算時間。之后還得花點心思鞏固。英文閱讀能力非常需要提高,浮點題那一塊,題目都看不太懂。以后做題前先翻翻書,想想知識點,不要直接憑空硬莽。
- 如有謬誤,敬請指正。
- Memory Dot,我的個人博客,歡迎來玩。