在不允许使用加减乘除运算符号的前提下,请实现加减乘除运算。
第一步:异或运算:^
异或运算其实就是无进位相加的结果,相同为0,没有进位,不同为1。
第二步:与运算:& ,然后左移一位
与运算的结果,双1才会是1,其实就是找出了所有出现进位的地方,然后左移一位,就是进位后的地方。
第三步:前两步的运算结果相加,其实等同于原来两个数相加的结果
无进位相加的结果,加上进位数,就是相加的结果。因此,如果进位结果不是0,就继续重复前两步,直到进位结果为0,无进位相加的结果就是最终结果。
代码
//用位运算实现加法
public static int add(int a,int b){
int sum = a;
//如果没有进位,说明不用再加了,就是无进位相加的结果
while (b!=0){
//异或运算,求出无进位相加的结果
sum = a^b;
//与运算后左移一位,求出进位结果
b = (a&b) << 1;
//把无进位相加结果赋值给a,继续下一轮
a=sum;
}
return sum;
}
减法其实很容易,计算机底层其实本来就只会加法,因此,减去一个数,就等于加上这个数的反码,一个数的反码就是他本身取反加1,因此,减法可以直接调用加法来实现。
代码
//用位运算实现减法
public static int minus(int a,int b){
//一个数的反码就是他本身取反加1
int fan=add(~b,1);
//减去一个数就是加上这个数的反码
return add(a,fan);
}
先来想一下,手动在演草纸上算二进制乘法的话,应该怎么算?
比如:
0111 X 1001
模拟演草纸上手动演算:
观察手动演算的过程,再结合二进制的特点,二进制只有0和1,因此,0乘以任何数都是0,1乘以任何数都是他本身。因此,图中,如果设第一个乘数为a,第二个为b,那么,如果b最右边一位是1,第一行的结果就是a照抄,如果b的最右边结果为0,那么第一行就是全0,然后,再依次看b的右边第二个数,对应乘法的第二行,a整体左移一位,然后,依次累加。
代码
//用位运算实现乘法
public static int multi(int a,int b){
//res累加和
int res=0;
//当b不为0时,也就是说还没乘完
while (b!=0){
//(b&1)取出最后一位,如果是1,就模拟a乘1,然后累加进res
if((b&1)!=0){
res=add(res,a);
}
//a左移一位,模拟乘法运算中的左移
a=a<<1;
//>>>是无符号右移,模拟乘法运算中,b的右边下一位
b=b>>>1;
}
return res;
}
还是参考手动除法的过程,我们在手动除法运算的过程中,第一步是不是在寻找被除数的最高位?
参考图中我们一般的手动除法的过程,第一步,找最高位能够除的下的,图中画红叉的地方不行,因为就算上1,也比被除数大了,因此,只能在第二个最高位上3,3是最大的,上4就比被除数大了。这就是我们一般手动除法的过程,对吧?
那么,计算机中的除法,其实也是一样的原理,首先,让除数左移,每左移一位,相当于看看更高一位能不能被除的下,直到再左移就比被除数大的位置,就是结果最高位的数,类比于上图中手动除法结果的最高位的3。唯一的不同就是,手动除法计算的是10进制,是需要从1到9挨个尝试的,而计算机只有0和1,要么没有,要么就是它本身,因此,我们只需要一直左移,然后观察被除数减去移动后的数是否大于0,就可以模拟出尝试过程。
上述是对比手动除法的过程,模拟的位运算除法的思路,思路是没问题,不过,如何用代码实现,除数一直左移,然后判断是不是当前最大的且能够被被除数减去的值?
其实,代码的实现过程反而和思路不一样,我们可以让被除数右移,和除数左移其实是一样的思路,但是,却能避免溢出的问题。同时,采用遍历的思想,因为int一共也就32位,让被除数从31位开始遍历,依次右移31位到0位,然后,挨个判断是否移动后大于除数,因为是从31开始右移的,因此,第一次出现能够除的下的位置,一定是最高位,此时,累加最高位就行,然后模拟除法中,被除数减去除数右移那么多位,然后依次循环上述过程。
代码
//a/b
public static int div(int a,int b){
//累计结果
int res=0;
//被除数右移来取代除数左移,避免溢出
for (int i=31; i>-1; i--){
//第一次进入if时,一定是结果的最高位
if((a>>i)>=b){
//累加最高位
res |= (1<<i);
//减去后,再进入下一轮
a=minus(a,b<<i);
}
}
return res;
}