# 2.二进制实现加减乘除
# 题目:
要求使用位运算实现加减乘除,实现过程中不允许出现 +, -, *, / 的计算表达式,只能使用位运算。
# 解析:
# 加法思路:
获取进位的值
假设 a , b 的值如图:

我们观察 a & b 的结果,由于是a,b相同位置都为1的时候取1。
a + b 的话相同位置为1,则当前位置为0,进位为1。
所以我们将 a & b 左移 1位,即可得到 a + b 的进位部分的值。
当前位置的值

我们观察 a ^ b 的结果,由于是a,b相同位置都不同的时候取1。
a + b 的话相同位置如果不同的话,只能是0和1,则保留1。
所以我们将 a ^ b ,即可得到 a + b 的不包含进位的值。
代码
// 递归写法
func Sum(a, b int) int {
// 当a等于0时,直接返回b
if a == 0 {
return b
}
// 记录下原来A的值
cacheA := a
// 求出进位的值
a = (a & b ) << 1
// 求出不包含即为的值
b ^= cacheA
// 继续求和,让进位位一直左移,直到变成0,得到结果返回
return Sum(a, b)
}
// 非递归写法
func Sum2(a, b int) int {
var cacheA int
for a != 0 {
cacheA = a
a = (a & b ) << 1
b ^= mid
}
return b
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 减法思路:
我们直到 a - b 等价于 a + (-b),那在计算机中,我们怎么表达取相反数呢?我们先使用 1.int类型的二进制码 打印一下1的二进制表达:

我们发现:-1 的二进制码 等于 1取反加1。
其实在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理。好处时可以加快计算机。想要直到具体的原理可以参考补码究竟是什么? (opens new window)。
所以我们可以使用我们之前实现的加法函数,实现减法:
func Reduce(a, b int) int {
return Sum(a, Invert(b))
}
// 取反
func Invert(num int) int {
return Sum2(^num, 1)
}
2
3
4
5
6
7
8
测试 10 - 5:

# 乘法思路:

上图给出了 3 * 3 二进制乘法的具体的示例,我们采用乘法竖式,可以得出每位相乘的结果,最后累加,即刻得出答案9的二进制。
我们想达到与乘法竖式相同的结果可以使用如下的方式:
- 初始化 答案 Ans 等于0,被乘数左移0位,乘数与1左移0位相与不等于0,则根据乘法竖式,Ans需要加上此时的左移0位的被乘数3.

- 被乘数左移1位,乘数与1左移1位相与不等于0,则根据乘法竖式,Ans需要加上此时的左移1位的被乘数3.

- 被乘数左移2位,乘数与1左移2位相与不等于0,则根据乘法竖式,Ans需要加上此时的左移2位的被乘数6.

- 被乘数左移3位,乘数与1左移3位相与等于0,则根据乘法竖式,Ans需要加上0,Ans不变

得出Ans = 9. 以上以4位的二进制的正数作为例子,实际实现乘法的时候,需要考虑正负数。
通过观察其实,只要保证乘数左移的时候不将符号数左移即可,java中有无符号左移,所以在java中无需特殊处理,但是由于在go中没有无符号左移,所以在go中,如果乘数是负数,需要将乘数取反进行计算,最后的时候还原符号返回即可。
代码
// 递归版本
func MultiplyProcess(a, b, ans int) int {
// 当乘数为0时,则已经获取到了所有的累加和
if b == 0 {
return ans
}
// 如果b小于0,需要取反进行计算
bLessZero := false
if b < 0 {
bLessZero = true
b = Invert(b)
}
// 当前位置不等于0,累加结果
if b & 1 != 0 {
ans = Sum2(ans, a)
}
// 被乘数左移
a <<= 1
// 乘数右移
b >>= 1
// 恢复计算现场
if bLessZero {
b = Invert(b)
ans = Invert(ans)
}
return MultiplyProcess(a, b, ans)
}
// 迭代版本
func Multiply2(a, b int) int {
bLessZero := false
if b < 0 {
bLessZero = true
b = Invert(b)
}
ans := 0
for b != 0 {
if b & 1 != 0 {
ans += a
}
a <<= 1
b >>= 1
}
if bLessZero {
return Invert(ans)
}
return ans
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# 除法思路:
采用除法竖式的思路,我们一步一步的确定我们结果每一个位置上是去0还是取1,通过移位并对比做减法,我们可以得到除法的答案,具体步骤如下:

我们以 4位的二进制除法 做了我们的例子,同过上述思路我们,可以初步确定我们的代码逻辑,但是还需要注意,在除法中,我们需要比较两数大小,如果带上符号位,在有正负的情况下我们不好做对比,所以我们可以将两个数都转为正数,最后根据原来的值决定是否需要修改符号返回。
由于负数在二进制总比表达正数的数多一个如二位的二进制:00->[0] ,01->[1] ,10->[-2] ,11->[-1] 。我们还需要注意一下几种情况,如:-2 / -1 原则上 -2/-1 等于 2,但是由于我们的最大值是1,我们无法有2这个值。我们还认为最小值除以-1等于最大值,所以我们认为在这种情况下 -2/-1 = 1
另一种情况:假设我们现在只有三位二进制数,0000 -> 1111 ,则我们能表示的数字是 -8 -> 7 之间的数,此时 -8/2 = -4,由于我们算除法的时候,需要对 -8 -> [1000] 取反加1 变成 [0111] -> 7 ,此时 7/2 = 3,与预期少了1。
所以,在被除数为最小的时候,如:-8 / 2 的时候,我们使用 (-8 + 1)/2 得到 -3,我们将 -3 * 2 = -6,此时 -8 - (-6 )= -2,用 -2 / 2 = -1,此时将差值 -1 + (-3) = -4,此时可以得到正确结果。
我们通过图示的方式可以计算出结果,但是有特殊情况如下:

我们发现,使用除数左移,容易超出上限,导致得到错误的结果。
所以我们实际使用被除数左移,从上限移动到0位。

代码
func divide(dividend int, divisor int) int {
if divisor == math.MinInt32 && dividend == math.MinInt32 {
// 为什么是 math.MinInt32 为最小值?因为力扣采用的是int32做的测试用例
// 最小值除本身等于 1
return 1
} else if divisor == math.MinInt32 {
// divisor 为系统最小时 除了相等的时候都是0
return 0
} else if dividend == math.MinInt32 {
if divisor == Invert(1) {
return math.MaxInt32
}
ans := Div2(dividend, divisor)
return Sum2(ans, Div2(Reduce(dividend, Multiply2(ans, divisor)), divisor))
}
return Div2(dividend, divisor)
}
func Div2(a, b int) int {
isALessZero, isBLessZero := false, false
if a < 0 {
isALessZero = true
a = Invert(a)
}
if b < 0 {
isBLessZero = true
b = Invert(b)
}
ans := 0
for i:=62; i>=0; i-- {
// 采用 a 右移 i 位
if (a >> i) >= b {
ans |= 1<<i
a = Reduce(a, b<<i)
}
}
if isALessZero && isBLessZero {
return ans
} else if isALessZero || isBLessZero {
return Invert(ans)
}
return ans
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 完整代码:
可拷贝到力扣网站直接执行通过,已验证。力扣原题连接 (opens new window)
import "math"
func Sum(a, b int) int {
if a == 0 {
return b
}
cache := a
a = (a & b ) << 1
b ^= cache
return Sum(a, b)
}
func Sum2(a, b int) int {
var mid int
for a != 0 {
mid = a
a = (a & b ) << 1
b ^= mid
}
return b
}
func Reduce(a, b int) int {
return Sum(a, Invert(b))
}
func Invert(num int) int {
return Sum2(^num, 1)
}
func MultiplyProcess(a, b, ans int) int {
// 当乘数为0时,则已经获取到了所有的累加和
if b == 0 {
return ans
}
// 如果b小于0,需要取反进行计算
bLessZero := false
if b < 0 {
bLessZero = true
b = Invert(b)
}
// 当前位置不等于0,累加结果
if b & 1 != 0 {
ans = Sum2(ans, a)
}
// 被乘数左移
a <<= 1
// 乘数右移
b >>= 1
// 恢复计算现场
if bLessZero {
b = Invert(b)
ans = Invert(ans)
}
return MultiplyProcess(a, b, ans)
}
func Multiply2(a, b int) int {
bLessZero := false
if b < 0 {
bLessZero = true
b = Invert(b)
}
ans := 0
for b != 0 {
if b & 1 != 0 {
ans += a
}
a <<= 1
b >>= 1
}
if bLessZero {
return Invert(ans)
}
return ans
}
func divide(dividend int, divisor int) int {
if divisor == math.MinInt32 && dividend == math.MinInt32 {
return 1
} else if divisor == math.MinInt32{
return 0
} else if dividend == math.MinInt32 {
if divisor == Invert(1) {
return math.MaxInt32
}
ans := Div2(dividend, divisor)
return Sum2(ans, Div2(Reduce(dividend, Multiply2(ans, divisor)), divisor))
}
return Div2(dividend, divisor)
}
func Div2(a, b int) int {
isALessZero, isBLessZero := false, false
if a < 0 {
isALessZero = true
a = Invert(a)
}
if b < 0 {
isBLessZero = true
b = Invert(b)
}
ans := 0
for i:=62; i>=0; i-- {
if (a >> i) >= b {
ans |= 1<<i
a = Reduce(a, b<<i)
}
}
if isALessZero && isBLessZero {
return ans
} else if isALessZero || isBLessZero {
return Invert(ans)
}
return ans
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117