用位操作实现整数的加减乘除
相关位操作:
由于使用位操作,所以需要进行二进制位的相关操作:
1. 按位异或 (^)
可用于二进制数的无进位加法。
同时,异或运算还支持结合律和交换律:
即 a^b^c == a^(b^c)
a^b^c == c^b^a
2. 按位或 (|)
通常用于将某一位置一,也可以将某位的数取出
比如 a |= 0x10 就可以将第4位置一,同时取出0~3位的数值。
3. 按位与 (&)
通常用于将某一位置零或取出特定一位的数值
两个二进制数之间使用还能确定哪些位数将会溢出(进位)
加法
与十进制下的加法类似,两大重要因素包括同位的相加以及相邻位的进位,需要用到 异或 与 与 运算
加法下我们注意两个步骤:无进位相加 以及 进位管理
例如:13 + 19
13
+ 19
——————————————————
22
+ 1
——————————————————
32
若转化为二进制:
1101
+ 10011
———————————————————
11110
+ 1
———————————————————
100000 -> 32
13 与 19 先不考虑进位,计算无进位相加的结果:
1101
^ 10011
————————————
11110 -> 30
接下来考虑进位:
1101
& 10011
------------
00001
因此在第一位溢出,所以应向第二位进位
所以 13&19 << 1 即为需要进的位 即为 00010
再用需要进的位 (00010)再次进行 无进位相加 再考虑进位
重复上述过程
直至进位数等于0为止。
代码实现 :
int sum(int a ,int b)
{
int ret = (a ^ b);//用于记录无进位相加
int yichu = (a & b) << 1 ;//用于管理进位
int temp ; //临时的中间变量
while(yichu != 0) //如有进位,则一直进行
{
temp = ret;
ret = ret ^ yichu ;
yichu = (temp & yichu) << 1;
}
return ret ;
}
用递归实现的优化版本 :
int sum1(int a,int b){
if(a & b == 0)
return (a ^ b);
else
return sum1(a ^ b,(a & b) << 1);
}
乘法
乘法运算可以说是加法运算的延申,可用加法的循环来实现,在此处我们使用位移运算计算:
——对有符号整型来说,数的左移和右移分别相当于 乘以二的次幂 和 除以二的次幂 ,称之为算术位移
例如:
a << 1 === a * 2;
a >> 1 === a / 2;
那么对于不是二的次幂该如何相乘呢?我们可以用二的次幂的乘法和加法组合获得相应的结果,例如:
a * 5 === a * 4 + 1;
b * 7 === b * 4 + b + b + b;
通过位运算也可以得到相同的结果。
不过我们在此处运用手算乘法时的步骤,比如说 :
十进制下乘法的手乘过程:
17 * 19 =
17
19
————————————————————
153
17
——————————————————————
323
二进制下:
10001
10011
——————————————————————
10001
10001
10001
——————————————————————
101000011 --> 32
*******二进制下的手算比十进制下的手算更加容易,只需要遍历其中一个因子的所有位数,若为一,则将其余一个因子左移相应位数的偏移量,即可,或者说,进行加权相加。
代码的实现即为模拟手算的过程:
int mult(int a , int b)
{
int flag = 1;
if(a < 0)
{
a = (~a) + 1;//若为负数,则转化为正数
flag = -flag; //控制符号正负
}
if(b < 0)
{
b = (~b) + 1;
flag = -flag;
}
if(a == 0 || b == 0)
return 0;//考虑乘数为0时
int ret = 0;
while(b)
{
if(b & 0x01)
{
ret = sum1(ret , a);
}
a <<= 1; //进行加权左移
b >>= 1;
}
if(flag == 1)
return ret;
else
return -ret;
}
减法
可以由加法转化而来,比如 a - b == a + (-b)
此处就涉及到运用位运算将正负数转化,由于负数是以补码的形式存储的,补码可以理解位需要加几才等于0,转化可由相应的正数取反加1获得。
代码实现
int mins(int a,int b){
return (sum1(a,(~b) + 1)) ;
}
与加法相似,这里不多做赘述。
除法
除法是减法的延续,我们可以用减法的循环来实现,但这样做未免太低效,我们可以做一些优化。
如果除数为二的次幂,则可以使用位右移来快速的实现:
int devide_2times(int a , int b)
{
int flag = 1;
if(a == 0 ||b == 0)
return 0;
if(a < 0)
{
a = (~a) + 1;
flag = -flag;
}
if(b < 0)
{
b = (~b) + 1;
flag = -flag;
}
while(b != 0x01)
{
a >>= 1;
b >>= 1;
}
if(flag == 1)
return a;
else
return -a;
}
如果除数不为二的次幂,我们可以使用循环相减,但在这里做一些优化,先找到小于被除数的最大的 因子,因子为 2^n*除数,用被除数减去该因子并在 结果值上加上2^n 在不断循环,直到n == 0为止。
代码实现:
int devide(int devided,int devider)
{
if(devided == 0 || devider == 0)
return 0;
int flag = 1;
int n = 0;
int ret = 0;
if(devided < 0)
{
flag = -flag;
devided = (~devided) + 1;
}
if(devider < 0)
{
flag = -flag;
devider = (~devider) + 1;
}
while(devided >= devider)
{
if((devided >> (30 - n)) >= devider)
{
ret = sum1(ret , (1 << (30 - n)));
devided = mins(devided,devider<<(30 - n));
}
n++;
}
if(flag == 1)
return ret;
else
return -ret;
}
在实现上述代码的过程中一定要注意是被除数右移,这样不会产生溢出,若将除数左移则很容易就产生溢出!!!