CS:APP Data Lab

《深入理解计算机系统》这本书每章后面都有配套的CMU的课后lab,难度还是很高的。做这些lab一方面能够让自己更加深入理解本章的内容,看不如做。
本章的Data Lab限制了我们的编程方式,比如不让使用if,while等,让我们能够从不同的角度去思考问题,解法不唯一,但是这些解法带给我们的灵感是非常珍贵的。

第一部分是关于整数的题目,这部分限制只能使用0x00 - 0xff大小的常数,一般只允许使用!, &, |, <<, <<. ~等运算符。

  1. 使用~和&实现异或操作。
    这个方法有很多,比较简单。我的思路是从异或的概念入手,只需要实现相同位异或为0,不同位异或为1。x&y会将01和10变为0,00是0,11是1,那么只需要取反再按位与一次即可。
/* 
 * bitXor - x^y using only ~ and & 
 *   Example: bitXor(4, 5) = 1
 *   Legal ops: ~ &
 *   Max ops: 14
 *   Rating: 1
 */
int bitXor(int x, int y) {
 //let 11 and 00 be 1&0 and 0&1, let 10 and 01 be 1&1 
 return (~(x&y)) & (~((~x)&(~y)));
}

  1. 求出32位有符号数的最小值。
    这里考察了补码的概念,因为只有正0,没有负0,所以负数表示范围比正数大一。
/* 
 * tmin - return minimum two's complement integer 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 4
 *   Rating: 1
 */
int tmin(void) {
  return 1<<31;
}
  1. 如果是有符号数的最大值返回1,否则返回0。
    这里利用了最大正数的一个特点,由于溢出(x+1)+(x+1)= 0,但是符合这一条件还有一个数是-1,所以再将-1排除即可。
/*
 * isTmax - returns 1 if x is the maximum, two's complement number,
 *     and 0 otherwise 
 *   Legal ops: ! ~ & ^ | +
 *   Max ops: 10
 *   Rating: 1
 */
int isTmax(int x) {
  //x+1 left shift 1 equal to 0, but x+1 don't equal to 0
  return !(x+x+2) & !!(x+1);
}
  1. 如果一个数的所有奇数位都被置1则返回1,否则返回0(从0开始计数)。
    利用对称性,因为所有的奇数位都是1,那么每次将x的高一半位和低一半位按位与的结果奇数位必然还是1,知道x只剩最后两位,然后看高位是不是1即可,奇数位只有一个不是1,最后必然是0。
/* 
 * 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
 */
int allOddBits(int x) {
  x = x & (x>>16);
  x = x & (x>>8);
  x = x & (x>>4);
  x = x & (x>>2);
  return (x>>1)&1;
}

  1. 求x的加法逆元。
    注意最大负数的逆元是他本身。这个题比较简单,但是后面会大量使用,将减法转化成加法。
/* 
 * negate - return -x 
 *   Example: negate(1) = -1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 5
 *   Rating: 2
 */
int negate(int x) {
  return ~x+1;
}

  1. 如果x的值是字符‘0’-‘9’的ASCII码值之间,即0x30<= x <= 0x39返回1,否则返回0
    只需要说明x-0x30和0x39-x都是正数就行,即使计算溢出也可以给出正确结果。
/* 
 * 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: ! ~ & ^ | +i << >>
 *   Max ops: 15
 *   Rating: 3
 */
int isAsciiDigit(int x) {
 int a = 0x30, b = 0x39;
 int moreThan = !((x+(~a+1))&(1<<31));
 int lessThan = !((b+(~x+1))&(1<<31));
 return moreThan & lessThan;
}

  1. 实现三目运算法?:。
    首先考虑这样一个表达式:a&y | b&z,这样只需要实现当x非0的时候,a为全1,b为全0;当x为0的时候,a为全0,b为全1,通过构造得出a = !x-1,b = ~!x +1.
/* 
 * conditional - same as x ? y : z 
 *   Example: conditional(2,4,5) = 4
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 16
 *   Rating: 3
 */
int conditional(int x, int y, int z) {
  int f = !x;
  return ((f+~1+1)&y) | ((~f+1)&z);
}
  1. 如果x<=y返回1,否则返回0
    仍然是做差,不过是要考虑溢出问题,所以分情况讨论
    当x为正数,y为负数,返回0
    当x为负数,y为正数,返回1
    当x与y同号,此时做差不会溢出,判断差的符号位即可。
/* 
 * 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 a = x>>31;
  int b = y>>31;
  int c = a ^ b;
  int diffSymbol = c&(a&1);
  int sameSymbol = !c&!(((y+~x+1)>>31)&1);
  return sameSymbol | diffSymbol;
}

  1. 实现!
    换一个角度来看,其实就是对于0返回1,对于非0返回0,所以只需要识别出0即可, 0具有一个特殊性质即0的逆元是本身,但是0xffffffff也是,此外0和0逆元的符号位都是0,其他的至少有一个是1,所以只需要对所有的符号位进行按位或,这样只有0的结果是0,然后取反即可。
/* 
 * 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 
 */
int logicalNeg(int x) {
  return ((~(x|(~x+1)))>>31)&1; // 最后一个&1不能删,因为最高位为1的时候,算术右移补的都是1,不是0
}

  1. 统计x最少需要多少位才能用补码表示出来
    这个题比较难,首先考虑正数情况,可以进行二分,每次看一半,然后加起来加上符号位。
    但是负数就比较麻烦,因为用的是补码,经过分析得出一个规律,负数只要直接取反,需要的位数是相同的。
    注意只能对负数取反,正数保持不变。
/* 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
 */
int howManyBits(int x) {
  int shift16, shift8, shift4, shift2, shift1, shift0;
  int s = x>>31;//负数s=1,正数s=0
  x = x ^ ((s<<31)>>31);//正数^ 0 ,负数 ^ 0xffffffff,这里用到了算术右移的性质
  shift16 = (!!(x>>16))<<4;
  x = x>>shift16;
  shift8 = (!!(x>>8))<<3;
  x = x>>shift8;
  shift4 = (!!(x>>4))<<2;
  x = x>>shift4;
  shift2 = (!!(x>>2))<<1;
  x = x>>shift2;
  shift1 = (!!(x>>1));
  shift0 = !!x;
  return shift16 + shift8 + shift4 + shift2 + shift1 + shift0 + 1;
}

第二部分是关于浮点数的题目,这部分基本可以使用所有的常量以及运算符,if,while等关键字

  1. 求浮点数x*2的结果
    这里考察的是浮点数存储方式和乘法,需要分情况讨论
    如果浮点数是特殊值,即阶码全1的时候,直接返回结果
    如果浮点数是非规格化的值,即阶码全0的时候,只需要对小数域左移一位即可(注意小数域到达最后,再左移就会使阶码值+1,因为这两个域是连续的)
    如果浮点数是规格化的值,需要对阶码+1,然后检测是不是达到无穷大,如果是要将小数域置为0,不要出现NaN值。
    最后保持符号位不变即可。
//float
/* 
 * 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
 */
unsigned floatScale2(unsigned uf) {
  unsigned int s = uf & 0x80000000;
  unsigned int exp = uf & 0x7f800000;
  unsigned int frac = uf & 0x007fffff;
  if(exp == 0x7f800000){
     return uf;
   }else if(!exp){
     frac <<= 1;
   }else {
     exp = exp + (1<<23);
     if(exp == 0x7f800000){
       frac = 0;
      }
   }
   return (s|exp|frac);
}
  1. 将浮点数值转化为int值,如果过大则返回1<<31
    同样是要对浮点数进行分类讨论,但是这次的分类标准与上次有所不同
    如果浮点数与0直接判==是true则说明,浮点数接近0,或者阶码是小于0的都直接返回0即可
    如果阶码大于31,则说明这个浮点数一定会溢出,直接返回最大值
    其他情况对浮点数的小数域进行正常移位操作即可,注意规格数的尾数是小数域+1
    最后注意符号位保持不变
/* 
 * 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
 */
int floatFloat2Int(unsigned uf){
  int INF, s, exp, formatFrac;
  INF  = 1<<31;
  s = uf>>31;
  exp =(uf & 0x7f800000) >> 23;
  formatFrac = uf & 0x00ffffff;
  exp -= 127;
  if(0 == uf || exp<0)
    return 0;
  if(exp > 31){
    return INF;
  }
  if(exp > 23){
    uf = formatFrac<<(exp-23);
  }else{
    uf = formatFrac>>(23-exp);
  }
  if(s){
    uf = ~uf+1;
  }
  return uf;
}

  1. 计算pow(2.0, x)
    计算2.0的x次幂,其实只与浮点数的阶码有关,因为浮点数的阶码就是表示2的多少次幂,分类讨论
    如果阶码值小于0则返回0,
    如果阶码值大于255则说明阶码达到最大,返回最大值即可
    其他情况只需要将阶码左移23位,即小数域直接置为0即可,对于规格数来说,小数域全0则是1
/* 
 * 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
 */
unsigned floatPower2(int x) {
  unsigned INF = 0xff << 23;
  int e = 127 + x;
  if (x < 0) return 0;
  if (e >= 255) return INF;
  return e << 23;
}

总体来说这个Lab的题还是有一定挑战的,不仅能够让我们在读书过程中对书中的知识理解的更加深入,而且也能启发我们从不同的角度进行编程,体会位运算的神奇之处,毕竟对于计算机来说只有位运算才是最快的。
参考文献:曾参考此篇博客

你可能感兴趣的:(计算机基础)