0x01 实验前的环境部署和操作 安装gcc-multilib插件 由于实验自带的检验和打分等程序是32位程序,而我的虚拟机是Ubuntu64位的,因此安装这个插件来兼容64位系统。
1 sudo apt-get install gcc-multilib
实验步骤简述 我这里用vscode远程连接本地Ubuntu虚拟机,打开datalab-handout
文件夹的bits.c
文件,根据里面的要求补全函数。
(参考网站:vscode远程连接本地虚拟机 ;出现无法连接的问题时 )
实验结果检验 在linux终端中输入以下命令
0x02 补全bits.c里的函数思路 01 bitXor 根据异或公式,(~x & y) | (x & ~y),由于这里不允许使用或|,于是使用摩根定律替换
1 2 3 4 5 6 7 8 9 10 11 int bitXor (int x, int y) { return ~(~(~x & y) & ~(x & ~y)); }
02 tmin 返回补码数的最小值,就是1开头后面全是0的数
1 2 3 4 5 6 7 8 9 int tmin (void ) { return (1 <<31 ); }
03 isTmax 检测是不是int补码数最大值,即0111后面全是1,我发现,只有-1和INT_MAX加1后再与本身异或运算,才有可能是位级表示下全1的数
1 0111 ^ 1000 = 1111 Or 1111 ^ 0000 = 1111
全1的数很好判断,只有取反再测是不是0。这时候只要排除-1的情况就好了,只要x加1后非0,这个数就不是-1。
1 2 3 4 5 6 7 8 9 10 int isTmax (int x) { return !!(x+1 ) & !(~((x+1 ) ^ x)); }
04 allOddBits 如果某个数所有的奇数位是1就返回1,比如0xAA:1010 1010
用0xAA AA AA AA取输入数x的掩码,然后再判断是否与0xAAAAAAAA相等
1 2 3 4 5 6 7 8 9 10 11 12 13 int allOddBits (int x) { int temp = 0xAA ; temp |= temp << 8 ; temp |= temp << 16 ; return !((x & temp) ^ temp); }
05 negate 返回一个负数
按照补码运算性质马上就能得出答案,按位取反再加1(最大补码负数的取负还是本身)
1 2 3 4 5 6 7 8 9 10 int negate (int x) { return (~x+1 ); }
06 isAsciiDigit 如果是ASCII码下的数字符号就返回1,在0x30到0x39之间
先排除大于0xff的情况,与一个高24位全是1的掩码,之后除了0x3x,再判断低四位,有0xxx,和100x两种情况,分别讨论即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 int isAsciiDigit (int x) { int test_number = (1 <<31 )>>23 ; return !(x & test_number) & (!((x & 0xf8 ) ^ 0x30 ) | !((x & 0xfe ) ^ 0x38 )); }
07 conditional x ? y : z
三元运算符,x为真取y,假取z
若x为真,根据x的值来生成一个全1的掩码,跟y与得到y本身;全1的非,即全0,跟z与得到0,最后两者再或运算
得到y,若x为假,同上理得到z。
1 2 3 4 5 6 7 8 9 10 11 int conditional (int x, int y, int z) { int Bool = ((!!x) << 31 ) >> 31 ; return (Bool & y) | (~Bool & z); }
08 isLessOrEqual 实现小于或等于号
判断y - x 是否大于或等于0,减法用加逆元来实现(实测发现虽然INT_MIN的逆元是本身,但是能正常运算)。
1 2 3 4 5 6 7 8 9 10 11 int isLessOrEqual (int x, int y) { int test_number = 1 << 31 ; return !(((y + (~x + 1 )) >> 31 ) & test_number) ; }
09 logicalNeg 不用 !
来实现 !
0得到1,非0得到0
非0的数的逆元(否定)会翻转符号位,而0不会,于是非0数的否定与本身或运算得到符号位为1的数。
然后再算术右移,若非0则得到-1,为0则得到0,再加1就实现成功。
1 2 3 4 5 6 7 8 9 10 11 int logicalNeg (int x) { return (((~x + 1 ) | x) >> 31 ) + 1 ; }
0A howManyBits 判断某个数,最少可以用多少位级表示,由于x和~x的位级所需位是一样的,所有负数都通过翻转成正数来处理(正数不变)。以下操作可以实现
之后开始查找第一个1的位置,表示第一个1的位置所需的位级再加上1(符号位),就能返回正确答案。
查找位置的方法用二分查找,每次取中间位置,看看高位是否存在1,若存在再右移x来查找高位,否则不位移来查找低位,记录被右移掉的位数,最后要加到结果中。经过5轮的二分,就能查找到第一个1的确切位置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 int howManyBits (int x) { x = x >> 31 ^ x; int b16, b8, b4, b2, b1; b16 = !!(x >> 16 ) << 4 ; x = x >> (b16); b8 = !!(x >> 8 ) << 3 ; x = x >> b8; b4 = !!(x >> 4 ) << 2 ; x = x >> b4; b2 = !!(x >> 2 ) << 1 ; x = x >> b2; b1 = !!(x >> 1 ); x = x >> b1; return b16 + b8 + b4 + b2 + b1 + x + 1 ; }
0B float_twice 把一个浮点数乘以2,返回对应的值。
NaN返回本身:exp = 0xff,frac != 0
非规格化:exp = 0, frac <<= 1
,与规格化的过渡是平滑的,直接frac左移一位即可
规格化乘以2后溢出:{exp = 0xff,frac = 0} Or {exp = 0xff-1}
,return ∞(返回无穷)
规格化未溢出:exp += 1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 unsigned float_twice (unsigned uf) { unsigned sign = uf & (1 << 31 ); unsigned exp = (uf ^ sign) >> 23 ; unsigned frac = uf & 0x7fffff ; if (!exp ){ return (uf << 1 ) | sign; } else if (!(exp ^ 0xff ) && frac){ return uf; } else if ((!(exp ^ 0xff ) && !frac) || !(exp ^ 0xfe )){ exp = 0xff ; return sign | (exp << 23 ); } else { return (uf ^ (exp << 23 )) | (exp + 1 ) << 23 ; } }
0C float_i2f (偶数舍入方法:先加上舍弃位的半值,利用四舍五入,小于半值的加上半值也不会进位,移位后自然舍弃,大于半值的加上半值会产生进位,完全符合我们的需求。对于刚好半值,加上半值会进位,但是它比较特殊,加完后舍弃的值是全0,如果最低有效位是1,说明原来是0,不该进位,所以再减1去掉进位。)
(这个有点难,我最后参考了网上的做法)
先把int正数取正(无符号),再算出uf的位数,把其转化为frac表示,然后记录exp,最后合并。
然后后九位要根据向偶数舍入来舍入(如果int部分超过24)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 unsigned float_i2f (int x) { unsigned uf, exp ; unsigned sign = x & (1 << 31 ); unsigned i = 30 , t; if (x==0 ){ uf = 0 ; } else if (x == 0x80000000 ){ uf = 0xcf000000 ; } else { if (sign){ uf = ~x + 1 ; } else { uf = x; } while (!(uf & (1 << i))){ i--; } uf = uf - (1 << i); if (i < 24 ){ uf = uf << (23 - i); } else { t = i - 23 ; uf += (1 << (t - 1 )); if (uf & ((1 << (t + 1 )) - 1 )){ uf -= 1 ; } uf = uf >> t; } exp = (127 + i) << 23 ; uf += exp | sign; } return uf; }
0D float_f2i 应该是默认向0舍入。
当float数小于1时,直接返回0
当float数大于1,但不溢出int所能表示的范围时(exp 在127到157之间)
先补全小数点前的1,再根据E = exp -1 调整阶数。
当float超过int所能表示的范围,或者是NaN时,按照题意返回 0x8000 0000
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 int float_f2i (unsigned uf) { unsigned sign = uf & (1 << 31 ); unsigned exp = (uf ^ sign) >> 23 ; unsigned frac = uf & 0x7fffff ; unsigned E; if (exp == 0 || (exp >= 1 && exp <= 126 )){ return 0 ; } else if (exp >= 127 && exp <= 157 ){ E = exp - 127 ; frac |= 0x800000 ; if (E <= 23 ){ frac >>= (23 - E); } if (E > 23 ){ frac <<= (E - 23 ); } } else if (exp >= 158 && exp <= 255 ){ return 0x80000000 ; } if (sign){ return ~frac + 1 ; } return frac; }