CSAPP-datalab

经典重温,冲冲冲。

Integer

bitXor

/* 
 * bitXor - x^y using only ~ and & 
 *   Example: bitXor(4, 5) = 1
 *   Legal ops: ~ &
 *   Max ops: 14
 *   Rating: 1
 */
int bitXor(int x, int y)
{
  return ~(~(~x & y) & ~(~y & x));
}

解题思路:布尔代数。对于异或,从定义式出发,并使用「德摩根定律」变换:

\[\begin{align} A \oplus B &= \overline{A}B + \overline{B}A \\ &=\overline{(\overline{\overline{A}B}) \cdot (\overline{\overline{A}B})} \end{align} \]

tmin

/* 
 * tmin - return minimum two's complement integer 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 4
 *   Rating: 1
 */
int tmin(void)
{
  return 1 << 31;
}

解题思路:Nothing special .

isTmax

/*
 * 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)
{
  return (!!(x + 1)) & (!(((x + 1) ^ x) + 1));
}

解题思路:isTmax(x) = 1 iff x == 0x7fffffff 。那么 x+1 = 0x80000000 ,显然 x ^ (x+1) == (int)(-1) 。ti需要考虑的特殊情况是 x = -1 ,因此加入 !!(x + 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
 */
int allOddBits(int x)
{
  int mask = (0xAA << 8) | 0xAA;
  mask = (mask << 16) | mask;
  return !((x & mask) ^ mask);
}

解题思路:偶数位上的 1 对本题毫无用处,因此构造掩码 mask = 0xAAAAAAAA 将偶数位全部清零,也就是 y = x & mask 。题目要求奇数位上必须全为 1 ,就说明 y == 0xAAAAAAAA == mask ,因此 (y ^ mask) == 0x0

negate

/* 
 * negate - return -x 
 *   Example: negate(1) = -1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 5
 *   Rating: 2
 */
int negate(int x)
{
  return (~x) + 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
 */
int isAsciiDigit(int x)
{
  return ((((x + (~0x30) + 1) >> 31) & 1) ^ 1) & ((((0x39 + (~x) + 1) >> 31) & 1) ^ 1);
}

解题思路:思路很明显,就是需要判断 0x30 <= x && x <= 0x30 。下面来看如何判断 a <= b

    a <= b
->  b - a >= 0
->  b + (~a) + 1 >= 0
->  sign(b + ~a + 1) == 0
->  sign(b + ~a + 1) ^ 1 == 1

sign(x) = (x >> 31) & 1 表示 x 的符号位。

conditional

/* 
 * 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)
{
  x = ~(!!x) + 1;
  return (y & x) + (z & (~x));
}

解题思路:原本的想法是构造 y*x + z*(!x) 这种形式作为返回值,其中 x 为 1 或 0 。但是不能用乘法,所以重新思考了一下,构造 (y & x) + (z & ~x) ,其中 x0x0 或者 0xffffffff

  • x == 0 时,需要构造 x = 0x0 = -1 + 1 = ~0 + 1
  • x != 0 时,需要构造 x = 0xffffffff = -1 = negate(1) = negate(!!x) = ~(!!x) + 1

关键点是要知道 !!x 可以将 int 映射为逻辑真值。

isLessEqual

/* 
 * 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 signx = (x >> 31) & 1;
  int signy = (y >> 31) & 1;
  int k1 = (signx ^ signy ^ 1) & (((x + (~y)) >> 31) & 1);
  int k2 = (signx & (signy ^ 1));
  return k1 | k2;
}

解题思路:看「最大数值」这一小节 。

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 
 */
int logicalNeg(int x)
{
  int neg = (x >> 31) & 1;
  int plus = ((~x + 1) >> 31) & 1;
  return (neg ^ 1) & (plus ^ 1);
}

解题思路:根据「0 既不是正数,也不是负数」这一个规律。所以如果 x 为 0 ,那么 x-x 的符号位都必然是 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
 */
int howManyBits(int x)
{
  int b16, b8, b4, b2, b1;
  x = (x >> 31) ^ x;
  b16 = (!!(x >> 16)) << 4, x >>= b16;
  b8 = (!!(x >> 8)) << 3, x >>= b8;
  b4 = (!!(x >> 4)) << 2, x >>= b4;
  b2 = (!!(x >> 2)) << 1, x >>= b2;
  b1 = (!!(x >> 1)), x >>= b1;
  return b16 + b8 + b4 + b2 + b1 + x + 1;
}

解题思路:有点类似二分查找,本题自己没做出来,是看了几个题解才写出来的。

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
 */
unsigned floatScale2(unsigned uf)
{
  unsigned sign = (uf & 0x80000000);
  unsigned exp = (uf & 0x7f800000);
  unsigned frac = (uf & 0x7fffff);

  //clear sign bit
  uf = uf & (0x7fffffff);

  //special cases: INF, NaN, zero
  if (exp == 0x7f800000 || uf == 0)
    return uf | sign;

  if (exp == 0) // uf is a denormalize number
  {
    if ((frac >> 22) == 1) // 2*uf change into a normalized number
    {
      exp = (1 << 23);
      frac = (frac << 1) & 0x7fffff;
      return sign | exp | frac;
    }
    else // 2*uf is still a denormalized number
      return sign | (frac << 1);
  }
  else // uf is a normalized number, so 2*uf must be a normalized number, too
  {
    exp += (1 << 23);
    return sign | exp | frac;
  }
}

解题思路:需要熟悉浮点数编码「IEEE 754」,然后是分类讨论。

  • uf 是非规格化数

    • 2*uf 仍然是非规格化数
    • 2*uf 是规格化数
  • uf 是规格化数

    那么 2*uf 必然是规格化数。

  • 其他特殊情况

    uf 是无穷大,NaN,0 。

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
 */
int floatFloat2Int(unsigned uf)
{
  unsigned sign = (uf & 0x80000000);
  unsigned exp = (uf >> 23) & 0xff;
  unsigned real = (uf & 0x7fffff) | (1 << 23);
  unsigned t;
  uf = uf & 0x7fffffff;

  if (exp < 127) // uf < 1.0
    return 0;
  if (exp >= 150) // we can keep all 23 frac
  {
    t = exp - 150;
    if (t < 8) // round(uf) is in int range, and we can put no more than 7 zeros after real
    {
      real = real << t;
      return (sign == 0) ? real : (-real);
    }
    return 0x80000000; // out of range, we can't put more than 7 zeros after real
  }
  real = real >> (150 - exp); // the last serval bits of frac should be thrown
  return (sign == 0) ? real : (-real);
}

本题其实仍然是分类讨论,但是分类的依据与某些特殊情况,需要不断测试才能写出代码,所以一定不能畏难,不会就马上找题解,这样收获会很小。上面的代码也是修改了好多次的,我也忘了最初的版本长什么样子了。解法也没什么特别的,就是不断地试,观察不通过的 Test Case ,然后修改。

首先有一个 trick 是把隐含的 1 还原的:

unsigned real = (uf & 0x7fffff) | (1 << 23);
/*
 * Here is a sample:
 *   155.55 = 0 10000110 00110111000110011001101
 *   frac   = 1.00110111000110011001101
 *   exp = 10000110 = 134, E = 7
 *   so the float val is:
 *     fval = 1.00110111000110011001101 × 2^7
 *          = 10011011.1000110011001101
 *   when fval cast into int, the '1000110011001101' will be thrown:
 *     ival = 0...0 10011011 = 155
 */

此时,我们默认小数点的位置在第 23 bit 与 第 22 bit 之间。

后面我们要解决的就是小数点移动到后面的哪一位的问题,这就要根据 E = exp - 127 的具体数值来决定:

  • 如果 E >= 23 ,那么 real 后面的所有小数位都得以保留,并且需要添加 E - 23 个 0 。
  • 如果 E <= 22 ,那么说明 real 的小数点最远就只能移动到第 1 bit 与第 0 bit 之间,小数点后的位都需要「丢弃」。

「丢弃」和「补 0」都通过移位来完成。

下面解析代码中的几个 if 条件。

  • exp < 127

    对于非规格化数,一定是小于 1.0 的,因为其值为 \(denormalied = 0.frac \times 2^{-126}\) 。对于规格化数,\(normalized = 1.frac \times 2^{exp - 127}\) ,要使 uf < 1.0 ,就要令 \(exp - 127 \le -1\) ,所以可得 uf < 1.0 的等价条件为 exp <= 126

  • exp >= 150

    此情况是可保留 23 位 frac 。令 E = exp - 127 >= 23 ,即可得:exp >= 150 。超出 23 的部分就要补上 exp - 150 个 0 ,但是最多只能补 7 个 0 ,因为 real 本身就有 24 位,int 的符号位占 1 位。

    /*
     * For an example:
     *   999999999.0 = 0 10011100 11011100110101100101000
     *   frac =   11011100110101100101000
     *   real = 1.11011100110101100101000
     *   exp = 10011100 = 0x9c = 156, E = 29
     *   so the float val is:
     *     fval = 1.11011100110101100101000 × 2^(29)
     *          = 111011100110101100101000 × 2^(6)
     *          = 111011100110101100101000 000000
     *   when cast into int val:
     *     ival = 111011100110101100101000 000000
     *   ival has 24 + 6 = 30 bits, put it into 32 bits is:
     *     ival = (00 111011100110101100101000 000000)2 = 1000000000
     *   the number '1000000000' is decimal
     */
    
  • 127 <= exp <= 149

    这里是就是单纯的「舍弃」小数点后面的数值。看上面提到的例子:

    /*
     * Here is a sample:
     *   155.55 = 0 10000110 00110111000110011001101
     *   real   = 1.00110111000110011001101
     *   exp = 10000110 = 134, E = 7
     *   so the float val is:
     *     fval = 1.00110111000110011001101 × 2^7
     *          = 10011011.1000110011001101
     *   when fval cast into int, the '1000110011001101' will be thrown:
     *     ival = 0...0 10011011 = 155
     */
    

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
 */
unsigned floatPower2(int x)
{
  int exp = x + 127;
  if (1 <= exp && exp <= 254) // 2^x can be a normalized number
    return exp << 23;
  else if (exp >= 255) // 2^x out of range
    return 0x7f800000;
  else // x <= -127 (exp <= 0), 2^x may be a denormalized number, maybe zero
  {
    if (x >= -149)
      return 1 << (149 + x);
    else
      return 0;
  }
}

本题要求实现 \(2^x\) 。显然,这就是浮点数的一种十分特殊的情况:\(1.0...0 \times 2^{exp - 127}\) 。但是 \(exp \in [1,254]\) ,这需要对 exp = E + 127 = x + 127 进行讨论。注意 exp 必须要使用有符号数进行存储,因为 x + 127 仍可能为负数

  • 1 <= exp && exp <= 254

    说明 \(2^x\) 在规格化数的范围内,即 \(val = 1.0\cdot\cdot\cdot0 \times 2^x\) ,所以 frac = 0...0, exp = x + 127

  • exp >= 255

    说明 \(2^x\) 超过了 float 的有效范围。

  • exp <= 0

    这时候 \(2^x\) 就必须采用非规格化数来存储。非规格化数 \(denormalized = 0.frac \times 2^{-126}\) ,所以:

    • \(2^x\) 的最大值为 \(max = 0.10\cdot\cdot\cdot0 \times 2^{-126} = 2^{-127}, x=-127\)

    • \(2^x\) 的最小值为 \(min = 0.0\cdot\cdot\cdot01 \times 2^{-126} = 2^{-149}, x=-149\)

    \(2^x\) 这种形式决定了 frac 中必定只有一个 1 ,而这个 1 的后面 0 的数目取决于 \(x\) 的大小,实际上不难发现是 \(149 + x\)

Evaluation

unix > ./driver.pl
Correctness Results     Perf Results
Points  Rating  Errors  Points  Ops     Puzzle
1       1       0       2       8       bitXor
1       1       0       2       1       tmin
1       1       0       2       8       isTmax
2       2       0       2       7       allOddBits
2       2       0       2       2       negate
3       3       0       2       13      isAsciiDigit
3       3       0       2       8       conditional
3       3       0       2       14      isLessOrEqual
4       4       0       2       9       logicalNeg
4       4       0       2       32      howManyBits
4       4       0       2       22      floatScale2
4       4       0       2       18      floatFloat2Int
4       4       0       2       10      floatPower2

Score = 62/62 [36/36 Corr + 26/26 Perf] (152 total operators)

Summary

Enjoy the fun of writing !

你可能感兴趣的:(CSAPP-datalab)