CS:APP配套实验 Data Lab
刚刚完成注册博客,想写一篇随笔,方便以后自己回顾。如果恰好也能帮助到你,是我的荣幸。
这次随笔是记载我的计算机系统(CS:APP,Computer Systems:A Programer's Perspective)课程的一次实验
为了实现这15个函数,参考了(抄袭了- -)网上很多大佬的解答,但是过程中也有了自己的一些体会。
下面分享一下自己的理解,每个函数的实现都附有相应解释,有个别自己还不是很理解就没有写解释,见谅啊。
注:实验环境 ubuntu 12.04
每次修改bits.c时,都要make btest才能用btest测试函数的正确性
/* * CS:APP Data Lab * * * bits.c - Source file with your solutions to the Lab. * This is the file you will hand in to your instructor. * * WARNING: Do not include the <stdio.h> header; it confuses the dlc * compiler. You can still use printf for debugging without including * <stdio.h>, although you might get a compiler warning. In general, * it's not good practice to ignore compiler warnings, but in this * case it's OK. */ #if 0 /* * Instructions to Students: * * STEP 1: Read the following instructions carefully. */ You will provide your solution to the Data Lab by editing the collection of functions in this source file. INTEGER CODING RULES: Replace the "return" statement in each function with one or more lines of C code that implements the function. Your code must conform to the following style: int Funct(arg1, arg2, ...) { /* brief description of how your implementation works */ int var1 = Expr1; ... int varM = ExprM; varJ = ExprJ; ... varN = ExprN; return ExprR; } Each "Expr" is an expression using ONLY the following: 1. Integer constants 0 through 255 (0xFF), inclusive. You are not allowed to use big constants such as 0xffffffff. 2. Function arguments and local variables (no global variables). 3. Unary integer operations ! ~ 4. Binary integer operations & ^ | + << >> Some of the problems restrict the set of allowed operators even further. Each "Expr" may consist of multiple operators. You are not restricted to one operator per line. You are expressly forbidden to: 1. Use any control constructs such as if, do, while, for, switch, etc. 2. Define or use any macros. 3. Define any additional functions in this file. 4. Call any functions. 5. Use any other operations, such as &&, ||, -, or ?: 6. Use any form of casting. 7. Use any data type other than int. This implies that you cannot use arrays, structs, or unions. You may assume that your machine: 1. Uses 2s complement, 32-bit representations of integers. 2. Performs right shifts arithmetically. 3. Has unpredictable behavior when shifting an integer by more than the word size. EXAMPLES OF ACCEPTABLE CODING STYLE: /* * pow2plus1 - returns 2^x + 1, where 0 <= x <= 31 */ int pow2plus1(int x) { /* exploit ability of shifts to compute powers of 2 */ return (1 << x) + 1; } /* * pow2plus4 - returns 2^x + 4, where 0 <= x <= 31 */ int pow2plus4(int x) { /* exploit ability of shifts to compute powers of 2 */ int result = (1 << x); result += 4; return result; } FLOATING POINT CODING RULES For the problems that require you to implent floating-point operations, the coding rules are less strict. You are allowed to use looping and conditional control. You are allowed to use both ints and unsigneds. You can use arbitrary integer and unsigned constants. You are expressly forbidden to: 1. Define or use any macros. 2. Define any additional functions in this file. 3. Call any functions. 4. Use any form of casting. 5. Use any data type other than int or unsigned. This means that you cannot use arrays, structs, or unions. 6. Use any floating point data types, operations, or constants. NOTES: 1. Use the dlc (data lab checker) compiler (described in the handout) to check the legality of your solutions. 2. Each function has a maximum number of operators (! ~ & ^ | + << >>) that you are allowed to use for your implementation of the function. The max operator count is checked by dlc. Note that '=' is not counted; you may use as many of these as you want without penalty. 3. Use the btest test harness to check your functions for correctness. 4. Use the BDD checker to formally verify your functions 5. The maximum number of ops for each function is given in the header comment for each function. If there are any inconsistencies between the maximum ops in the writeup and in this file, consider this file the authoritative source. /* * STEP 2: Modify the following functions according the coding rules. * * IMPORTANT. TO AVOID GRADING SURPRISES: * 1. Use the dlc compiler to check that your solutions conform * to the coding rules. * 2. Use the BDD checker to formally verify that your solutions produce * the correct answers. */ #endif /* * bitAnd - x&y using only ~ and | * Example: bitAnd(6, 5) = 4 * Legal ops: ~ | * Max ops: 8 * Rating: 1 */ int bitAnd(int x, int y) { return ~((~x) | (~y)); } //利用德摩根律 /* * getByte - Extract byte n from word x * Bytes numbered from 0 (LSB) to 3 (MSB) * Examples: getByte(0x12345678,1) = 0x56 * Legal ops: ! ~ & ^ | + << >> * Max ops: 6 * Rating: 2 */ int getByte(int x, int n) { return ((x >> (n << 3)) & 0xFF); } //将所要取的字节移到最右端然后其余位通过与运算置0 /* * logicalShift - shift x to the right by n, using a logical shift * Can assume that 0 <= n <= 31 * Examples: logicalShift(0x87654321,4) = 0x08765432 * Legal ops: ! ~ & ^ | + << >> * Max ops: 20 * Rating: 3 */ int logicalShift(int x, int n) { int tmp = 32 + (~n); return (x >> n) & ((1 << tmp) + (~0) + (1 << tmp)); } //运用机器的算术右移,然后高位置0和低位保持 //右移n位后原最高位到了第31-n位,31-n表示为31+((~n)+1) = 32+(~n) //通过与高n位全为0,第32-n位全为1的数实现高位置0和低位保持 //这个数是(1 << ((32+(~n)+1)) - 1 //由于n可能为0,这样左移32位会根据gcc编译规则左移 32 % 32(类型位长) = 0位 //故将该数表示为(1 << (32+(~n)+1)) + (~0) + (1 << (32+(~n)+1)) /* * bitCount - returns count of number of 1's in word * Examples: bitCount(5) = 2, bitCount(7) = 3 * Legal ops: ! ~ & ^ | + << >> * Max ops: 40 * Rating: 4 */ int bitCount(int x) { //造数 int _tmp1 = 0x55 | (0x55 << 8); //0x00005555 int _tmp2 = 0x33 | (0x33 << 8); //0x00003333 int _tmp3 = 0xf | (0xf <<8); //0x00000f0f int tmp1 = _tmp1 | (_tmp1 << 16); //0x55555555 int tmp2 = _tmp2 | (_tmp2 << 16); //0x33333333 int tmp3 = _tmp3 | (_tmp3 << 16); //0x0f0f0f0f int tmp4 = 0xff | (0xff << 16); //0x00ff00ff int tmp5 = 0xff | (0xff << 8); //0x0000ffff //求和 int res = 0; res = (x & tmp1) + ((x >> 1) & tmp1); res = (res & tmp2) + ((res >> 2) & tmp2); res = (res & tmp3) + ((res >> 4) & tmp3); res = (res & tmp4) + ((res >> 8) & tmp4); res = (res & tmp5) + ((res >> 16) & tmp5); //返回 return res; } //类似递归分治的思想,以统计二进制数x=10中1的个数为例 //方法是将高位移到低位和0x1相与,(x & 0x1) + ((x >> 1) & 0x1) //造出5个数,0x55555555,0x33333333,0x0f0f0f0f,0x00ff00ff,0x0000ffff //这五个数写成二进制,分别隔着1,2,4,8,16个0 /* * bang - Compute !x without using ! * Examples: bang(3) = 0, bang(0) = 1 * Legal ops: ~ & ^ | + << >> * Max ops: 12 * Rating: 4 */ int bang(int x) { return ((~((x | ((~x)+1)) >> 31)) & 0x1); } //将一个非零数的补码与其相反数的补码相或,最高位一定是1 //但如果对0进行此操作,最高位还是0 /* * tmin - return minimum two's complement integer * Legal ops: ! ~ & ^ | + << >> * Max ops: 4 * Rating: 1 */ int tmin(void) { return (0x1 << 31); } //最小负整数的二进制补码是1000...0000 /* * fitsBits - return 1 if x can be represented as an * n-bit, two's complement integer. * 1 <= n <= 32 * Examples: fitsBits(5,3) = 0, fitsBits(-4,3) = 1 * Legal ops: ! ~ & ^ | + << >> * Max ops: 15 * Rating: 2 */ int fitsBits(int x, int n) { return !(((x >> (n+(~0))) + 1) >> 1); } //若n位能表示这个数 //正数的1只能出现在低n-1位,其余位全为0 //负数的0只能出现在低n-1位,其余位全为1 //故将该数右移n-1位后所得结果,正数全为0,负数全为1 //此时再+1后右移1位,可以得到全0 //若n位不能表示这个数,则一定不会有以上结论 //n-1表示为n+(-1),即n+(~0) /* * divpwr2 - Compute x/(2^n), for 0 <= n <= 30 * Round toward zero * Examples: divpwr2(15,1) = 7, divpwr2(-33,4) = -2 * Legal ops: ! ~ & ^ | + << >> * Max ops: 15 * Rating: 2 */ int divpwr2(int x, int n) { return ( x + (((x >> 31) & 0x1) << n) + (~0) + (!((x >> 31) & 0x1)) ) >> n; } //本题是对于标准除法/的实现,全正取下整,有负取上整 //而通过移位实现除以2的幂,都是取下整 //故对于负数要加一个偏移量(1 << n) - 1 (证明在深入理解计算机系统第9版P73) //因为是负数才要加偏移量,所以式子中的1刚好可以用符号位((x >> 31) & 0x1)表示 //若x > 0,(((x >> 31) & 0x1) << n)的结果为0,会导致多减去了1即(~0) //巧妙多加一个符号位的逻辑非结果即(!((x >> 31) & 0x1)),负数为0,整数为1,恰好弥补 /* * negate - return -x * Example: negate(1) = -1. * Legal ops: ! ~ & ^ | + << >> * Max ops: 5 * Rating: 2 */ int negate(int x) { return ((~x) + 1); } //对一个数的补码进行按位取反加1就能得到其相反数的补码 /* * isPositive - return 1 if x > 0, return 0 otherwise * Example: isPositive(-1) = 0. * Legal ops: ! ~ & ^ | + << >> * Max ops: 8 * Rating: 3 */ int isPositive(int x) { return !( ((x >> 31) & 0x1) | (!(x << 1)) ); } //正数和零的符号位为0,负数为1 //左移1位后用!,正数的结果一定为0,负数的结果可0可1,零的结果一定是1 //将以上两个结果相或,正数的结果为0,负数一定为1,零的结果一定是1 /* * isLessOrEqual - if x <= y then return 1, else return 0 * Example: isLessOrEqual(4,5) = 1. * Legal ops: ! ~ & ^ | + << >> * Max ops: 24 * Rating: 3 */ int isLessOrEqual(int x, int y) { int xSign = (x >> 31) & 0x1; int ySign = (y >> 31) & 0x1; int signDiff = xSign & (!ySign); int signSame = !(((y + (~x+1)) >> 31) & 0x1) & !(xSign ^ ySign); return signDiff | signSame; } //作差法判断大小 //首先要判断符号位,因为可能存在溢出 //正数符号位为0,负数符号位为1,列出x,y的真值表 //x y z //0 0 符号相同要进行下一步作差 //0 1 0(表示x > y) //1 0 1(表示x < y) //1 1 符号相同要进行下一步作差 //可以看到若用一个表达式(xSign & !(ySign))可以实现只用第三种情况为1,就可以完成符号位的判断 //接下来还要看其他三种情况,通过与上!(xSign ^ ySign)保证两者同号,因为第二种不同号的情况在该表达式下结果为0,与0就置0 //前面取y-x的符号位,为0说明,y >= x,否则y < x,再取逻辑非配合与操作 /* * ilog2 - return floor(log base 2 of x), where x > 0 * Example: ilog2(16) = 4 * Legal ops: ! ~ & ^ | + << >> * Max ops: 90 * Rating: 4 */ int ilog2(int x) { int res = 0; res = res + ((!!(x>>(16 + res)))<<4); res = res + ((!!(x>>(8 + res)))<<3); res = res + ((!!(x>>(4 + res)))<<2); res = res + ((!!(x>>(2 + res)))<<1); res = res + ((!!(x>>(1 + res)))<<0); return res; } /* * float_neg - Return bit-level equivalent of expression -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 representations of * single-precision floating point values. * When argument is NaN, return argument. * Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while * Max ops: 10 * Rating: 2 */ unsigned float_neg(unsigned uf) { unsigned exp = (uf >> 23) & 0xFF; unsigned frac = uf & 0x7FFFFF; unsigned res = uf ^ 0x80000000; if(exp == 0xFF && frac) { res = uf; } return res; } //取exp和frac判断是否为NaN //异或改变符号位 /* * float_i2f - Return bit-level equivalent of expression (float) x * Result is returned as unsigned int, but * it is to be interpreted as the bit-level representation of a * single-precision floating point values. * Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while * Max ops: 30 * Rating: 4 */ unsigned float_i2f(int x) { unsigned ans; int xSignx = x & (1 << 31); int res = 31; int ss = 0; int ff = 0; int tmp; if(x == 0) ans = 0; else{ if(xSignx) x = (~x) + 1; while(!((1 << res) & x)) { res--; } x = x ^ (1 << res); if(res < 23) x = x << (23 - res); else { tmp = res - 24; if(tmp >= 0) ss = (x >> tmp) & 1,ff = ((1 << tmp) - 1) & x; x = (x >> (res-23)); } x = x | ((res+127) << 23); if(ff == 0) { ss = (ss & x); } x = x + ss; x = x | xSignx; ans = x; } return ans; } /* * float_twice - 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 */ unsigned float_twice(unsigned uf) { unsigned tmp = uf; unsigned exp = (tmp >> 23) & 0xFF; unsigned frac = tmp & 0x007fffff; if(exp == 0x0) { //非规格化数 tmp = (tmp & 0x80000000) | (frac << 1); } else if(exp != 0xFF) { //规格化数 tmp += (1 << 23); if(((tmp >> 23) & 0xFF) == 0xFF) { tmp = tmp >> 23 << 23; } } return tmp; }
如果读者看完后有自己独到的实现方式,欢迎一起交流学习。