實驗及其環境
Data Lab [Updated 12/16/19]
環境: Ubantu
gcc版本: 4.8.5
常用命令
make:編譯所有文件
./dlc bits.c: 測試使用的操作符是否合法
./btest -h //測試命令的功能列表,按照說明使用即可
如./btest -f bitXor,測試bitXor是否能通過所有的測試用例
在data-handout的readme文件中對上述命令有具體說明,在此不多贅述
題解
第一關
1.bitXor
/*
* bitXor - x^y using only ~ and &
* Example: bitXor(4, 5) = 1
* Legal ops: ~ &
* Max ops: 14
* Rating: 1
*/
大意:使用~和&來完成^運算符。
大致思路是先完成( ~x & y) | ( x & ~y)
運算,最後利用摩根定律,使用~和&運算符來替代 | 操作符。
int bitXor(int x, int y) {
return ~(~(~x & y) & ~( x & ~y));
}
2.tmin
/*
* tmin - return minimum two's complement integer
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 4
* Rating: 1
*/
大意:求Tmin
送分題,直接移位
int tmin(void) {
return 1 << 31;
}
3.isTmax
/*
* isTmax - returns 1 if x is the maximum, two's complement number,
* and 0 otherwise
* Legal ops: ! ~ & ^ | +
* Max ops: 10
* Rating: 1
*/
大意:判斷給出的x是否是Tmax。
如果x == Tmax,則有!~((x + 1) ^ x ) = 1
,當然 x == -1時,該等式也成立。因此特判一下 x != -1,此時!!(x + 1)
應該爲0x1,等式成立只有這兩種情況了。
int isTmax(int x) {
return !~((x + 1) ^ x ) &!!(x + 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: ! ~ & ^ | + << >>
* Max ops: 12
* Rating: 2
*/
大意:如果x中所有的奇數位都爲1則返回1,反之返回0.(x位數範圍爲0-31),偶數位可以不用管。
取其中的一個字節10101010,十進制爲170,將其生成爲一個所有奇數位都爲1的數字mask(10101010 10101010 10101010 10101010),拿這個數字和x來比較,如果x == mask,則必有(x&mask) ^ mask == 0
(a^a == 0, x&mask是爲了去掉偶數位)
int allOddBits(int x) {
int mask = 170 + (170 << 8) + (170 << 16) + (170 << 24);
return !((x & mask) ^ mask);
}
2.negate
/*
* negate - return -x
* Example: negate(1) = -1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 5
* Rating: 2
*/
大意:求相反數
書本中給有這個公式
int negate(int x) {
return ~x + 1;
}
第三關
1.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
*/
大意:判斷給定的x是否是ascii碼,即0x30 <= x <= 0x39
返回1,繁殖返回0
先算出0x30等於十進制的48,而0x39位十進制的57,所以轉化一下,即求 x-48>=0 && x-58<0
(-運算符使用第二關第三題可以計算),再通過取反並移31位。如果x是在此區間範圍,!((x + ~48 + 1) >> 31) = 0x1
, ((x + ~58 + 1) >> 31 = 0x1
,通過&連接即可
int isAsciiDigit(int x) {
return (!((x + ~48 + 1) >> 31)) & ((x + ~58 + 1) >> 31);
}
2.conditional
/*
* conditional - same as x ? y : z
* Example: conditional(2,4,5) = 4
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 16
* Rating: 3
*/
大意:實現三目運算符 x ? y : z
這題可以這樣想,既然x!=0返回y,則有一部分應該是0xffffffff & y,此時應該無視z,那麼另外一個部分就可以是0x0 & z,然後通過|操作符連接。即返回(0xffffffff & y) | (0x0&z)
那麼問題來了,x!=0時,則需將x轉化爲0xfffffff,反之則轉化爲0x0;我是用移位操作來完成的,類似二分的遞增。
int conditional(int x, int y, int z) {
int bits = !!x;
bits += bits << 1;
bits += bits << 2;
bits += bits << 4;
bits += bits << 8;
bits += bits << 16;
return (bits & y) | (~bits & z);
}
看到網上有將x轉化爲0xfffffff更神仙的方法:
int conditional(int x, int y, int z) {
return ((!x+~1+1)&y)|((~!x+1)&z);
}
3.isLessOrEqual
/*
* isLessOrEqual - if x <= y then return 1, else return 0
* Example: isLessOrEqual(4,5) = 1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 24
* Rating: 3
*/
大意: x<= y時返回1,反正返回0
先得到x, y和 y - x 的符號位,再分三種情況討論即可
①x < 0, y < 0, y - x >= 0
②x > 0, y > 0, y - x >= 0
③y >=0, x < 0
int isLessOrEqual(int x, int y) {
int negX = x >> 31;
int negY = y >> 31;
int gte = !((y + ~x + 1) >> 31);
return (negX & negY & gte) | (!negX & !negY & gte) | (negX & !negY);
}
第四關
1.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
*/
大意:實現!的邏輯
這題大概是第四關裏面最簡單了的
注意一個特點, 只有+0 == -0,其他的數都不等,那麼可以利用這個特點,來對x進行一個比較和判斷,當 x == 0時,(~-0) & (~+0) 的符號位一定爲1, 若是其他的數,符號位則不爲1. 最後右移31位,&1去除其他的位可以得到結果
int logicalNeg(int x) {
return (((~(~x + 1)) & (~x)) >> 31) & 1;
}
2.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
*/
大意:返回最少用來表示x的位數.
可以說是最難的一題了
比如說howManyBits(12) 返回 5,12的需要最少的二進制是 01100,高位的0可以去掉,此時需要的位數是5位。但是如果用1100就不行,因爲這時表示的是-4。表示-5的需要最少的二進制爲 1001,返回4
可以先做一個預處理,把負的都取反,比如上面的-5,取反後爲(1…1)0110,這時就可以把-5當成正數來處理了。
接下來這一部分比較頭疼,我也是想了很久纔想明白,主要是使用了一個二分的思想
核心思想是,查看x的後16bit是否都是1,如果是這樣的話,那麼x至少要16位才能表示;如果後16位都是1,那麼將x右移16位,反之則右移0位,使用(!!(x >> 16)) << 4
可以使得s16變爲0或16;接下來,再依次對x的後8位,4位,2位,1位進行處理。
最後特殊情況需要注意,比如當x輸入是0時,這是s1 - s16都是0,此時s16 + s8 + s4 + s2 + s1 = 0, 但實際上都需要返回1,嘗試在之前的結果加上x+1,此時返回的剛好等於1;再看另一種情況, x是0x01需要返回2,此時s1 - s16均爲0, 最後的x = 1,x+1 =2,也成立。所以最後應該返回s16 + s8 + s4 + s2 + s1 + x + 1
而不是s16 + s8 + s4 + s2 + s1
int howManyBits(int x) {
int s16, s8, s4, s2, s1;
int sign = (x >> 31) & 1;
int mask = ~sign + 1;
x = (mask & ~x) | (~mask & x);
s16 = (!!(x >> 16)) << 4;
x >>= s16;
s8 = (!!(x >> 8)) << 3;
x >>= s8;
s4 = (!!(x >> 4)) << 2;
x >>= s4;
s2 = (!!(x >> 2)) << 1;
x >>= s2;
s1 = (!!(x >> 1)) << 0;
x >>= s1;
return s16 + s8 + s4 + s2 + s1 + x + 1;
}
3.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
*/
大意:給定一個位表示的浮點數f,返回2*f的位表示
這裏面牽扯到的是浮點數的知識了,翻書可以知道float是32位表示,其中符號位sign佔1位,階碼位exp佔8位,尾數frac佔23位。所以可使用掩碼操作,先分別得到sign,exp,frac的位表示。
接下來就很明朗了,直接將這個數分爲規格化數(normalize)和非規格化數(denormolize)討論即可,對sign,exp,frac進行計算,最後返回sign | exp | frac
。
①exp = 0的時候,十進制的值表示爲2,這時候uf*2,也就是2,直接frac左移一位就可以了(非規格化的值時,M=f)。
②exp !=0 且 uf不爲無窮時,十進制2乘2就變成了2,又,所以只需要將exp加1就可以完成計算了,此外,還要注意一下溢出的情況特判。
unsigned floatScale2(unsigned uf) {
unsigned sign = uf & 0x80000000;
unsigned exp = uf & 0x7f800000;
unsigned frac = uf & 0x007fffff;
if( exp == 0x0){
frac <<= 1;
}else if(exp!= 0x0 && exp != 0x7f800000){
exp += 0x00800000;
// return ∞
if(exp == 0x7f800000) return sign | exp;
}
return sign | exp | frac;
}
4.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
*/
大意:將float轉化爲int,其中float給出的是位表示。
還是先分別取其中的sign,exp和frac三個位。先計算出,然後返回sign == 0 ? abs : -abs
;
可以先在草稿紙上算一算,uf<1時肯定要返回0,所以計算uf==1時的臨界值,即,此時E是0,根據 可得exp爲127,所以分下面幾種情況討論
①當exp<127時,向下取整進而返回0;
②這時再來考慮一下溢出的情況,int最大值爲所以這個時候 剛好溢出,所以當exp > 157的時候返回一個0x80000000u
③最後來考慮可以正常計算的情況,先求得abs的值,然後再來考慮frac位的情況。由於,所以只需要在原來的基礎上加上,這就很好辦了,使用一個循環,依次計算frac的每一位即可。
int floatFloat2Int(unsigned uf) {
int sign = uf >> 31;
int exp = (uf >> 23) & 0xff;
int frac = uf & 0x007fffff;
int E;
int cnt;
int abs;
if (exp < 127) {
return 0;
}
else if (exp > 157) {
return 0x80000000u;
}
E = exp - 127;
//2^E * M
cnt = -23;
abs = 1 << E;
while (frac > 0) {
abs += (cnt + E >= 0) ? (1 << (cnt + E)) : 0;
cnt++;
frac >>= 1;
}
return !sign ? abs : ~abs + 1;
}
5.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
*/
大意:返回2^x的位級表示
也是一系列的特判,計算float可以表示的範圍。
①最小的邊界值爲,所以x<-149時直接返回0
②最大的值爲,所以x>127時返回一個正無窮就行
③這時再來計算最小的規格化值,也就是exp等於1,frac=0時x的邊界值。這時十進制是,所以當x<-126時,exp=0,frac=-x
④最後的情況就是exp!=0了,這時位數frac=0,由於,所以exp = x+127,通過掩碼操作和移位,得到exp部分的位表示。
unsigned floatPower2(int x) {
unsigned sign = 0x0;
unsigned exp;
unsigned frac;
if(x < -149) return 0;
else if(x > 127) return 0x7f800000u;
else if( x < -126 ){
exp = 0x0;
frac = ~x + 1;
}else{
frac = 0x0;
exp = ((x + 127) & 0xff) << 23;
}
return sign | exp | frac;
}
運行結果
舒服了
小結
這個實驗寫了兩天,文章寫了一天(猝。有部分題還是有難度的。推薦vscode上的一個插件Romote-SSH,通過這個插件連接遠程服務器,直接在vscode上寫的。
剩下的部分和書接下來再看,有一說一csapp這本書真有意思,接下來再繼續完成bomb實驗。