datalab作答记录
零、简要说明
此为在课程学习中布置的datalab,相对于官网提供的版本是有所修改的,因此题目和官网的版本并不是一致的。但总体上来说大同小异,毕竟重要的不是题目,而是思想。
这样的训练的主要目的是加深对系统的各种理解。因为感觉如果直接写完就扔掉不管的话,就会很快就忘记各种思路,使得训练达不到应有的效果,所以决定在CSDN上记录自己的思路,以加深印象,同时,如果可能的话,也希望能给后来人提供一些思路上的指引。
一、实验目的
通过练习,熟练位操作级别的运算与二补数的性质。
二、函数分析
1、按位与的实现
函数实现:
/* NO:1
* bitAnd - x&y using only ~ and |
* Example: bitAnd(6, 5) = 4
* Legal ops: ~ |
* Max ops: 8
* Rating: 1
*/
int bitAnd(int x, int y) {
return ~((~x) | (~y));
}
函数分析:
如图1-1所示,与门可以拆分为非门和或门的组合:
而按位运算无非是分别对每一位进行运算,因此也可以像图1-1一样进行分解,分解为三个按位非和一个按位或
2、按位NOR的实现
函数实现:
/* NO:2
* bitNor - ~(x|y) using only ~ and &
* Example: bitNor(0x6, 0x5) = 0xFFFFFFF8
* Legal ops: ~ &
* Max ops: 8
* Rating: 1
*/
int bitNor(int x, int y) {
return ~~((~x) & (~y));
}
函数分析:
如图2-1所示,或门可以拆分成非门和与门的组合:
与1一样,按位或可以分解为三个按位非和一个按位与
3、复制最低位的bit
函数实现:
/* NO:3
* copyLSB - set all bits of result to least significant bit of x
* Example: copyLSB(5) = 0xFFFFFFFF, copyLSB(6) = 0x00000000
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 5
* Rating: 2
*/
int copyLSB(int x) {
return x << 31 >> 31;
}
函数分析:
int型变量的移位是算术移位,而算术右移的特性是:在空出的高位补上原有的最高位的值。因此,只要将最低位移动到最高位,再将其右移,移回到最低位即可
4、偶数bit为1的整数
函数实现:
/* NO:4
* evenBits - return word with all even-numbered bits set to 1
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 8
* Rating: 2
*/
int evenBits(void) {
int a = 0x55;
return a + (a << 8) + (a << 16) + (a << 24);
}
函数分析:
最低位为第零位,也是偶数,因此此数满足:01010101…0101的特性。由于每八位都是相等的,并且允许定义0x00-0xff范围内的常数,因此,定义一个常数a=0x55( 0101010 1 2 01010101_2 010101012),并分别将其左移8位、16位、24位,以完成拼接。
5、逻辑移位
函数实现:
/* NO:5
* logicalShift - shift x to the right by n, using a logical shift
* Can assume that 1 <= n <= 31
* Examples: logicalShift(0x87654321,4) = 0x08765432
* Legal ops: ~ & ^ | + << >>
* Max ops: 16
* Rating: 3
*/
int logicalShift(int x, int n) {
int firstStep = ((x >> 1) & ~(1 << 31));
return firstStep >> (n + (~1 + 1));
}
函数分析:
逻辑移位,关键在于高位补零。因此,如果要将算术移位换为逻辑移位,只需要保证最高位为0即可。
在此处采用的方法是:先右移一位,强行将补上的最高位设置为0,随后再进行剩余次数的移动,这样可以保证此后的算术移位都是补0。
注:
∼
1
+
1
\sim 1+1
∼1+1表示
−
1
-1
−1,因为此处不能使用减号,故使用此形式表示相减。
6、逻辑非的实现
函数实现:
/* NO:6
* bang - Compute !x without using !
* Examples: bang(3) = 0, bang(0) = 1
* Legal ops: ~ & ^ | + << >>
* Max ops: 12
* Rating: 4
*/
int bang(int x) {
int y = ~(~x + 1) & (~x);
int z = y >> 31;
return (~z) + 1;
}
函数分析:
此函数的主要目的是将非零的数映射到0,将0映射到1。因此从0的独特之处开始思考:只有0和TMin具有
∼
x
+
1
=
x
\sim x+1=x
∼x+1=x的特性,这个特性会非常地有用。
分别列出
x
=
0
x=0
x=0、
x
=
T
M
i
n
x=TMin
x=TMin以及
x
x
x为其他数时、
∼
x
+
1
\sim x+1
∼x+1与
x
x
x的形式:
value | x x x | ∼ x + 1 \sim x+1 ∼x+1 |
---|---|---|
0 | 00…00 | 00…00 |
TMin | 10…00 | 10…00 |
others_pos | 0*…** | 1*…** |
others_neg | 1*…** | 0*…** |
现在的目标是分离出0这个情形——让它与其他三个情形独立开来。最直接的方法就是右移:将所有位全部变为最高位。
如果使用
x
&
(
∼
x
+
1
)
x\&(\sim x+1)
x&(∼x+1),则会使得只有
x
=
T
M
i
n
x=TMin
x=TMin对应的运算结果为11…11,其余的都是00…00。而如果两端取反,使用
(
∼
x
)
&
∼
(
∼
x
+
1
)
(\sim x)\&\sim(\sim x+1)
(∼x)&∼(∼x+1),则会使得只有
x
=
0
x=0
x=0对应的运算结果为11…11(对应十进制的-1),其余的都是00…00(对应十进制的0)。这正是想要的情形。
而由于对0取逻辑非的结果为1,对非0取逻辑非的结果为0,则只需将上述对
x
=
0
x=0
x=0运算得到的结果-1取负即可(对其他情形下的运算结果0,取负后仍然为0),最后返回目标值。
7、最小bit位
函数实现:
/* NO:7
* leastBitPos - return a mask that marks the position of the
* least significant 1 bit. If x == 0, return 0
* Example: leastBitPos(96) = 0x20
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 6
* Rating: 4
*/
int leastBitPos(int x) {
return x & ((~x) + 1);
}
函数分析:
设x=…0010…00,其中1后面都是0。
则~x=…1101…11,原来的1变成了0,后面全是1。
因此,~x+1,即-x,等于…1110…00,注意到从原来的1那个位置往后数,还是和之前是一样的,而往前数,则全部反了过来。
此函数的目的是要把1前面的全部数都置0,后面的保留为100…00。因此,使用
x
&
(
∼
x
+
1
)
x\&(\sim x+1)
x&(∼x+1)即可达到目的:最靠后的1保留了下来,其他位全为0。
8、返回TMax
函数实现:
/* NO:8
* TMax - return maximum two's complement integer
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 4
* Rating: 1
*/
int tmax(void) {
return (1 << 31) + (~1) + 1;
}
函数分析:
TMax= 0111...111 1 2 0111...1111_2 0111...11112,而TMax+1= 1000...000 0 2 1000...0000_2 1000...00002,因此,只要轻松地得到后者,再减去1就可以得到前者。
9、返回-x
函数实现:
/* NO:9
* negate - return -x
* Example: negate(1) = -1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 5
* Rating: 2
*/
int negate(int x) {
return (~x) + 1;
}
函数分析:
根据关系 − x = ∼ x + 1 -x=\sim x+1 −x=∼x+1即可。
10、x是否是正数
函数实现:
/* NO:10
* isPositive - return 1 if x > 0, return 0 otherwise
* Example: isPositive(-1) = 0.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 8
* Rating: 3
*/
int isPositive(int x) {
int nonNeg = ((x >> 31) + 1);
int zero = (((x + (~1) + 1) >> 31) + 1);
return nonNeg & zero;
}
函数分析:
整体分为两个部分。
第一部分:输入的x是否非负。如果非负,则x>>31必然为0,即:置nonNeg=1。而如果为负,则x>>31必然为-1,即:置nonNeg=0.
第二部分:输入的x与零的关系(此处命名有歧义)。因为要从第一部分中判定为1的集合里,将0抹去,所以此处应该满足:对于输入0,返回0,对于输入正数,返回1。容易注意到,在大于等于0的数中,只有0减去1后会变成负数,因此,对((x-1)>>31)+1进行判定即可(与上述判定类似,只不过这里的x变成了x-1)。
最后,对第一部分(
x
≥
0
x\ge0
x≥0)和第二部分(
x
≠
0
x\neq 0
x=0)进行与运算即可。
11、x是否是非负数
函数实现:
/* NO:11
* isNonNegative - return 1 if x >= 0, return 0 otherwise
* Example: isNonNegative(-1) = 0. isNonNegative(0) = 1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 6
* Rating: 3
*/
int isNonNegative(int x) {
return (x >> 31) + 1;
}
函数分析:
此函数实现的功能完全与上个函数的第一部分一致,因此可以直接照搬。
12、使用一次加法,求三个数的和
函数实现:
/* NO:12
* sum3 - x+y+z using only a single '+'
* Example: sum3(3, 4, 5) = 12
* Legal ops: ! ~ & ^ | << >>
* Max ops: 16
* Rating: 3
*/
/* A helper routine to perform the addition. Don't change this code */
static int sum(int x, int y) {
return x+y;
}
int sum3(int x, int y, int z) {
int word1 = 0;
int word2 = 0;
/**************************************************************
Fill in code below that computes values for word1 and word2
without using any '+' operations
***************************************************************/
word1 = x ^ y ^ z;
word2 = ((x & y) | (y & z) | (z & x)) << 1;
/**************************************************************
Don't change anything below here
***************************************************************/
return sum(word1,word2);
}
函数分析:
首先,对于两个数的加法,a+b:
在不考虑进位的情况下,a+b的每一位应该满足a^b,即:
^ | 0 | 1 |
---|---|---|
0 | 0 | 1 |
1 | 1 | 0 |
现在考虑进位:如果a和b上的某一位都为1,则进一位,进位自然是给左边的那一位加1。由此描述,进位是由(a&b)<<1来表示的。即:a+b=a^b+(a&b)<<1
而对于三个数的加法,a+b+c:
不考虑进位时,容易看出类似于a+b地,a+b+c的每一位应该满足a^b^c。而对于进位,即便a、b、c上的某一位都为1,也只需要给左边进1。因此,不必考虑更复杂的情形,只要a、b、c中至少有两个都为1时,就可以产生进位,且两个为1和三个为1带来的进位都是一样的。
综上所述:a+b+c=a^b^c+((a&b)|(b&c)|(c&a))<<1
13、判断是否发生溢出
函数实现:
/* NO:13
* addOK - Determine if can compute x+y without overflow
* Example: addOK(0x80000000,0x80000000) = 0,
* addOK(0x80000000,0x70000000) = 1,
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 20
* Rating: 3
*/
int addOK(int x, int y) {
int signIdentical = !((x >> 31) ^ (y >> 31));
int signChange = !((((x + y) >> 31) ^ (x >> 31)) + 1);
return !(signIdentical & signChange);
}
函数分析:
int型整数加法溢出时,必然满足:
1、两个加数同号,即符号位相同
2、加法的和与加数异号,即符号位相反
因此,首先使用异或,对两个加数的符号位进行了比较:让符号位填满全部32个bit,如果同号,则异或运算结果为0。反之,异号时,结果为-1。最后取逻辑非,使得同号结果为1,异号结果为0。
随后使用了同样的方法,判断两数的和是否与加数异号,注意在最后取逻辑非之前,使用了+1,使得运算结果变成:同号为0,异号为1。
对上述两个判定结果进行与运算,即可得到是否溢出的判断,最后根据函数的功能要求,再取一次逻辑非,使得溢出时返回0。
14、求绝对值
函数实现:
/* NO:14
* logicalAbs - absolute value of x (except returns TMin for TMin)
* Example: abs(-1) = 1.
* Legal ops: ! ~ & ^ | + << >>
* Max ops: 10
* Rating: 4
*/
int logicalAbs(int x) {
int firstStep = (x ^ (x >> 31));
int control = (~(x >> 31)) + 1;
return firstStep + control;
}
函数分析:
取绝对值时,正数应该保持不变,而负数则应该变成相应的正数。
考虑使用异或的关系,让正数运算后不变,负数运算后被取反。发现负数右移31位的结果等于-1(11…11
2
_2
2),而正数右移31位的结果等于0(00…00
2
_2
2)。对于负数而言,移位得到结果在位级别上,全为1,与自身进行按位异或运算时,恰好能够将0变1,1变0。而对于正数而言,则是0和1保持不变。在这样的运算之后,就能够保证正数不变、负数取反。
而负数取反后还要加上1才能真正等于绝对值(正数却不需要),因此要想办法构造一个“区别对待”的、要加上去的数,使得对正数的第一步运算后的结果+0,对负数的第一步运算后的结果+1。注意到正数的
∼
(
x
>
>
31
)
+
1
\sim(x>>31)+1
∼(x>>31)+1结果为0,而负数对应的结果为1。这样一来,就找到了要加上去的数。
因此,将上述两项加在一起返回即可。
15、判断非0
函数实现:
/* NO:15
* isNonZero - Check whether x is nonzero using
* the legal operators except !
* Examples: isNonZero(3) = 1, isNonZero(0) = 0
* Legal ops: ~ & ^ | + << >>
* Max ops: 12
* Rating: 4
*/
int isNonZero(int x) {
int y = ~(~x + 1) & (~x);
int z = y >> 31;
return z + 1;
}
函数分析:
此函数的要求,实际上与第6个函数“逻辑非的实现”基本一致,都是区分0和非0的功能。
只不过在此处,要把结果的0与1反过来,之前是将(-1,0)变成(1,0),那么现在只要将(-1,0)变成(0,1)就行了。因此,使用+1的手段即可。