位运算实现加减乘除

加法

思路

思路很简单,首先假设两个数相加,所有位都没进位,那么结果就是所有位直接对应相加即可,比如123+234=357,而如果有一些位有进位的清空,比如456+789,我们先不考虑进位,每一位单独算,得到的结果设为a,这里a=135,再考虑进位,这里每一位都进位了,分别进了十位,百位和千位,所以进位的结果是1110,那很显然,我们可以将456+789的答案转化为不考虑进位的和加上进位的值,在这里也就是135+1110=1245,如果这个加法也出现了进位,那么很明显就采用递归/迭代的思路继续操作下去,直到两个数的加法不出现进位,即为答案。

上面是10进制的情况,2进制的道理是一样的,而且因为2进制我们可以采用位运算来快速计算上面的不进位加法(位异或)和进位和(位与再左移1位),所以就可以采用位运算的方法来计算两个数的和。

代码

递归版:

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

非递归版:

int add(int a,int b){
    while(b!=0){
        int c=a^b;
        b=(a&b)<<1;
        a=c;
    }
    return a;
}

减法

思路

减去一个数,就等于加上一个的相反数,所以我们考虑相反数的二进制表示,总所周知,计算机是使用补码来表示一个数,比如我们分别输出-5和5的二进制位如下。

void print(int a){
    for(int i=31;i>=0;i--){
        printf("%d",(a>>i)&1);
    }
    printf("\n");
}
//111111111111111111111111111111011
//000000000000000000000000000000101

所以相反数的二进制表示就是按位取反再加1,而不用考虑正数还是负数的问题。

代码

int neg(int a){
    return add(~a,1)
}
int sub(int a,int b){
    return add(a,neg(b));
}

乘法

思路

乘法的思路是二进制数列竖式计算的思路,首先单独处理符号位(a^b可以判断符号位),用绝对值来进行计算,对于被乘数乘以乘数,我们从低到高遍历乘数,当乘数当前位为1,答案应该加上被乘数乘以乘数当前位的权重,所以这里可以用一个简单的优化,因为随着乘数的遍历,权重一直都是左移一位的关系(1–>10–>100->…),所以直接让被乘数左移即可,而另外为了方便计算乘数的当前位,我们可以直接让乘数右移,把当前位固定在最后一位,这样通过(b&1)就能快速得到这一位的值。

代码

int mul(int a,int b){
    // 取绝对值
    int aa=a<0?neg(a):a;
    int bb=b<0?neg(b):b;
    int ans=0;
    while(bb){
        // 如果被乘数当前(最后一位)为1,答案加上被乘数
        if((bb&1)==1){
            ans=add(ans,aa);
        }
        // 被乘数左移一位,被乘数左移是因为对应的权重不同
        aa<<=1;
        // 乘数右移一位,乘数右移是为了方便定位当前计算的位(一直是最后一位)
        bb>>=1;
    }
    // 判断符号位
    if((a^b)<0){
        ans=neg(ans);
    }
    return ans;
}

除法

思路

除法是一个贪心的做法,从高位到低位(1«31,1«30,…)依次试除。

代码

int div2(int a,int b){
    int aa=a<0?neg(a):a;
    int bb=b<0?neg(b):b;
    int ans=0;
    for(int i=31;i>=0;i--){
        if((aa>>i)>=bb){
            ans=add(ans,1<<i);
            aa=sub(aa,bb<<i);
        }
    }
    if((a^b)<0){
        ans=neg(ans);
    }
    return ans;
}

完整代码

以上加减乘除四种做法其实都没有考虑到溢出的情况,又不是做数电作业题如果遇到这么刁难的面试题就拒了吧

#include <bits/stdc++.h>
using namespace std;
void print(int a){
    for(int i=31;i>=0;i--){
        printf("%d",(a>>i)&1);
    }
    printf("\n");
}
int add(int a,int b){
    if(b==0){
        return a;
    }
    return add(a^b,(a&b)<<1);
}
int add2(int a,int b){
    while(b!=0){
        int c=a^b;
        b=(a&b)<<1;
        a=c;
    }
    return a;
}
int neg(int a){
    return add(~a,1);
}
int sub(int a,int b){
    return add(a,neg(b));
}
int mul(int a,int b){
    // 取绝对值
    int aa=a<0?neg(a):a;
    int bb=b<0?neg(b):b;
    int ans=0;
    while(bb){
        // 如果被乘数当前(最后一位)为1,答案加上被乘数
        if((bb&1)==1){
            ans=add(ans,aa);
        }
        // 被乘数左移一位 被乘数左移是因为对应的权重不同
        aa<<=1;
        // 乘数右移一位 乘数右移是为了方便定位当前计算的位(一直是最后一位)
        bb>>=1;
    }
    // 判断符号位
    if((a^b)<0){
        ans=neg(ans);
    }
    return ans;
}
int div2(int a,int b){
    int aa=a<0?neg(a):a;
    int bb=b<0?neg(b):b;
    int ans=0;
    for(int i=31;i>=0;i--){
        if((aa>>i)>=bb){
            ans=add(ans,1<<i);
            aa=sub(aa,bb<<i);
        }
    }
    if((a^b)<0){
        ans=neg(ans);
    }
    return ans;
}
int main(){
    printf("%d\n",add(13,58));
    printf("%d\n",add2(13,58));
    printf("%d\n",sub(13,58));
    printf("%d\n",sub(58,13));
    printf("%d\n",mul(58,13));
    printf("%d\n",mul(-58,13));
    printf("%d\n",mul(-58,-13));
    printf("%d\n",div2(58,13));
    printf("%d\n",div2(-58,-13));
    printf("%d\n",div2(-58,13));
    printf("%d\n",div2(13,58));
    return 0;
}