Datalab

Befunny 于 2021-01-20 发布

本实验共有13个Puzzle.

BitXor

题目要求

 * bitXor - x^y using only ~ and & 
 *   Example: bitXor(4, 5) = 1
 *   Legal ops: ~ &
 *   Max ops: 14

开始自己没有认真审题,除了规定的运算符之外,还是用了+,不过好在思路比较清晰,所以也把不符合要求的解决方法也记录下来。

解题思路

按位异或是将数以二进制形式表示,当在某位上数值不同时,则异或结果为1,否则结果为0。以8位二进制数为例,

X     0 0 0 0 1 0 0 1
Y     1 0 1 0 1 0 0 0
X^Y   1 0 1 0 0 0 0 1

我的想法是可以找出x和y中都为1的bit位,并将该bit位设置为1,其余bit位都设置为0。 X&Y

同样的,也通过同样的方法找到x和y中都为0的所有bit位,并将该bit位设置为1,其余bit位设置为0。 (~X)&(~Y)

将两者相加,其结果中为1的bit位表示在X和Y在该bit位上同为1或0,即在该位上两数相同。而结果中为0的bit位表示X和Y在该bit位上一个为1,另一个为0。 (X&Y)+((~X)&(~Y))

此时和题目的最终要求是相反的,只需要再按位取反即可。 ~((X&Y)+((~X)&(~Y)))

很不幸的是,以上的方法虽然思路很清晰,但是超过了题目中对运算符类型的限制。因此需要想办法使用允许的运算符替换+

那换个思路,如果将X&Y按位取反,则结果中为0的bit位表示X和Y在该bit位上都为1。然后将(~X)&(~Y)也按位取反,则结果中为0的bit为表示X和Y在该bit位上都为0。 最终将两结果按位与,相当于一个交集,两者相同的部分被保留,其他都置为0。 (~(X&Y))&(~((~X)&(~y)))

tmin

题目要求

/* 
 * tmin - return minimum two's complement integer 
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 4
 *   Rating: 1
 */

解题思路

有符号数的最小值就是符号位为1,其余位都为0的数。对0x01进行左移可以得到Tmin。

int tmin()
{
	return 1<<31;
}

isTmax

题目要求

/*
 * isTmax - returns 1 if x is the maximum, two's complement number,
 *     and 0 otherwise 
 *   Legal ops: ! ~ & ^ | +
 *   Max ops: 10
 *   Rating: 1
 */

解题思路

Tmax : 符号位为0,其余位都为1。因此Tmax有个特点是,Tmax加1会导致溢出,结果变成Tmin。下图以8bit为例

|Tmax   | 0 1 1 1 1 1 1 1|
|1      | 0 0 0 0 0 0 0 1|
|———————|———————————————-|
|result | 1 0 0 0 0 0 0 0|

而 Tmax + Tmin + 1 = 0, 因此(x+1+x+1)==0可以用来判断x是否为Tmax。需要注意的是,还有一个特殊的例子也是满足这个等式的,那就是当x==0xff时,因此需要排除这个例外。如果排除这个例外呢,就需要仔细想想0xff和Tmax有啥不同的点。如果对0xff和Tmax都按位取反,会发现~0xff = 0, 而~Tmax != 0。 对于!!(~x), 当x==0xff时,结果为0,而x==Tmax时,结果为1。

int isTmax(int x)
{
    int is_tmax_or_0xff = !(x+1+x+1);
    // is_tmax_or_0xff 用来判断该数是否为0xff或Tmax
    // (!!(~x))用来排除是否为0xff
    return is_tmax_or_0xff&(!!(~x));
}

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
 */

解题思路

我自己的思路既然要判断所有偶数位是否为1,那就得想办法先提取这些位来

  1. 构造一个偶数位为1,奇数位为0的 int 数 0xAAAAAAAA, 假设命名为allOddIs1
  2. 将该数与allOddIs1进行按位与 allOddIs1&x

如果该数的偶数位都为1,按位与的结果与(~allOddIs1)+1 互为相反数。 而该数的任意偶数位不为1时,按位与的结果与(~allOddIs1)+1不会是互为相反数。因此我们可以利用这个规律来解题。

int allOddBits(int x)
{
	int allOddIs1 = (0xAA<<24)+(0xAA<<16)+(0xAA<<8)+0xAA;
	return !(allOddIs1&x + ~allOddIs1 + 1);
}

另外一种思路是通过移位操作,依次将所有偶数位按位与到 低2位。只有所有的偶数位都为1时,最终结果的偶数位才能为1.

int allOddBits(int x)
{
    int a = (x&x>>2);
    int b = (a&a>>4);
    int c = (b&b>>8);
    int d = (c&c>>16);
    return (d>>1)&0x01;  
}

negate

题目要求

/* 
 * negate - return -x 
 *   Example: negate(1) = -1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 5
 *   Rating: 2
 */

解题思路

x + (~x) = -1 x + (~x) + 1 = 0 因此x的相反数时 (~x)+1

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

解题思路

开始自己一直执着于进行bit位比较来判断大小,后来发现这条路走不通。参考了别人的思路,数学上比较大小可以使用减法

当结果为0或者正时,表示被减数大于等于减数;当结果为负时,表示被减数小于减数。

虽然题目要求中并没有允许使用-,但是减去一个数可以变化成加上这个数的相反数,而相反数又可以使用反码表示,即 X + Y = X + (-Y) = X + (~Y) + 1 有符号数的二进制表示中,最高位为符号位,因此我们可以通过该位来判断数的正负。

!(((x+(~y)+1)>>31)&0x01) 将结果进行右移31位,相当于将符号位放置于最低有效位,这样不论是算数右移还是逻辑右移,在于0x01进行按位与操作后,都将只保留最低有效位。当结果等于0时,说明之前的符号位是0,进而得知X>=Y。

int isAsciiDigit(int x)
{
	int great_than_0x30 = !(((x+(~0x30)+1)>>0x31)&0x01);
	int less_than_0x39 = !(((0x39+(~x)+1)>>31)&0x01);
	return great_than_0x30&less_than_0x39;
}

conditional

题目要求

/* 
 * conditional - same as x ? y : z 
 *   Example: conditional(2,4,5) = 4
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 16
 *   Rating: 3
 */

解题思路

这道题需要将x这个条件变化成全1或者全0。将任意一个数变成0或1,一般考虑使用取反!运算符,这样结果要不是0x01,要不是0x00。此时先利用左移将最低位移至符号位,然后再利用算数右移的特点,向右移位移时使用符号位填充。

int conditional(int x)
{
    int mask = (!x)<<31>>31;
    return ((~mask)&y) + (mask&z);
}

isLessOrEqual

题目要求

/* 
 * isLessOrEqual - if x <= y  then return 1, else return 0 
 *   Example: isLessOrEqual(4,5) = 1.
 *   Legal ops: ! ~ & ^ | + << >>
 *   Max ops: 24
 *   Rating: 3
 */

解题思路

这道题目需要注意的点是,两符号不同的数进行相减操作时有可能造成溢出。因此我们需要两种情况来分析。

  1. 两数同符号

    当两数同为正或同为负时,比较大小可以使用减法,然后判断差的符号位即可。

  2. 两数不同符号

    此时只要y是正数,即符号位为0,就应该返回1

判断是否同符号可以对符号位进行异或操作 (x»31&0x01)^(y»31&0x01),然后分别用两分支来对不同的情况进行处理。

需要注意的是,两分支之间使用的是+号,因此需要保证当一个分支满足的时候,另外一个分支是0.

in isLessOrEqual(int x, int y)
{
    int is_diff = (x>>31&0x01)^(y>>31&0x01);
    int is_y_not_less_than_x = !((y+(~x)+1)>>31&0x01);
    return ((!is_diff)&is_y_not_less_than_x) + (diff&(!(y>>31&0x01)));
}

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 
 */

解题思路

一般情况下,x与相反数((~x)+1)按位或,结果的符号位为1。但是,0是一个特例,因为0的相反数也是0。

然后我们可以利用算数右移31位,用符号位填充。当x为0时,0 ((~0)+1)=0,符号位是0,算数右移时使用0填充,最终的结果是0。而当x不为0时,x ((~x)+1) < 0, 符号位为1,算数右移时使用1填充。最后将结果加1,满足题目要求。
int logicalNeg(int x)
{
    return (x|(~x)+1))>>31+0x01;
}

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
 */

解题思路

表示一个数需要用到的最少位数,需要将该问题分为正负两种情况来讨论。

  1. 正数

    找到最高的为1的位,以0x05为例,最高的为1的位是3,再加上符号位,因此只需要4位二进制就可以表示0x05。

    ------------------------------
    |    0x05   | 0 0 0 0 0 1 0 1|
    ------------------------------
    
  2. 负数

    找到最高的为1的位,以0xF5为例,最高的为0的位数4,再加上符号位,因此只需要5位二进制就可以表示。

    ------------------------------
    |    0xF5   | 1 1 1 1 0 1 0 1|
    ------------------------------
    

    如果我们先把负数按位取反,就可以将问题转化为与正数情况一样的查找最高的为1的位。

说到这里,我们就可以使用二分查找法来解决该问题。

int howManyBits(int x)
{
    int b16, b8, b4, b2, b1, b0;
  	int sign = x>>31;  
  	//根据正负预处理, x的符号位为0
    x = (sign&(~x))+(!sign&x);
    //x的符号位为0,因此在进行算数右移时,使用0来填充,因此位移后高16位为0,原来的高16位移到低16位。然后根据使用!来判断是否为0来判断该16位是否含有1。如果有1,则b16为16,否则为0》
    b16 = !!(x>>16)<<4;
    // 当高16中含有1时,b16为16。将高16为移到低16位,然后再进行二分处理。
    x = x>>b16;
    b8 = !!(x>>8)<<3;
    x = x>>b8;
    b4 = !!(x>>4)<<2;
    x = x>>b4;
    b2 = !!(x>>2)<<1;
    x = x>>b1;
    b0 = x;
    return b16+b8+b4+b2+b1+b0;
}

floatScale2

题目要求

//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 exp = uf&0x7f800000;
  unsigned int sign = uf&0x80000000;

  if(exp == 0)
    return (uf<<1)|sign;
  if(exp == 0x7f800000)
    return uf;
  exp=exp+0x00800000;
  if(exp==0x7f800000)
    return 0x7f800000|sign;
  return sign+exp+(uf&0x007fffff);
}

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
 */

解题思路

IEEE的浮点数表示

\[\begin{aligned} & F = (-1)^s*M*2^E \\ & s: 符号位 \\ & M: 尾数,二进制小数,范围为[1, 2) \\ & E: 阶码 \end{aligned}\]

而计算机对浮点数进行编码时,以单精度浮点数为例,其结构如下

---------------------------------
| s |    exp    |      frac     |
---------------------------------

其中 s, exp, frac 分别为 1 位,8 位 和 23 位。

题目中的要求是将浮点数转变为整型,

以下是本题的正确解法。

int floatFloat2Int(unsigned uf) {
  // E = exp - bias
    int E = ((uf>>23)&0xff) - 127;
    unsigned int sign = uf&0x80000000;
    int frac = (uf&0x007fffff)|0x00800000;

    if (E>31)
        return 0x80000000u;
    if (E<0)
        return 0;
    int value = ((E>23)?(frac<<(E-23)):(frac>>(23-E)));
    return sign?-value:value;
}

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
 */

解题思路

\[2.0^x = (-1)^0*(1.0)_2*2^x\]

根据上述推导,其实x就相当于E,x + bias 就是exp。也可以得知该浮点数为规格化浮点数(因为尾数M为1.x),因此exp的范围为(0, 255)

unsigned floatPower2(int x) {
  int exp = x + 127;
  if (exp<=0)
    return 0;
  if (exp>=0xff)
    return 0x7f800000;
  return exp<<23;
}