有的时候,出于精度与大小或是效率等方面的考虑,我们需要通过位运算实现两个数加、减、乘、除、两个变量交换。这里将详细的总结一下用位运算来实现整数的四则运算以及两个变量的交换。位运算的操作符有|、&、^、~、>>、<<,如果对这些操作符还不很了解,可以查看这里
加法运算
因为位运算符是按位来运算的,那么我们先看二进制的1位数相加
1 + 1 = 10
1 + 0 = 1
0 + 1 = 1
0 + 0 = 0
那么,此时我们需要在脑海里联想使用什么办法能实现这样的效果呢?于是我们发现了一个规律,两个1位二进制整数进行异或运算,结果等于其右边第一位数上的数字(设为A)。两个1位二进制整数进行与运算,结果等于其右边第二位数上的数字(设为B),如果将B往左移1位加上A,结果等于二进制的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
于是可以得到
1 + 1 = 1 ^ 1+(1 & 1)<<1
1 + 0 = 1 ^ 0+(1 & 0)<<1
0 + 1 =0 ^ 1+(0 & 1)<<1
0 + 0 =0 ^ 0+(0 & 0)<<1
为什么会这样呢?
因为进行异或运算,代表两个不考虑进位的加法运算。进行与运算后在往左移动一位,代表只考虑进位的两个数相加结果。
对应两位数的二进制是否也成立呢?
例1
10+01=11
10^01=11
10&01=0
11+0<<1=11
例2
11+01=100
11^01=10
11&01=1
10+1<<1=10+10=100
通过例1、例2已经验证了我们的方法,如果不放心,还可以做更多测试验证,至于3位数、4位数等就不再验证了。
于是问题简化为 a+b=a^b+(a&b)<<1;但是此时,里面仍然包含了加法,怎么办呢?想想,如果a^b或(a&b)<<1有一个为0,是不是很好,就不需要计算加法了,那么到底哪个为0呢?肯定是进位。
代码实现如下:
/**
*思想:计算两个数不考虑进位相加的结果+考虑进位相加的结果。然后不断循环计算,直到两个数相加的进位结果为0,那么
*两个数不考虑进位相加的结果就是两个数相加的结果
*/
//非递归
function toAdd1($a,$b){
$m=$a^$b; //获取两个数相加的结果(不包含进位)
$n=$a&$b;// 将其向左移动一位即为两个算相加的进位
while($n){
$n=$n<<1;
$tmp=$m;
$m=$m^$n;
$n=$tmp&$n;
}
return $m;
}
//递归
function toAdd2($a,$b){
return $b?toAdd2($a^$b,($a&$b)<<1):$a;
}
减法运算
既然加法的位运算已经实现了,那么减法位运算就相对简单了,因为减去一个数,等于加上这个数的相反数,关键在于求一个数的相反数。已知一个数的相反数对于其各位取反加1。即a的相反数等于~a+1。
于是a-b=a+~b+1
于是通过程序实现如下
function toAdd2($a,$b){
return $b?toAdd2($a^$b,($a&$b)<<1):$a;
}
function toMinus1($a,$b){
return toAdd1($a,toAdd1(~$b,1));
}
function toMinus2($a,$b){
return toAdd2($a,toAdd2(~$b,1));
}
乘法运算
原理上还是通过加法计算。将b个a相加,注意下面实际的代码。
代码如下:
//$a乘以$b等于$b个$a相加
function toMultip($a,$b){
$i=1;
if($b==0) return 0;
$sum=$a;
while ( $i<$b) {
$sum=toAdd1($sum,$a);
$i=toAdd1($i,1);
}
return $sum;
}
除法运算
除法运算是乘法的逆。a最多能被多少个b减
代码如下:
//a最多能被多数个b减
function toDivision($a,$b){
$count=0;
while ($a >=$b) {
$a=toMinus1($a,$b);
$count=toAdd1($count,1);
}
return $count;
}
两个变量交换
function change(&$i,&$j){
$i=$i^$j;
$j=$i^$j;
$i=$i^$j;
}
$a=3;
$b=5;
change($a,$b);
关于交互变量,我在之前的博客里面详细讨论过了,详细描述请点击这里