首页 CSAPPLAB-DATALAB
文章
取消

CSAPPLAB-DATALAB

1

1
2
3
4
5
6
7
8
9
10
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) {
  return ~(x & y) & (~((~x)&(~y)));
}

​ 这道题目,只允许使用~和&来实现异或操作,首先异或就是两位不一样的时候,结果是1,也就是一个是0一个是1。

​ 我先是遍历了所有可能的情况:

  • x&y:两位同时是1就是1,找到了两位同时是1的位
  • ~x&y:y是1时,x是0结果是1,找到了y是1时,xy不同的位
  • x&~y:x是1时,x是0结果是1,找到了y是1时,xy不同的位
  • ~x&~y:xy同时是0结果就是1,找到了两位同时是0的位

​ 其实这个时候,我发现用x&y和~x&~y可以找到两位同时是0或者1的位,那么除了这些情况,不就是二者不同的情况吗?

2

1
2
3
4
5
6
7
8
9
10
11
/* 
 * tmin - return minimum two's complement integer 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 4
 *   Rating: 1
 */
int tmin(void) {

  return 1<<31;

}

​ 这个直接左移即可。

3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/*
 * 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) {
  int tmin = ~x;
  return !((1+x)^(tmin))&!!(x^(~0));
}
//优化
int isTmax(int x) {
    int y = x + 1;
    return !( (y ^ ~x) ) & !!y;
}

​ 这道题目是利用tmax的性质:

  • tmax+1=tmin
  • ~tmax = tmin

​ 但是还有一个值符合上面的性质,就是-1,那么直接排除掉-1即可

​ 优化:我之前是用^直接对比-1这个值,但是实际上可以利用-1+1=0这个性质,这个写法会省两个操作

4

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
/* 
 * 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 a = 0xAA;
	int b = a << 2;
	int c = b | a;
	int d = c << 4;
	int e = d | c;
	int f = e << 8;
	int g = f | e;
	int h = g << 16;
	int i = h | g;
	int j = x & i;
	int k = j ^ i;
  return !k;
}
//优化
int allOddBits(int x) {
    int mask = 0xAA;              // 00000000 00000000 00000000 10101010
    mask = mask | (mask << 8);    // 00000000 00000000 10101010 10101010  -> 0x0000AAAA
    mask = mask | (mask << 16);   // 10101010 10101010 10101010 10101010  -> 0xAAAAAAAA
    return !((~x) & mask);
}

​ 这道题就是判断所有奇数位是不是1,如果是,那么返回1,不是就返回0。

​ 那么问题来了,怎么判断全部奇数位是不是1呢?

  • 只要让x和全部奇数位为1的数也就是0xAAAAAAAA进行一个&,如果x的全部奇数位是1的话(不管偶数位是不是1),得到的答案就是0xAAAAAAAA,否则答案一定不是0xAAAAAAAA。

​ 那么怎么返回0或者1呢?

  • 根据第3题的写法,使用^就能判断x&0xAAAAAAAA是否是完全一样的,完全一样返回0,这时候取个!就可以了

​ 那么允许使用的的int范围在0-255,怎么获得0xAAAAAAAA呢?

  • 就不断左移和|就可以了

​ 优化:

​ 我之前貌似对0xaa有点错误的理解……实际上只需要两次就可以做出来。

​ 对x取~后,如果奇数位所有都是1,那么最后(~x) & mask就是0,反之,但凡有一位奇数位是0,那么(~x) & mask就会存在1。

5

1
2
3
4
5
6
7
8
9
10
/* 
 * negate - return -x 
 *   Example: negate(1) = -1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 5
 *   Rating: 2
 */
int negate(int x) {
  return ~x+1;
}

​ 算负数,直接取反加1秒杀了。

6

1
2
3
4
5
6
7
8
9
10
11
12
13
//3
/* 
 * 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)|!(x^0x31)|!(x^0x32)|!(x^0x33)|!(x^0x34)|!(x^0x35)|!(x^0x36)|!(x^0x37)|!(x^0x38)|!(x^0x39));
}

​ 我先想到了暴力做法,通过^可以遍历一下x是不是‘0’-‘9’,是那么必然有一项是0其他都是大于0的数,取个!就能把这个关系变成1和0,再一|,但凡有一个1就是1,也就是说在‘0‘-‘9’。但是这不符合条件要求的15个ops。

1
2
3
int isAsciiDigit(int x) {
  return (!((x+(~0x30+1)>>31) | ((0x39+(~x+1))>>31)));
}

​ 我又想到一个办法,就是让通过x-0x30>=0和0x39>=0同时满足就可以,之前我一直以为!是可以直接判断正负的,我以为大于0的取!会得到0,小于等于0的取!会得到1,然而事实是,0取!得到1,非0取!得到0。

  • 通过x»31&1可以判断正负,但是这里不需要&1,因为最后会有!兜。
1
2
3
4
5
6
7
8
int isAsciiDigit(int x) {
    int lower = x + (~0x30 + 1);  // x - 0x30
    int upper = 0x39 + (~x + 1);  // 0x39 - x
    int sign  = (lower | upper) >> 31; // 只要有一个为负,最高位就是1(-1)

    return !sign;  // sign=0 → 在区间内 → 返回1;sign=-1 → 返回0
}

​ 这个方式可以少一个op。

7

1
2
3
4
5
6
7
8
9
10
11
/* 
 * 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 a = ~(!x)+1;
  return (y & (~a)) | (z & a);
}
​ 我首先想要实现的一点是y oprator expression = 全0或者本身,z同理,这样的话二者取就可以得到y或者z了。

​ 那么我又开始遍历了

  • y & 0 = 0
  • y & 全1 = y(全1其实也就是-1)
  • y | -1 = -1
  • y | 0 = y

​ 那么现在问题变成了怎么根据x的变换来获得0和-1,那么已知通过!可以根据x是否非0得到0或1,那么再取个负数,0还是0,1就变成全1了也就是-1,颗秒!邦邦邦邦!

8

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
/* 
 * 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 isOppositeSign = (x^y)>>31&1;
  int isxNeg = x>>31&1; 
  int sameSign = (y + (~x + 1))>>31&1;
  //conditional(isOppositeSign, isxNeg, !sameSign)
  int a = ~(!isOppositeSign)+1;
  return (isxNeg & (~a)) | (!sameSign & a);
}
//优化:
int isLessOrEqual(int x, int y) {
    int sx = (x >> 31) & 1;           // x 的符号:x<0 → 1, 否则 0
    int sy = (y >> 31) & 1;           // y 的符号
    int signDiff = sx ^ sy;           // 符号是否不同:1 表示异号

    int diff = y + (~x + 1);          // y - x
    int sd = (diff >> 31) & 1;        // y-x 的符号:<0 → 1, 否则 0

    // 情况1:符号不同时,只要 x<0,y>=0 就一定 x<=y
    //   signDiff=1 且 sx=1 → x<=y
    // 情况2:符号相同,则看 y-x 是否非负(sd=0)
    //   signDiff=0 且 sd=0 → x<=y
    return (signDiff & sx) | ((!signDiff) & (!sd));
}

​ 这道题直接复用上道题目的答案了。这道题其实本意还是判断正负,x<=y挪过去,y-x>=0也就是判断y-x的符号是否是正数即可,也就是说,右移31位&1后,如果结果是0,那么这个不等式就成立了,这也是为什么没有把y往左挪的原因。如果是x-y<=0,那么同样的方式右移31位&1后,如果结果是1,只能说明x-y<0,等于不在里面。

​ 好了,现在要解决第二个问题,也就是溢出问题,那么现在开始考虑,什么时候会溢出?

  • xy同号的时候,肯定不会溢出,直接用上面的公式判断不等式结果即可
  • xy异号的时候,就可能会溢出了,那么异号又有两种情况
    • x正y负,上面的不等式肯定不成立,也就是返回0
    • x负y正,上面的不等式肯定成立,也就是返回1

​ 那么现在问题变成了,先判断xy是同号还是异号,如果是同号,那么就走不等式判断正负的分支。如果xy是异号,那么就根据x是否是负数,来返回1或者0即可。

​ 优化:其实类似conditionnal的判断,这样子省一些ops。

9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//4
/* 
 * 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 y = ~x+1;
  return (((x>>31&(~0)))|(y>>31&(~0)))+1;
}
//优化:
int logicalNeg(int x) {
  int y = ~x + 1;
  return ((x >> 31) | (y >> 31)) + 1;
}
//优化:
int logicalNeg(int x) {
    return ((x | (~x + 1)) >> 31) + 1;
}

​ 这道题要利用0的特性,也就是0的负数还是0,但是正数和负数时相反数,但但是,tmin的负数还是本身,奇妙的是,如果用»31&1的方式来判断正负的话,对于0而言就永远是0,所以对0取正负然后用»31&1判断就只有一种情况也就是00(指的是正数符号是0,负数符号也是0),对于非0值x,当对x取负数的时候,x»31&1有三种情况,就是11(如tmin正数负数都是本身,都是负数)、10、01,所以这样就把0和非0区分开了。

​ 还有一点就是0要返回1,非0要返回0,这一点比较反人类,不能»31&1得到x的正负数后直接用|,因为这样的结果是相反的,所以此处得用个小巧思,让判断正负的式子&-1,这样的话,如果就变成了-1-1、-10、0-1、00,然后|之后加一就行了。

​ 优化:补码中,正负数右移就是0x000000000xFFFFFFFF,不需要&全1了。

​ 优化:其实真正需要的就只有符号位罢了,所以先正负数|了,再取符号也可以,省ops。

10

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/* 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 a = conditional(x>>31&1,~x,x);
	int pos = 0;
	int bits16, bits8, bits4, bits2, bits1,bits0;
	bits16 = !!(a >> 16) << 4;
	a = a >> bits16;
	bits8 = !!(a >> 8) << 3;
	a = a >> bits8;
	bits4 = !!(a >> 4) << 2;
	a = a >> bits4;
	bits2 = !!(a >> 2) << 1;
	a = a >> bits2;
	bits1 = !!(a >> 1);
	a = a >> bits1;
  bits0 = !!(a);
	pos = bits16 + bits8 + bits4 + bits2 + bits1 + bits0;
	return pos+1;
}
//优化:
int howManyBits(int x) {
    int sign = x >> 31;          // 0 or -1
    int a = x ^ sign;            // x>=0 -> a=x; x<0 -> a=~x

    int bits16, bits8, bits4, bits2, bits1, bits0;
    int pos;

    bits16 = !!(a >> 16) << 4;
    a >>= bits16;

    bits8 = !!(a >> 8) << 3;
    a >>= bits8;

    bits4 = !!(a >> 4) << 2;
    a >>= bits4;

    bits2 = !!(a >> 2) << 1;
    a >>= bits2;

    bits1 = !!(a >> 1);
    a >>= bits1;

    bits0 = a;                  // 此时 a 是 0 或 1

    pos = bits16 + bits8 + bits4 + bits2 + bits1 + bits0;
    return pos + 1;
}

​ 这道题目首先要理解一个定义,也就是有效位。对于正数而言,有效位就是1所在最高位,因为最高位的1左侧的0只是拓展符号位,如000110这个二进制数的前3个0,相当于一个0的作用,也就是符号位,和0110值是一样的。对于负数而言,恰恰相反,有效位就是0所在的最高位,因为最高位0左侧的1相当于拓展符号位,如11101,左边3个1相当于一个1的作用,也就是和101值是一样的。

​ 正数还好理解,负数怎么理解呢?其实可以这么想,对于11101来说,相当于 \(-2^4+2^3+2^2+2^0\) ​ 而前三项加起来,就是\(-2^2\),这样的话,不就相当于101了吗,所以前置1都是拓展符号位。

​ 所以对于负数,最开始取个~就能和正数一样计算了,算1所在下标,然后+1即可。

​ 然后要解决的问题就是怎么算出来最高位的下标,用二分法即可。先判断是在高16位还是低16位,如果在高16位,那么就把这个数右移16位,否则就不用管。然后在去判断是在当前这个数的高8位还是低8位,以此类推,就能得到最高位1所在的下标。

​ 对于0和-1这两个数来说,符号位也可以作为数字位,可以想象1这个二进制数,作为补码,也就是\(-2^0\),不就是-1吗。

​ 优化:用^全0或者全1,就可以得到本身或者取~。

11

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
//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 s = uf >> 31 & 1;
	unsigned maskSExp = (~0 << 23);
	unsigned exp = (uf & maskSExp) << 1 >> 24;
	unsigned maskM = ~maskSExp;
	unsigned m1 = uf & maskM;
	unsigned newExp;
	unsigned newM1;
	if (!(exp | 0)) {
		newExp = m1 >> 22 & 1;
		newM1 = m1 << 1;
	}
	else {
		newExp = exp + !!(exp ^ 0xff);
		newM1 = m1;
	}

	return (s << 31) | (newExp << 23) | (newM1 & maskM);
}
//优化:
unsigned floatScale2(unsigned uf) {
    unsigned s   = uf & 0x80000000u;    // 符号位
    unsigned exp = (uf >> 23) & 0xFF;   // 指数字段 [0..255]
    unsigned frac = uf & 0x7FFFFFu;     // 尾数字段

    // 情况 1:exp == 255,NaN 或 Inf,按题意原样返回
    if (exp == 0xFF) {
        return uf;
    }

    // 情况 2:exp == 0,0 或 非规格化数
    if (exp == 0) {
        // 非规格化:倍乘就是 frac 左移一位
        frac <<= 1;
        // 如果左移后“顶到了隐藏位”,变成规格化数
        if (frac & 0x800000u) {      // 检查 bit23
            exp = 1;
            frac &= 0x7FFFFFu;      // 清掉 bit23,只保留 22:0
        }
    } else {
        // 情况 3:规格化有限数:指数 +1
        exp += 1;
        // 如果加到 255,说明溢出到 +Inf,尾数清零
        if (exp == 0xFF) {
            frac = 0;
        }
    }

    return s | (exp << 23) | frac;
}

​ 这道题首先要明白浮点数的定义和乘法和特殊条件。

​ 首先,浮点数由三部分构成,S、Exp、M。其中S一位,表示符号位,Exp八位是阶码,M二十三位是尾数。

​ 其次E=Exp-Bias,\(Bias=2^k-1\),对于8位,就是127。

​ 一个浮点数计算结果就是:\((−1)^S*M*2^E\)。

​ 所以乘以2还是很容易的,一般情况下就直接让exp+1即可,这里需要考虑几个特殊情况,主要就是非规格化数部分。

​ 第一,当exp全1的时候,那么乘以2,此时不需要+1,全1要么是正无穷或者负无穷,要么就是Nan。

​ 第二,当exp全0的时候,那么乘以2,就不一定是+1了,此时还有两个情况,如果尾数没有前置1,那么只是把尾数左移即可,exp不用变。如果有前置1,仍然需要把尾数左移,只是这时候,从非规格化数到了规格化数,所以exp就要+1了,记得尾数左移要截断那个1,因为此时变成了潜在1。

​ 优化:

​ 首先,exp的算法可以直接用uf右移去算;其次,我疏忽了一个情况,就是exp=254,frac不为0,此时乘以2应该是inf,而不是Nan

12

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
/* 
 * 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 s = uf >> 31;
  unsigned exp = (uf>>23) & 0xff;
  unsigned frac = uf&0x7fffff;
  unsigned m = (1u<<23)|frac;
  unsigned ans;
  int E = exp - 127;
  
  if(E <= -1) return 0;
  if(E > 30) return 0x80000000u;
  if(E>=23){
    ans = m<<(E-23);
  }else{
    ans = m>>(23-E); 
  }
  if(s) return ~ans+1;
  
  return ans;
}

​ 规格化数:E=exp-Bias(127),非规格化数E=1-Bias,这道题完全不用考虑非规格化数,所以直接算规格化数即可,尾数是有个潜在1的,把这个注意好就可以。

13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* 
 * 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) {
    if(x >= 128) return 0x7f800000;
    if(x < -149) return 0;
    if(x<=-127) return 1u << (x+149);

    return (x + 127)<<23;
}

​ 这道题目主要就是考虑几个情况。

​ 第一,x>=128也就是当exp全1的时候,E=exp-Bias=255-127=128的时候就已经需要返回inf了,也就是正无穷。

​ 第二,当x<-149也就是当exp全0的时候,E=1-Bias=1-127=-126,然后尾数是1,-126-23=-149,此时是能取到的最小值,那么小于-149的,就返回0。

​ 第三,当介于[-127,-149]之间的,都属于非规格化数,exp还是全0,尾数不停左移即可。

​ 第三就是其他范围都属于规格化数,算出来exp即可,尾数就是全0。

Results

第一次全通过时的分数:

Correctness Results Perf Results

Points Rating Errors Points Ops Puzzle

1 1 0 2 7 bitXor

1 1 0 2 1 tmin

1 1 0 2 9 isTmax

2 2 0 2 11 allOddBits

2 2 0 2 2 negate

3 3 0 2 12 isAsciiDigit

3 3 0 2 7 conditional

3 3 0 2 18 isLessOrEqual

4 4 0 2 10 logicalNeg

4 4 0 2 41 howManyBits

4 4 0 2 23 floatScale2

4 4 0 2 17 floatFloat2Int

4 4 0 2 9 floatPower2

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

本文由作者按照 CC BY 4.0 进行授权