不用加号实现两整数相加

1.减法实现

int addWithoutPlusSign(int a, int b)
{
    return a - (-b);
}

2.异或实现

对于二进制的加法运算,若不考虑进位,则1+1=0,1+0=1,0+1=1,0+0=0,通过对比异或,不难发现,此方法与异或运算类似。因而排出进位,加法可用异或来实现。然后考虑进位,0+0进位为0,1+0进位为0,0+1进位为0,1+1进位为1,该操作与位运算的&操作相似。

那么加法运算可以这样实现:
(1)先不考虑进位,按位计算各位累加(用异或实现),得到值a;
(2)然后再考虑进位,并将进位的值左移,得值b,若b为0,则a就是加法运算的结果,若b不为0,则a+b即得结果,此时可递归调用位运算函数。

具体实现:

#include <iostream>
using namespace std;

int g_fBitAdd(int a,int b)
{
        if(b==0)
            return a;
        int sum = a^b;
        int carry =(a&b)<<1;
        return g_fBitAdd(sum,carry);
}

int main()
{
        cout<<"9+1="<<g_fBitAdd(99,11)<<endl;
        cout<<"99+11="<<g_fBitAdd(99,11)<<endl;
        cout<<"-2+11="<<g_fBitAdd(-2,11)<<endl;
}

输出:

9+1=110
99+11=110
-2+11=9

计算机本质是二进制运算,许多高人和天书都展示了如何用位运算来实现让人纠结却又惊奇的事情。上面用位运算实现加法,是基于如下定理实现:
定理1:设a,b为两个二进制数,则a+b = a^b + (a&b)<<1。
**证明:**a^b是不考虑进位时加法结果。当二进制位同时为1时,才有进位,因此 (a&b)<<1是进位产生的值,称为进位补偿。将两者相加便是完整加法结果。

定理2:使用定理1可以实现只用位运算进行加法运算。
证明:利用定理1中的等式不停对自身进行迭代。每迭代一次,进位补偿右边就多一位0,因此最多需要迭代二进制数位长减一次,进位补偿就变为0,这时运算结束。

3.内嵌汇编

C/C++函数返回值是通过寄存器eax返回,所以通过内联汇编指令的方式可以实现两数相加。注意GNU C++内联汇编语法使用AT&T/UNIX语法,和Visual C++的Intel内联汇编语法不同。具体实现如下:

#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<<"99+1="<<asmAdd(99,11)<<endl;
    cout<<"99+11="<<asmAdd(99,11)<<endl;
    cout<<"-2+11="<<asmAdd(-2,11)<<endl;
}

使用g++ test.cpp编译运行结果如下:

99+11=110
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


参考文献

[1]不用算术运算符实现两个数的加法(按位异或).CSDN
[2]不用算术运算符实现两个数的加法(按位异或).博客园
[3]GCC-Inline-Assembly-HOWTO

猜你喜欢

转载自blog.csdn.net/k346k346/article/details/80494451