这是一道LeetCode的medium难度的算法题:
给你两个整数 `a` 和 `b` ,不使用运算符 `+` 和 `-`,计算并返回两整数之和:
例如:输入a=2, b=3;
输出:5
数学思路
从数学的角度思考,不允许直接使用加减运算,那么可以通过指数相乘,再求对数的方式
var getSum = function (a, b) {
if (a == 0) return b;
if (b == 0) return a;
return Math.log(Math.exp(a) * Math.exp(b));
};
除了用数学的角度,还有什么方法能解吗,毫无疑问是有的,接下来让我们来看看Js中的位运算
js中的位运算
运算符 | 描述 | 例子 | 二进制[原码] | 结果->十进制数[补码] |
---|---|---|---|---|
& | AND/与(对应二进制码只有1-1为1,其余均为0) | 1 & 2 | 0001 & 0010 | 0000 -> 0 |
| | OR/或(对应二进制码1-1,1-0为1,其余均为0) | 1 | 1 | 0001 | 0001 | 0001 -> 1 |
~ | 取反 | ~ 1 | ~ 0001 | 1110 -> -2 |
^ | 异或(对应二进制码只有1-0为1,其余均为0) | 1 ^ 1 | 0001 ^ 0001 | 0000 -> 0 |
<< | 左移 | 1 << 1 | 0001 << 1 | 0010 -> 2 |
>> | 右移 | 1 >> 1 | 0001 >> 1 | 0000 -> 0 |
对于~1为什么是-2,这里给不懂的同学补充下计算机二进制编码关于原码,补码和反码相关概念。
[原码:自然正数(包括0)的二进制编码,正数在计算机中也用补码存储,因为正数的原码=对应相反数(即负数)的补码
反码:求补码的过程中,反码=原码逐位取反,计算机并不存储
补码:计算机求相反数的编码,补码=反码+1,负数使用补码存储]
比如1对应二进制编码为[0000 0001]
~1取反后的原码为[1111 1110],由首位符号位可知,对应的是负数
先求反码:[1111 1110] -> [1000 0001] 符号位不变
反码+1拿到补码:[1000 0001] +1 -> [1000 0010] = -2
由此得到~1 最后得到的值是-2
至此,我们回到算法题,不通过+-符号求和,如何通过二进制位操作呢?以a =2, b=3,求a+b为例
a=2 -> 0010 (简单点就用4位表示)
b=3 -> 0011
从位运算的处理来看,&位运算符只有两个数的二进制码均为1才为1,其余均为0,而^位运算符只有二进制码为1-0才为1,其余均为0;由此我们可以把&操作符处理进位,即存在两位数的二进制码对应的某位均为1,则该位保留为1,将结果往左移一位,即进位+1;把^操作符当做求和处理,即进位后的二进制码与b进行^运算,当进位为0,则得到结果,演示如下:
结合上面运算过程,算法的位运算解法为:
var getSum = function (a, b) {
if (a == 0) return b;
return getSum((a & b) << 1, a ^ b);
};
扩展
1. 位运算在日常开发中确实是非常少用,当我们需要找出number数组中不一样的数时,使用^位运算符更加简洁高效,不过记得留好注释,因为可读性一般。
const list = [1, 2, 2, 3, 3]
const specNum = list.reduce((total, item) => {
total = total ^ item;
return total;
}, 0)
console.log(specNum) // 1
- 取整
因为按位操作只支持整型,所以小数点部分全部都被抛弃.
1.2 | 0 = 1;
-2.3 | 0 = -2;