datalab 解题思路
本篇文章并不会花太长时间,因为解题思路都写在代码注释中了(写代码的时候用注释描述
整体方向和关键步骤实在是个好习惯)。
代码中的注释都是用蹩脚的英文写就的,虽然说不能保证没有语法问题,但是一般不会太影响理
解。
一共15道题目,12道关于 int 类型数据操作,3道关于 single float 类型操作,难的容易的都
有,分布还算合理。基本上代码中的注释就够解释清楚了,不过对于 bitCount 这道题
(说好的只是二进制操作谜题,怎么二分法都乱入了?)会详细解释。
- bitAnd
- getByte
- logicalShift
- bitCount
- bang
- tmin
- fitsBits
- divpwr2
- negate
- isPositive
- isLessOrEqual
- ilog2
- float_neg
- float_i2f
- float_twice
bitAnd
1
2
3
4
5
6
7
8
9
10
11
|
/*
* bitAnd - x&y using only ~ and |
* Example: bitAnd(6, 5) = 4
* Legal ops: ~ |
* Max ops: 8
* Rating: 1
*/
int bitAnd(int x, int y) {
/* ~(~ x | ~y) = x & y */
return ~ (~ x | ~ y);
}
|
getByte
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/*
* getByte - Extract byte n from word x
* Bytes numbered from 0 (LSB) to 3 (MSB)
* Examples: getByte(0x12345678,1) = 0x56
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 6
* Rating: 2
*/
int getByte(int x, int n) {
/* Left shift, right shift and mask */
int shift = n << 3;
int result = x >> shift;
return 0xFF & result;
}
|
logicalShift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
/*
* logicalShift - shift x to the right by n, using a logical shift
* Can assume that 0 <= n <= 31
* Examples: logicalShift(0x87654321,4) = 0x08765432
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 20
* Rating: 3
*/
int logicalShift(int x, int n) {
/* Make a mask */
int z = 1 << 31;
int filter = ~ ((z >> n) << 1);
int result = (x >> n) & filter;
return result;
}
|
bitCount
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/*
* bitCount - returns count of number of 1's in word
* Examples: bitCount(5) = 2, bitCount(7) = 3
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 40
* Rating: 4
*/
int bitCount(int x) {
/* This one is beyond, I cheat */
int temp_mask1 = 0x55 | (0x55 << 8);
int temp_mask2 = 0x33 | (0x33 << 8);
int temp_mask3 = 0x0f | (0x0f << 8);
int mask1 = temp_mask1 | (temp_mask1 << 16);
int mask2 = temp_mask2 | (temp_mask2 << 16);
int mask3 = temp_mask3 | (temp_mask3 << 16);
int mask4 = 0xff | (0xff << 16);
int mask5 = 0xff | (0xff << 8);
x = (x & mask1) + ((x >> 1) & mask1);
x = (x & mask2) + ((x >> 2) & mask2);
x = (x & mask3) + ((x >> 4) & mask3);
x = (x & mask4) + ((x >> 8) & mask4);
x = (x & mask5) + ((x >> 16) & mask5);
return x;
}
|
步骤推演
其实就是二分法(或者二分法的变种?本文中 ilog2 函数的解法也是二分法,它的解决
方法就不详细推演了)。
假设传入参数是 0xffffffff,也就是说,二进制表示全是1。
index |
0 |
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 |
bits |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
mask1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
mask-x |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
mask-x-right-shift-by-one |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
masked1+masked2 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
1 |
0 |
过筛,统计传入参数的二进制表示上,每两个位置有几个1,可能的个数:0,1,2,上面的表格来展示筛选过程
使用表格中所示的 mask 对 x 进行筛选,找出每两位低位上是否有1,有的话低位上用1表示,表示此位有1个1;
再用 mask 对右移一位的 x 进行筛选,找出原 x 上每两位高位上是否有1,有的话结果中每两位的低位上用1表示,表示此位有1个1;
将筛选后的两个结果相加,得到的结果的二进制表示如果每两位高位上是1的话,表示原 x 中该两位范围内有2个1;如果是1,则表示有1个1;如果是0,则0。
换一个二进制表示位 0x33333333 的 mask 继续过筛,即进行上次获得的结果中每四位有多少个1的计算;
以此类推,不再赘述,最后获得的结果就是 x 的二进制表示中有多少个1。
bang
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
/*
* bang - Compute !x without using !
* Examples: bang(3) = 0, bang(0) = 1
* Legal ops: ~ & ^ | + << >>
* Max ops: 12
* Rating: 4
*/
int bang(int x) {
/* When x is 0, temp1 is overflow, it's value is 0 */
int temp1 = ~ x + 1;
/* When x is 0, temp2 is 0, else its top most bit must be 1 */
int temp2 = x | temp1;
/* Negation' mainly purpose is to swtich the top most bit */
/* x = 0, temp3 = 0xffffffff; else temp3 = 0x0... */
int temp3 = ~ temp2;
/* Right shift of 31 bits */
/* x = 0, mask = 0xffffffff; else mask = 0 */
int mask = temp3 >> 31;
return mask & 1;
}
|
tmin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/*
* tmin - return minimum two's complement integer
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 4
* Rating: 1
*/
int tmin(void) {
/* A n-bit integer could make 0xffffffff binary representations
* According to the law of two's complement
* Top most bit of int type is used to define a positive or a negative
* There are 0xffffffff / 2 numbers of positive int representations
* Then the maximum positive int number is 0xffffffff / 2
* We know the total number, so we could compute the minimum negative integer
* in two's complement style
*/
int max_n_bit_range = ~ 0x0;
int mask = 0x1 << 31;
int max_post_int = max_n_bit_range ^ mask;
int min_nega_int = ~ max_post_int;
return min_nega_int;
}
|
fitsBits
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/*
* fitsBits - return 1 if x can be represented as an
* n-bit, two's complement integer.
* 1 <= n <= 32
* Examples: fitsBits(5,3) = 0, fitsBits(-4,3) = 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 15
* Rating: 2
*/
int fitsBits(int x, int n) {
/* Left shift (32 - n) bits and then right shift the same
* Make a ^ operation with the original one
* If stay the same the result would be 0, else non-zero
* Stay the same meaning that x could be represented as an n-bit
* And ! the output as a return value
*/
int neg_n = ~ n + 1;
int shift = 32 + neg_n;
int temp1 = x << shift;
int temp2 = temp1 >> shift;
int temp3 = temp2 ^ x;
int result = ! temp3;
return result;
}
|
divpwr2
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
|
/*
* divpwr2 - Compute x/(2^n), for 0 <= n <= 30
* Round toward zero
* Examples: divpwr2(15,1) = 7, divpwr2(-33,4) = -2
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 15
* Rating: 2
*/
int divpwr2(int x, int n) {
/* The actual value of 15 / 2 is 7
* The actual value of -33 / 4 is -3
* We need to add a patch with the one in the second situation
* If x is negative,
* and n is non-zero,
* and the result has difference from the dividend,
* then the patch is 1 and add it with the value.
*/
int mask1 = 0x1;
int temp1 = x >> 31;
int temp2 = x >> n;
int temp3 = temp2 << n;
int n_zero_or_not = ! n;
int post_or_not = ! (temp1 & mask1);
int same_or_not = ! (x ^ temp3);
int patch = (n_zero_or_not | post_or_not | same_or_not) ^ 0x1;
int result = temp2 + patch;
return result;
}
|
negate
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
|
/*
* negate - return -x
* Example: negate(1) = -1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 5
* Rating: 2
*/
int negate(int x) {
/* According to the law of two's complement
* The two's complement representation of n-bit integer x is
* 2^n + x
* which is equal to (2^n - 1) + x + 1
* thus the binary representation is
* 0xfff...(n-bit) + x + 1
* when the x is negative a more friendly form is
* ~(-x) + 1
* And when to compute the value of one negative integer y
* The form would be -1 * 2^(n-1) + a_(n-2) * 2^(n-2) + ...
* Could be changed to -(0xffff(n-1 bits f) - y(n-1 bits) + 1)
* Top most bit of y could be treated as turning from 1 to 0
* So a more friendly form -(~y + 1)
* Such throry could be used to negate one of int type
*/
int result = ~ x + 1;
return result;
}
|
isPositive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
/*
* isPositive - return 1 if x > 0, return 0 otherwise
* Example: isPositive(-1) = 0.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 8
* Rating: 3
*/
int isPositive(int x) {
/* The law is that if the value is non-zero
* and positive, the output would be 1
*/
int mask = 0x1;
int temp = x >> 31;
int post_or_not = ! (temp & mask);
int zero_or_not = ! (x ^ 0x0);
int result = post_or_not & (! zero_or_not);
return result;
}
|
isLessOrEqual
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
|
/*
* 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) {
/* Three situations
* 1. equal
* 2. different symbol
* 3. there's a need to do x - y operation
*/
int mask = 0x1;
/* Determine if equal */
int equal_or_not = ! (x ^ y);
/* Determine if x and y have different symbol */
int x_symbol = (x >> 31) & mask;
int y_symbol = (y >> 31) & mask;
int diff_symbol = x_symbol ^ y_symbol;
/* If so, when x is negative, x is less than y */
int x_l_y = diff_symbol & x_symbol;
/* Operation x - y */
int nega_y = ~ y + 1;
int temp = x + nega_y;
/* Determine the symbol of difference */
int temp_nega_or_not = (temp >> 31) & mask;
/* Attention that with different symbol, the result only relies on x_l_y,
* we know that they're not equal,
* and the value of temp_nega_or_not should be screened
*/
int result = equal_or_not | x_l_y | ((! diff_symbol) & temp_nega_or_not);
return result;
}
|
ilog2
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
64
65
66
67
|
/*
* ilog2 - return floor(log base 2 of x), where x > 0
* Example: ilog2(16) = 4
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 90
* Rating: 4
*/
int ilog2(int x) {
/* Return floor, ilog2(16) = 4, ilog2(30) = 4, and x > 0
* When x is 2^(n-1), there is only ont bit 1 in the representation,
* result is the left shift of 0x1 to x
* And the result only depends on the top most bit 1
* This simplifies the question to calculate where the top most bit 1 is
* The return value would be its index
*/
/* Initial index as 0 */
int index = 0;
/* Make masks */
int temp_mask1 = 0x0f | (0x0f << 8);
int temp_mask2 = 0x33 | (0x33 << 8);
int temp_mask3 = 0x55 | (0x55 << 8);
int mask1 = 0xff | (0xff << 8);
int mask2 = 0xff | (0xff << 16);
int mask3 = temp_mask1 | (temp_mask1 << 16);
int mask4 = temp_mask2 | (temp_mask2 << 16);
int mask5 = temp_mask3 | (temp_mask3 << 16);
/* Use mask1 0x0000ffff to screen x,
* determine whether there's a difference
* between the original one and screened one
* The main purpose is to get where the top most bit 1 is,
* in the higher 16 bits section or the lower?
* Then the index equals to the start index of the section
*/
int masked_x_1 = x & mask1;
int same_or_not_1 = ! (masked_x_1 ^ x);
int section_start_1 = 16 >> (same_or_not_1 << 3);
index = index + section_start_1;
/* If not staying same after ^, remove the lower 16 bits section
* else x is kept unchaged
*/
int left_1 = (x >> index) << index;
/* Almost same procedure to deal with the left part */
int masked_x_2 = left_1 & mask2;
int same_or_not_2 = ! (masked_x_2 ^ left_1);
int section_start_2 = 8 >> (same_or_not_2 << 2);
index = index + section_start_2;
int left_2 = (left_1 >> index) << index;
int masked_x_3 = left_2 & mask3;
int same_or_not_3 = ! (masked_x_3 ^ left_2);
int section_start_3 = 4 >> (same_or_not_3 << 2);
index = index + section_start_3;
int left_3 = (left_2 >> index) << index;
int masked_x_4 = left_3 & mask4;
int same_or_not_4 = ! (masked_x_4 ^ left_3);
int section_start_4 = 2 >> (same_or_not_4 << 1);
index = index + section_start_4;
int left_4 = (left_3 >> index) << index;
int masked_x_5 = left_4 & mask5;
int same_or_not_5 = ! (masked_x_5 ^ left_4);
int section_start_5 = 1 >> (same_or_not_5 << 1);
index = index + section_start_5;
return index;
}
|
float_neg
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
|
/*
* float_neg - Return bit-level equivalent of expression -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 representations of
* single-precision floating point values.
* When argument is NaN, return argument.
* Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
* Max ops: 10
* Rating: 2
*/
unsigned float_neg(unsigned uf) {
/* Other than NaN, top most bit of argument should be inversed
* NaN reamins unchanged as a return value
*/
/* Get the part exponent */
unsigned exponent = (uf << 1) >> 24;
/* Get the part fraction */
unsigned fraction = uf << 9;
unsigned is_exponent_full_1 = ! (exponent ^ 0xff);
/* Determine if this value a NaN */
unsigned is_NaN_or_not = is_exponent_full_1 && fraction;
/* If uf a NaN, keep it unchanged, else inverse the top most bit */
unsigned result = uf + ((! is_NaN_or_not) << 31);
return result;
}
|
float_i2f
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
64
65
66
67
68
69
70
71
72
|
/*
* float_i2f - Return bit-level equivalent of expression (float) x
* Result is returned as unsigned int, but
* it is to be interpreted as the bit-level representation of a
* single-precision floating point values.
* Legal ops: Any integer/unsigned operations incl. ||, &&. also if, while
* Max ops: 30
* Rating: 4
*/
unsigned float_i2f(int x) {
/* First determine the exponent bias based on the value x
* Then keep the symbol of x, get the alsolute value of x
* Then determine the index of left most 1 of the abs x,
* that index plus bias is the exponent of the single float
* Left shifted that clears all left zero bits of abs x
* right shifted and masked that leave 9 slots for symbol and exponent,
* see that if the lost part equals or greater than 128,
* two situations: greater; equal and the last bit of shifted vaule is 1,
* both that the shifted vaule should plus 1
* Determine if there's a bit 1 in index 23,
* if so, fraction is 0 and exponent increases by 1
*/
/* Determine the bias */
unsigned exponent_bias = 0;
if(x)
{
exponent_bias = 0x7f;
}
/* Get the symbol and abs of x */
unsigned symbol = x & 0x80000000;
unsigned x_absoulte = x;
if(symbol)
{
x_absoulte = ~ x + 1;
}
/* A while loop to right shift abs x by 1 until the value is 0,
* record the shifted setps, which is index of top most bit 1
*/
int index = 0;
unsigned temp = x_absoulte;
while(temp)
{
temp = temp >> 1;
index = index + (temp && 0x1);
}
/* Calculate exponent and left shift setps that could clear left 0 bits */
unsigned exponent = index + exponent_bias;
int left_shift = 31 + (~ index + 1);
unsigned fraction = x_absoulte << left_shift;
/* Keep the lost part */
unsigned tail = fraction & 0xff;
/* Leave 9 slots for symbol and exponent */
fraction = (fraction >> 8) & 0x7fffff;
/* Determine if it's necessary to increase fraction by 1
* If greater than 0.5, increment;
* equals to 0.5 and the last bit is 1, increment, round up to even
*/
int tail_7th_bit = tail & 0x80;
int tail_left_bits = tail & 0x7f;
int tail_l_128 = tail_7th_bit && tail_left_bits;
fraction = fraction + (tail_l_128 || (tail_7th_bit && (fraction & 1)));
/* Determine if fraction has a carry after a possible increment
* if so, fraction is 0 and exponent increases by 1
*/
if(fraction >> 23)
{
fraction = 0;
exponent = exponent + 1;
}
unsigned result = symbol | (exponent << 23) | fraction;
return result;
}
|
float_twice
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
|
/*
* float_twice - 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 float_twice(unsigned uf) {
/* Four special cases: NaN, infinite, zero, non-normalized,
* return it self if uf meets first three cases,
* for non-normalized, be careful that
* it may become a normalized single float when doubled
*/
/* Get the part exponent */
unsigned exponent = (uf << 1) >> 24;
/* Get the part fraction */
unsigned fraction = uf & 0x7fffff;
unsigned exponent_full_1 = ! (exponent ^ 0xff);
/* Determine type of this value */
unsigned NaN_or_not = exponent_full_1 && fraction;
unsigned infinite_or_not = exponent_full_1 && (! fraction);
unsigned zero_or_not = ! (uf & 0x7fffffff);
unsigned not_normalized_or_not = (! exponent) && fraction;
/* If meeting the first three cases, return it self */
if(NaN_or_not || infinite_or_not || zero_or_not)
{
return uf;
}
/* If uf a non-normalized single float, check the fraction part */
if(not_normalized_or_not)
{
unsigned temp_fraction = fraction << 1;
fraction = temp_fraction & 0x7fffff;
/* That if there's a carry of 1 */
if(temp_fraction & 0x800000)
{
/* The value of non-normalized is (-1)^(s) * 0.xxx * 2^(-126),
* the exponent is always -126 implicitly;
* Now make the exponent 0x1, make it -126 explicitly
*/
return (uf & 0x80000000) | 0x00800000 | fraction;
}
/* If no carry, just change the fraction */
return (uf & 0xff800000) | fraction;
}
/* Notice that if the original exponent is 0xfe
* double f would make exponent 0xff
* with such a exponent and a non-zero fraction
* the return value is a NaN
* it's out of the single float representation range
*/
return uf + 0x00800000;
}
|