通过位运算实现整数的加减乘除
Table of Contents
前言
很久以前就了解过怎样通过位运算实现整数的加减乘除,但是每次都只是大概的了解了一下加减法的实现。
最近正好有时间就去进一步的了解了乘法和除法的实现,这里一起总结一下。
加法实现
对于二进制的整数加法来说,我们需要让 1
和 0
的运算结果为 1
, 而 0
和 0
的运算结果为 0
,
这和 异或 运算的结果刚好相同。
1 ^ 0 = 1, 0 ^ 0 = 0
同时,我们需要让 1
和 1
的运算结果为 10
, 也就是需要进位,很明显这无法用单次的位运算操作实现,因此,
我们可以考虑通过 与 运算和 移位 运算来实现。
(1 & 1) << 1 = 10
和竖式加法一样,如果两个数相加不发生进位,那么直接用 异或 就足够了,如果发生进位,
那么我们就需要将 异或 的结果和 进位 的结果相加。
C 语言实现:
int plus(int a, int b) {
int sum = a, carry = b;
while (carry) {
int temp = sum;
sum = temp ^ carry; // a ^ b
carry = (temp & carry) << 1; // (a & b) << 1
}
return sum;
}
减法实现
减去一个数其实就是加上那个数的相反数,我们可以通过如下方法得到一个数的相反数:
int negate(int num) {
return plus(~num, 1);
}
然后,减法就可以很简单的实现了:
int subtract(int a, int b) {
return plus(a, negate(b));
}
乘法实现
对于乘法来说,直接的实现就是通过很多次的加法来实现,简单粗暴:
int abs(int num) {
return num < 0 ? negate(num) : num;
}
int multiply(int a, int b) {
int multiplier = abs(a), multiplicand = abs(b);
int product = 0;
while (multiplicand) {
product = plus(product, multiplier);
multiplicand = subtract(multiplicand, 1);
}
if ((a ^ b) < 0) {
product = negate(product);
}
return product;
}
对于符号位,可以先计算两个数绝对值的乘积,然后根据符号位修改结果。
但是,我们也可以利用一点数学技巧来进行改进:
- 当我们的乘数是一个 偶数 的时候,我们可以将被乘数乘 2 而乘数除 2,这不影响结果
- 当我们的乘数是一个 奇数 的时候,我们可以让积加上一次被乘数,然后让乘数减 1,这同样不影响结果
int multiply(int a, int b) {
int multiplier = abs(a), multiplicand = abs(b);
int product = 0;
while (multiplicand) {
if (multiplicand & 0x1) { // 奇数的最低位为 1
product = plus(product, multiplier);
multiplicand = subtract(multiplicand, 1);
} else {
multiplier = multiplier << 1; // multiplier * 2;
multiplicand = multiplicand >> 1; // multiplicand / 2
}
}
if ((a ^ b) < 0) {
product = negate(product);
}
return product;
}
然后我们在观察一下,当乘数是奇数的时候,我们的操作会是:
- 乘数减 1,变成偶数
- 乘数除 2
这和除 2 并向下取整的结果是一样的,对于奇数,右移位的效果和向下取整相同,因此。我们的代码可以修改为:
int multiply(int a, int b) {
int multiplier = abs(a), multiplicand = abs(b);
int product = 0;
while (multiplicand) {
if (multiplicand & 0x1) {
product = plus(product, multiplier);
}
multiplier = multiplier << 1;
multiplicand = multiplicand >> 1;
}
if ((a ^ b) < 0) {
product = negate(product);
}
return product;
}
除法实现
和乘法一样,我们可以通过不断的减法来实现除法,但是,同样的,我们可以借助数学技巧来获得更好的实现。
首先我们来看一下以下两个数的除法:
1
-----------------------------
1 0 1 0 | 1 0 1 0 0 0 1 1
| 1 0 1 0
当我们像这样进行除法计算的时候,我们的下一步应该是:
1
-----------------------------
1 0 1 0 | 1 0 1 0 0 0 1 1
| 1 0 1 0
---------------------------
0 0 0 0
此时,我们进行了一次减法,我们减去的是什么数字呢?是 1010
吗?很明显,不是的,而是:
1
-----------------------------
1 0 1 0 | 1 0 1 0 0 0 1 1
| 1 0 1 0 0 0 0 0
---------------------------
0 0 0 0 0 0 1 1
这相当与将 1010
向左移了 4
位,我们在更换数字尝试一下:
1
-----------------------------
1 0 1 1 | 1 0 1 0 1 0 1 1
| 1 0 1 1 0 0 0
---------------------------
1 0 1 0 0 1 1
很明显,这相当于将数字 1011
向左移了 3
位,为什么不移 4
位呢?因为如果移 4 位,得到的除数就比被除数大了。
由此,我们可以归纳出除法需要进行的步骤:
- 首先将除数和被除数进行对齐,即除数和被除数的第一个 1 在同一位上
- 判断除数是否大于等于被除数,如果为否,就不断右移除数,直到为真
- 用除数减去当前的被除数,减法的结果作为新的被除数
- 重复前面的步骤,直到被除数为 0
然后,我们就可以尝试实现了:
// 计算整数 a 的有效位长度
int bitlength(int a) {
int length = 0;
while (a) {
plus(length, 1);
a = a >> 1;
}
return length;
}
// 计算整数 a 和 b 的有效位长度的差值
int lengthdiff(int a, int b) {
return subtract(bitlength(a), bitlength(b));
}
int division(int a, int b) {
int dividend = abs(a), divisor = abs(b);
int quotient = 0;
for (int i = lengthdiff(dividend, divisor); i >= 0; i = subtract(i, 1)) {
int r = (divisor << i);
// Left shift divisor until it's smaller than dividend
if (r <= dividend) {
quotient |= (1 << i);
dividend = subtract(dividend, r);
}
}
if ((a ^ b) < 0) {
quotient = negate(quotient);
}
return quotient;
}
求余实现
实现了除法,求余也就差不多,直接把最后剩余的被除数返回就可以了:
int remain(int a, int b) {
int dividend = abs(a), divisor = abs(b);
int quotient = 0;
for (int i = lengthdiff(dividend, divisor); i >= 0; i = subtract(i, 1)) {
int r = (divisor << i);
// Left shift divisor until it's smaller than dividend
if (r <= dividend) {
dividend = subtract(dividend, (int) r);
}
}
if (a < 0) {
dividend = negate(dividend);
}
return dividend;
}
结语
这里尝试了通过位运算实现整数的四则运算,假如你有兴趣的话,可以试一下浮点数的 @_@
获取浮点数的二进制表示:
unsigned float2binary(float x) {
return ((unsigned*)&x)[0];
}
完整的代码链接:Incremental addition, subtraction, multiplication and division of integers by bit operations