每道题都有约束的规则,具体内容在代码块里;

1.bitXor

运算两数异或,不使用 ^;

代码:

1
2
3
4
5
6
7
8
9
10
11
/* 
* bitXor - x^y using only ~ and &
* Example: bitXor(4, 5) = 1
* Legal ops: ~ &
* Max ops: 14
* Rating: 1
*/
int bitXor(int x, int y) {
int result = ~(~x&~y)&~(x&y)
return result;
}

二进制异或是相同0或相同1就变0,而不同为0或1就变1;

让原来不同的地方变为相同,同的地方变为不同,再用 & 运算就可得到该值;

2.tmin

返回最小的二进制补码整数;

代码:

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

最小二进制补码整数是一个负数,补码规则最高位为1表示负数,则最小的负数是10000000 00000000 00000000 0000000;也即是 0x80 00 00 00;

3.isTmax

返回1,如果是二进制补码最大值,不然返回0;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* 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 result = x + x + 1;
result = ~result;
result = result + !(x + 1) //排除-1特殊
return !result;
}

最大值是开头为0,后面全为1;

返回的需要是0,1,那应该想到用逻辑非 !,当为最大时是0,其他情况当然就为非0,倒置一下就变为输出1时最大,其他情况为0;

各个非0数 !都为0,所以需要位 ~ ,所以要使得0111…. 变为全0;

又因为溢出的效应,-1也有加1再加自己为全1的特点,所以来排除这种情况;

4.allOddBits

返回1,当所有奇数位为1时,否则返回0;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* 
* 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 + (0xAA<<8);
mask = mask + (mask<<16); //32位10101010....
result = (x&mask)^mask;
return !result;
}

根据例子可以知道,位数从0开始计算,而不是1;

要使其奇数位全为1时(偶数位无关,则运算时把偶数全看为0),结果生成1,不难想到掩码;

最后用异或转换;

5.negate

返回x的负值;

代码:

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

取反加1求补码的补码;

6.isAsciiDigit

返回1,如果输入的时ascii中 ‘0’ ~ ‘9’ 的字符,否则返回0;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* 
* 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) {
int mini = 0x80<<24;
int min = (~0x2F + x) & mini; //正确时,min是0000...
int max = (~0x39 + x) & mini; //正确时,max是1000...
int result = (min^max)>>31;
result = ~result + 1;
return result;
}

用位级运算判断x是否在这个范围内;

已知~x + y(y小于等于x),该值为负数,而y大于x时,这个值是非负数;

因此约定两个范围数:0x2F,0x39;

结果如果在区间内,则两个算出的值必为 正数 与 负数,则返回值用符号位来计算,需要用到最小补码来&运算得到符号位;

注意移位是符号移位,最后得变一下;

7.conditional

实现三目运算 x ? y : z

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 
* 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;
x = ~x + 1;
int result = (~x&y)|(x&z)
return result;
}

最后的结果是y,z中的值,根据前面题里的规律,可以想象让其中一个与全0&,另一个与全1&,再或,可以得到其中之一的结果,这个全0与全1就由x实现;

8.isLessOrEqual

如果x <= y 则返回1,否则返回0;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 
* 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 r1 = (x>>31) & ~(y>>31); //是否x是负数,y是正数
r1 = !!r1;
int r2 = !((x>>31)^(y>>31)); //是否x,y同号
int r3 = (!(y + ~x + 1))>>31; //y-x的符号
r3 = !!r3;
int result = r1|(r2&r3);
return result;
}

比大小,先看符号,符号相同比差与0的大小;

9.logicalNeg

实现逻辑非且不使用 ! ;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 
* 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 result = ((~x+1)>>31)^(x>>31);
result = ~result + 1;
return result;
}

0的补码是自己,正数的补码是负数,负数的补码是正数;

根据符号的不同,用异或来判断是否不同,同为0(此时全为0),不同为非0(此时全为1);

再求补码一次让符号填充从0变0或者从-1变1;

10.howManyBits

求一个数用补码表示最少需要多少位;

代码:

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
/* 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) {
x = x^(x>>31); //得到负数补码,正数不变
int A,B,C,D,E,F;
A = !!(x>>16)<<4; //看最高16位有无1,如果有,A为16;
x = x>>A; //如果上面成立,则消掉后面16位(后面的数字不讨论,折半讨论有1的区域)
B = !!(x>>8)<<3; //看原数据最高8位有无1,有则B为8;
x = x>>B; //成立则消掉后面8位(相当于总地消掉原数据后面24位)
C = !!(x>>4)<<2; //同理,直到遍历到1的准确位置,之后把记的数字加起来,就是需要表示的最小数量
x = x>>C;
D = !!(x>>2)<<1;
x = x>>D;
E = !!(x>>1);
x = x>>E;
F = x;
int result = A + B + C + D + E + F + 1; //+1表示符号位
return result;
}

这道题就是需要找这个数字二进制补码最高位为1的位数;

所以我们可以用位运算来模拟遍历,找最高位的1,可以一半一半地来找;

这里要思考正负数的问题:正就是最高位为1,但负数时最高位为0,统一一下,就设置个判断正负,让其全为找1;

11.floatScale2

求2乘一个浮点数;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/* 
* 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) {
int exp = (uf&0x7F800000)>>23; //求指数
if(exp==0) //无穷小和0的情况,返回乘2的值
return uf<<1|(!!(uf>>31));
if(exp==255) //无穷大和NaN的情况,返回原值(它们乘2等于本身)
return uf;
exp = exp + 1;
if(exp==255) //剩下的情况则是如果指数+1为255,那就返回原符号无穷大,否则返回指数加1后的原符号数
return 0x7F800000|(!!(uf>>31));
else
result = (exp<<23)|(uf&0x807FFFFF);
return result;
}

浮点数这块可以用if while这种判断了,那可以就可以更直接地区分无穷,NaN,0的情况;

无穷大和NaN的指数都是255,而无穷小和0的指数都是0;

12.floatFloat2Int(f)

将浮点数转换为整数;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 
* 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 exp = (uf&0x7F800000)>>23;
int frac = (uf&0x007FFFFF)|0x00800000; //或上0x00800000是为了加上省掉的1
if((exp<127)||(!uf)) return 0; //特殊情况
if(exp>158) return 0x80000000u; //指数大于31+127
if(exp>150) frac <<= (exp-150); //把小数转换成整数
else frac >>= (150-exp);
if(!((frac>>31)^(uf>>31))) return frac; //符号相同
else if(frac>>31) return 0x80000000u; //原符号为正溢出
else return ~frac+1; //原负数情况
}

先想想溢出的情况:指数大于了31+127,或者原来的符号是正,变换后变成了负;

另一种特殊情况是:原来的数字是小数(也就是指数小于0+127)或者0,则直接返回0;

原来的符号是负,变换后为正,就求一次补码;

其他的就是正常返回变换过后的值就行了;

13.floatPower2(x)

求浮点2的x次幂;

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* 
* 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(exp<=0) return 0;
if(exp>=255) return 0xFF<<23;
return exp<<23;
}

2的几次幂只需要在指数上加几次就行了,且题目上说太小就返回0,太大就返回INF;

那么意思就为无穷大和NaN返回INF,无穷小返回0;

总结

实验题是学习内容的一个运用总和,所以必须要自己动手做,做不来的时候就回头去翻书,看看哪里没有理解到位;

这也反着衬托出学习的效率,所以应该思考如何去提高看书吸收的内容?是否可以看慢一点,学地更好一点呢?

需要归纳这些解题的思路,当以后遇到这类数字问题的时候,可以很快地反应过来进行解决;