1.问题描述
不用加号实现两整数相加。
2.难度等级
middle。
3.热门指数
★★★☆☆
比较热门,需要掌握。
4.减法实现
负负得正,所以可以使用减号减去目标数的相反数来实现相加。
int addWithoutPlusSign(int a, int b) {
return a - (-b);
}
5.异或实现
5.1 解题思路
对于二进制的加法运算,若不考虑进位,则 1+1=0,1+0=1,0+1=1,0+0=0,不难发现,结果等同于异或运算。因而排除进位,加法可用异或来实现。然后考虑进位,0+0 进位为 0,1+0 进位为 0,0+1 进位为 0,1+1 进位为1,结果等同于与运算。
设 a,b 为两个二进制数,a^b 是不考虑进位时加法结果。当二进制位同时为 1 时,才有进位,因此 (a&b)<<1 是进位产生的值,称为进位补偿。将两者相加便是完整的加法结果。
因此 a+b = a^b + (a&b)<<1。
那么加法运算可以这样实现:
(1)先不考虑进位,按位计算各位累加(用异或实现),得到值 a;
(2)然后再考虑进位,并将进位的值左移,得值 b。如果 b 不为 0 与 a 值继续相加。如果 b 为 0,则 a 就是加法运算的结果。此过程可循环或递归实现。
5.2 复杂度分析
时间复杂度:O(1),最差情况下(例如 a= 0x7fffffff,b = 1 时),需循环 32 次。
空间复杂度:O(1),使用常数大小的额外空间。
5.3 实现示例
5.3.1 C++
#include <iostream>
using namespace std;
// 循环实现。
// 以循环的方式通过位运算实现两整数相加。
int bitAdd (int a, int b) {
int sum = a;
while (b!=0) {
a = sum;
sum = a^b;
b =(a&b) << 1;
}
return sum;
}
// 递归实现。
// 以递归的方式通过位运算实现两整数相加。
int bitAdd (int a, int b) {
if(b==0) {
return a;
}
int sum = a^b;
int carry =(a&b) << 1;
return bitAdd(sum, carry);
}
int main() {
cout << "9+1=" << bitAdd(9,1) << endl;
cout << "99+11=" << bitAdd(99,11) << endl;
cout << "-2+11=" << bitAdd(-2,11) << endl;
}
运行输出:
9+1=10
99+11=110
-2+11=9
6.内嵌汇编
以 GNU C++ 为例讲解。
C/C++ 函数返回值是通过寄存器 eax 返回,所以通过内联汇编指令的方式可以实现两数相加。注意 GNU C++ 内联汇编语法使用 AT&T/UNIX 语法,和 Visual C++ 的内联汇编语法不同。
#include <iostream>
using namespace std;
int asmAdd(int a,int b) {
asm
(
"movl %0,%%eax\n\t"
"movl %1,%%ecx\n\t"
"addl %%ecx,%%eax"
:
:"r"(a),"r"(b)
);
}
int main() {
cout << "9+1=" << asmAdd(9,1) << endl;
cout << "99+11=" << asmAdd(99,11) << endl;
cout << "-2+11=" << asmAdd(-2,11) << endl;
}
使用g++ test.cpp
编译运行结果如下:
9+1=10
99+11=110
-2+11=9
关于上述内联汇编代码的有如下几点解释:
(1)多行汇编指令使用 \n\t 进行换行,并使用双引号将单行指令括起来;
(2)使用双百分号引用寄存器,告诉编译器引用的是寄存器而非操作数;
(3)第一个冒号表示引用的 C++ 的变量,用于输出,因无需输出变量,所以留空;
(4)第二个冒号表示汇编代码需要读取的 C++ 的变量,"r"表示使用任意寄存器来存放变量 a 和 b 的值,多个变量使用逗号分隔。
在汇编代码中访问时,按照申请的顺序从数字 0 开始,使用 % 进行访问。比如上面代码中 %0 表示变量 a,%1 表示变量b。关于 GCC 的内联汇编语法,具体可以参见:GCC-Inline-Assembly-HOWTO。
参考文献
不用加减乘除做加法 - Leetcode CN
不用算术运算符实现两个数的加法(按位异或).CSDN
GCC-Inline-Assembly-HOWTO