A+B问题及扩展

描述

给出两个整数ab,求它们的和

样例

如果a=1并且b=2,返回3.

挑战

你可以直接使用return a+b,但是不使用+运算符

位运算知识扩展

& 按位与

二进制00000110(6),00001011(11) 按位与运算:

	00000110
&	00001011
	00000010

总结:同为1则为1,否则为0

| 按位或

按位或运算:

	00000110
|	00001011
	00001111

总结:只要有一个为1则为1,否则为0

^ 按位异或

按位异或运算:

	00000110
^	00001011
	00001101

总结:不相同则为1,否则为0

扫描二维码关注公众号,回复: 3953381 查看本文章

~ 按位取反

按位取反运算:

	00000110
~	
	11111001

总结:01,10

<< 左移

左边消失,右边补0

>> 右移

右边消失,左边补符号位

位运算加法

十进制中:

13 + 9

操作步骤:

  1. 不考虑进位,sum = 13 + 9 = 12;
  2. 只考虑进位,carry= 13 + 9 = 10;
  3. 如果carry != 0,使用sumcarry重复1,2步,直至carry = 0为止,则结果为sum = 22;
    步骤:
1. sum = 13 + 9 = 12;
2. carry = 13 + 9 = 10;
3. carry != 0;
4. sum = 12 + 10 = 22;
5. carry = 12 + 10 = 0;
6. carry = 0, sum = 22;

二进制中:

13: 00001101    9: 00001001
1. sum = 00001101 + 00001001 = 00000100;
2. carry = 00001101 + 00001001 = 00010010;
3. carry != 0;
4. sum = 00000100 + 00010010 = 00010110;
5. carry = 00000100 + 00010010 = 00000000;
6. carry = 0, sum = 00010110 = 22;

第一步操作就是按位异或^操作;
第二步操作是按位与&操作,再左移<< 1位.

所以,操作就可以使用递归或者迭代方式实现
递归代码:

int add(int num1, int num2) {
	if (num2 == 0) {
		return num1;
	}

	int sum = num1 ^ num2;
	int carry = (num1 & num2) << 1;
	return add(sum, carry);
}

迭代代码:

int add(int num1, int num2) {
	int sum = num1 ^ num2;
	int carry = (num1 & num2) << 1;
	while (carry != 0) {
		int a = sum;
		int b = carry;
		sum = a ^ b;
		carry = (a & b) << 1;
	}
	return sum;
}

扩展减乘除

减法

13 - 9 可以转换为 13 + (-9),然后使用前面的加法方法实现减法
在计算机中,负数是以补码形式存在的:

-9		原码: 00001001
		反码: 11110110
		补码: 11110110 + 1 = 11110111

所以-9在计算机中的表现形式是:11110111.
~00001001 + 1就可以得到-9的表现形式.
总结:取反加一
所以:

int subtract(int num1, int num2) {
	int sub = add(~num2, 1);
	return add(num1, sub);
}

乘法

13 * 9 转换为加法就是913相加,符号最后判断,同号为正,异号为负.
所以:

int multiply(int a, int b) {
	//取绝对值
	int absa = a < 0 ? add(~a, 1) : a;
	int absb = b < 0 ? add(~b, 1) : b; //如果为正 则直接取b,否则取反加一取补码
	int sum = 0;        							//叠加值
	int count = 0;								//叠加次数
	while (count < absb) {					//叠加次数始终小于b
		sum = add(sum, absa);			//叠加依次
		count = add(count, 1);				//次数加一
	}
	//判断正负号, 按位异或,最高位为1则为负数,最高位为0则为正数
	if ((a ^ b) < 0) {
		sum = add(~sum, 1);
	}
	return sum;
}

上面的方法,如果乘数很大,即9很大时,需要叠加的次数也就很多,运算起来就会很大,效率不高
可以参考十进制的乘法:

			1	3						
*			1	4
			5	2
		1	3
		1	8	2		

二进制:

							1	1	0	1
		*					1	1	1	0
							0	0	0	0
						1	1	0	1
					1	1	0	1
				1	1	0	1
			1	0	1	1	0	1	1	0 (182)

相当于b从低位开始取值,如果为1,则加入sum中,为0,则不需要相加
取b的下一位, 即右移一位,取最低位, a右移一位,相当于在十进制中乘以10,重复上面.
直到最后b右移为0时,结束,判断符号
所以优化结果为:

int multiply(int a, int b) {
	//取绝对值
	int absa = a < 0 ? add(~a, 1) : a;
	int absb = b < 0 ? add(~b, 1) : b;
	int sum = 0;
	while (absb > 0) {
		if ((absb & 0x1) > 0) {			//每次考察最后一位, 为 1 加, 为 0 跳过
			sum = add(sum, absa);
		}
		absa = absa << 1;				//a 左移一位, 在十进制中就是乘于10, 进一位,二进制中同理
		absb = absb >> 1;				//b 右移一位, 在十进制中就是除以10, 退一位, 二进制中同理
	}
	//判断符号
	if ((a ^ b) < 0) {
		sum = add(~sum, 1);
	}
	return sum;
}

除法

除法转换为减法, 被除数减除数,直到被除数小于除数位置,减的次数就是商,此时被除数就是余数.符号确认跟乘法一样,余数的符号和被除数一样.

int divide(int a, int b) {
	int absa = a < 0 ? add(~a, 1) : a;
	int absb = b < 0 ? add(~b, 1) : b;
	int count = 0;		//商
	int rema = 0;		//余数
	while (absa >= absb) {			//如果被除数小于除数,结束操作
		count = add(count, 1);			//次数加一
		absa = subtract(absa, absb);	//减
	}
	//判断符号
	if ((a ^ b) < 0) {
		count = add(~count, 1);
	}
	//判断余数符号
	rema = a > 0 ? absa : add(~absa, 1);
	return count;
}

跟乘法类似 a很大 b很小 减的次数就很大,所以可以增加步数去减
在计算机中,所有数都可以使用 [20,21,22,…,231] 表示,所以可以从31开始,如果可以减31倍,就把31倍的次数,依次类推

int divide(int a, int b) {
	int absa = a < 0 ? add(~a, 1) : a;
	int absb = b < 0 ? add(~b, 1) : b;
	int sum = 0;
	int rema = 0;
	for (int i = 31; i >= 0; i--) {
	//
		if ((absa >> i) >= absb) {
			sum = add(sum, 1 << i);
			absa = subtract(absa, absb << i);
		}
	}
	if ((a ^ b) < 0) {
		sum = add(~sum, 1);
	}
	rema = a > 0 ? absa : add(~absa, 1); 
}

结束

猜你喜欢

转载自blog.csdn.net/weixin_42983482/article/details/82772774
今日推荐