ICS-LAB1-Report
Bits.c
1. bitAnd–与
题目:
只用~和|实现&
样例:
bitAnd(6, 5) = 4
可使用操作: ~ |
最大操作数限制: 8
使用操作数: 4
int bitAnd(int x, int y) {
return ~(~x | ~y); //De Morgan's laws
}
应用摩根律 ~(x | y) = ~x & ~y, 可得 x & y = (x | ~y)
2. getByte–获取字节
题目:
从x中提取字节n, n编号从0至3
样例:
getByte(0x12345678,1) = 0x56
可使用操作: ! ~ & ^ | + << >>
最大操作数限制: 6
使用操作数: 3
代码:
int getByte(int x, int n) {
return (x >> (n << 3)) & 0xff;
}
分析:
由于 1Byte = 8bits = 2^3bits, 所以 n Bytes = 2^3 * n bits
因而将n左移3位,即 n * 2^3, 再将x右移 n * 2^3 即可将所求字节放在低8位,将其与上0xff,即可取出字节。
3. logicalShift–逻辑右移
题目:
将x逻辑右移n位
样例:
logicalShift(0x87654321,4) = 0x08765432
可使用操作: ! ~ & ^ | + << >>
最大操作数限制: 20
使用操作数: 10
代码:
int logicalShift(int x, int n) {
//flag equals to: if n == 0 return 0; else return 1;
int flag = !!n;
int mask = ~(flag << (32 + (~n + 1)));
return (x >> n) & mask;
}
分析:
- 算数右移
算数右移即在右移后用原符号位数将高位补齐,保持右移后二进制数的符号保持不变。
- 逻辑右移
逻辑右移即在右移后用 0 将高位补齐,是“逻辑上”的右移。
在正常右移运算中使用的是算数右移,因而要解决的问题即对于负数如何将最高位补上0,而非符号位1。
我采取掩码的方式,先将x正常右移n位与上其高位的掩码,使其右移产生的高位变为0
- 掩码构造
掩码不能草率的构造为 ~(-1 << (32 - n)), 这种构造方式当n为0时会因-1被左移32位而导致异常,构造出来的mask仍为0
由于不能使用if,为判断n是否为0,我才用了一个flag = !n + ~0, 其有很好的性质。当n为0时,flag也为0,而当n不为零时,flag统一为-1,这样使用flag代替原先的-1, 从而避免上述情况。
这样我们可以使用 mask = ~(flag << (32 + (~n + 1))),来构造掩码,当n为0时,flag为0,从而mask = -1,避免上述错误。
4. bitCount–比特计数
题目:
返回二进制数中1的个数
样例:
bitCount(5) = 2, bitCount(7) = 3
可使用操作: ! ~ & ^ | + << >>
最大操作数限制: 40
使用操作数: 36
代码:
int bitCount(int x) {
int tmp, l1, l2, l4, l8, l16; //tmp is used to save ops
tmp = (0x55 << 8) + 0x55;
l1 = (tmp << 16) + tmp; //0x55555555
tmp = (0x33 << 8) + 0x33;
l2 = (tmp << 16) + tmp; //0x33333333
tmp = (0x0f << 8) + 0x0f;
l4 = (tmp << 16) + tmp; //0x0f0f0f0f
l8 = (0xff << 16) + 0xff; //0x00ff00ff
l16 = (0xff << 8) + 0xff; //0x0000ffff
x = (x & l1) + ((x >> 1) & l1);
x = (x & l2) + ((x >> 2) & l2);
x = (x & l4) + ((x >> 4) & l4);
x = (x & l8) + ((x >> 8) & l8);
x = (x & l16) + ((x >> 16) & l16);
return x;
}
分析:
- 分治思想
本题使用了一个简单的分治思想,对于一个二进制数,要对其中为1的位做计数, 对于1位二进制数来说,1的个数无非就是其本身所表示的1或0。利用这个特性,我们可以先将一个二进制数每一位独立分开为相间隔的两部分, 其每位表示的就是自身的二进制个数,再将两串二进制数对其相加,所得到的每两位分隔的二进制数就是表达这个位置的位为1的个数。
进一步相加为4位,8位其所代表的含义不变,最后合并至32位二进制数,其所表示的就是原二进制数中所含1的个数。
//以八位二进制数 10101110 为例//
按 1|0|1|0|1|1|1|0 分割, 为两串1|1|1|1和0|0|1|0,再将其合并,成为 01 | 01 | <