- BitXor
- tmin
- isTmax
- allOddBits
- negate
- isAsciiDigit
- conditional
- isLessOrEqual
- logicalNeg
- howManyBits
- floatScale2
- floatFloat2Int
- floatPower2
本实验共有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,奇数位为0的 int 数 0xAAAAAAAA, 假设命名为allOddIs1
- 将该数与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
*/
解题思路
这道题目需要注意的点是,两符号不同的数进行相减操作时有可能造成溢出。因此我们需要两种情况来分析。
-
两数同符号
当两数同为正或同为负时,比较大小可以使用减法,然后判断差的符号位即可。
-
两数不同符号
此时只要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的位,以0x05为例,最高的为1的位是3,再加上符号位,因此只需要4位二进制就可以表示0x05。
------------------------------ | 0x05 | 0 0 0 0 0 1 0 1| ------------------------------ -
负数
找到最高的为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
*/
解题思路
该题目需要分情况讨论:
-
exp等于0
该情况下为非规格化浮点数(包含0.0)。该情况下只需要整体左移,相当于将尾数乘2,在这里还分为两个情况,以8bit的浮点数为例,其中最高位是符号位,第四位是尾数,中间三位是指数 exp
-
尾数溢出, 尾数的最高位的1移到了指数位
位移前
\[(-1)^0*(0.1000)_2*2^{-6} = \frac{1}{2}*2^{-6} = 2^{-7}\]------------------- | 0 0 0 0 1 0 0 0 | -------------------左移一位
\[(-1)^0*(1.0000)_2*2^{-6} = 2^{-6}\]-------------------- | 0 0 0 1 0 0 0 0 | --------------------左移后变成了左移前的2倍,符合题目要求。
-
尾数不溢出
位移前
\[(-1)^0*(0.0100)_2*2^{-6} = \frac{1}{4}*2^{-6} = 2^{-8}\]------------------- | 0 0 0 0 0 1 0 0 | -------------------左移一位
\[(-1)^0*(0.1000)_2*2^{-6} = \frac{1}{2}*2^{-6} = 2^{-7}\]------------------- | 0 0 0 0 1 0 0 0 | -------------------左移后变成了左移前的2倍,同样符号要求。
-
指数和尾数都为0时,表示为浮点数0.0。左移一位,还是0.
左移一位会覆盖掉符号位,因此需要将原符号位赋于位移结果。
-
-
exp 等于 0xff
- 尾数等于0时,根据符号位分别表示正无穷大或者负无穷大,正(负)无穷大乘2依然是正(负)无穷大,直接返回
- 尾数不等于0时,该值为NaN(Not a Number),直接返回
-
0<exp<255
此时被称为规格化浮点数,只需要将指数exp加一即可,同时需要注意符号位。
- exp+1 小于0xff时,正常返回
- exp+1 等于0xff时,返回无穷大
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 位。
- s 用来表示符号位
- exp为阶码字段,通过一些特定规则表示E(exp并不直接等于E)
- frac小数字段,用来编码尾数M
题目中的要求是将浮点数转变为整型,
-
当E>31时,超过了Int型可以表示的范围
-
当E<0时,该数为小于1的数,因此取0
-
当 0<=E<=31时,此时该浮点数为规格化浮点数(非规格化浮点数E=1-bias=1-127=-126)。\
f为浮点数小数,其二进制表示为 \(0.f_{23}f_{22}...f_0\) 则尾数定义为 \(M = 1 + f\) 需要判断指数E是否大于小数字段的长度23
-
当 E > 23
说明有E位需要在小数点左侧,而此时只有23位,因此我们需要左移(E-23)位。
-
当 E < 23
说明(23-E)位的小数字段需要在小数点右侧,因此需要右移(23-E)位。
最后注意需要考虑符号位。
-
以下是本题的正确解法。
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;
}