两整数求和,认识js的位运算

84 阅读3分钟

这是一道LeetCode的medium难度的算法题:

给你两个整数 `a` 和 `b` ,不使用运算符 `+` 和 `-`,计算并返回两整数之和:
例如:输入a=2, b=3;
输出:5
数学思路

从数学的角度思考,不允许直接使用加减运算,那么可以通过指数相乘,再求对数的方式

loge(eaeb)=>a+blog_e(e^a * e^b) => a+b
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 & 20001 & 00100000 -> 0
|OR/或(对应二进制码1-1,1-0为1,其余均为0)1 | 10001 | 00010001 -> 1
~取反~ 1~ 00011110 -> -2
^异或(对应二进制码只有1-0为1,其余均为0)1 ^ 10001 ^ 00010000 -> 0
<<左移1 << 10001 << 10010 -> 2
>>右移1 >> 10001 >> 10000 -> 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,则得到结果,演示如下:

无标题-2022-04-08-1552.png

结合上面运算过程,算法的位运算解法为:
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. 取整
    因为按位操作只支持整型,所以小数点部分全部都被抛弃.
    1.2 | 0 = 1;
    -2.3 | 0 = -2;