只用位运算不用算术运算实现整数的加减乘除运算
位运算实现整型算术运算
【题目】
给定两个32位整数a和b,可正、可负、可0。不能使用算术运算符,分别实现a和b的加减乘除运算。
【要求】
如果给定的a和b执行加减乘除的某些结果本来就会导致数据的溢出,那么你实现的函数不必对那些结果负责。
实现结果皆为真实算术运算结果,无溢出。
加法
加法结果拆分成无进位加法(半加法)和进位。
无进位加法:a ^ b
进位:(a & b) << 1
无进位加法与进位结果再重复以上操作(再相加),直到进位为0。
注意:
由于python不限制数的范围,当异号数相加大于0时,进位为负数,带符号左移时不断变小,因此统一转换为加数相反数之和,取反。即(a + b) = - (-a + -b)。e.g. a = -3, b = 4, -3 + 4 = -(3 + -4) = -(-1) = 1
相应代码
# (有进位)加法,无溢出
# a^b实现无进位加法,(a&b)<<1实现进位
def add(a, b):
if b == 0:
return a
# a, b互为相反数
if a == -b:
return 0
flag = False
# 异号数之和大于0,转换加数符号
if a < 0 and b > 0:
if -a < b:
flag = True
a = -a
b = -b
if a > 0 and b < 0:
if a > -b:
flag = True
a = -a
b = -b
while b != 0:
res = a ^ b
b = (a & b) << 1
a = res
if flag is True:
res = -res
return res
#简单测试
if __name__ == '__main__':
a = 1
b = -1
print(add(a, b)) # 0
a = 2
b = -3
print(add(a, b)) # -1
a = -4
b = 3
print(add(a, b)) # -1
a = -5
b = 100
print(add(a, b)) # 95
减法
a - b = a + (-b)
注意:
-
在代码中为取反操作符。
平常我们可以用~n+1
实现取反运算,而此时我们需要实现加法运算,而在实现加法运算的代码中,又需要取反操作,这就构成了一个死循环,因此引入-
取反操作符,而不使用~n+1
实现取反运算。
相应代码
# 减法
# a - b = a + (-b)
def sub(a, b):
return add(a, -b)
乘法
整型的二进制表达:
b
=
∑
0
j
(
b
i
×
2
i
)
,
b
i
=
0
o
r
1
b = \sum_{0}^{j}(b_i \times 2^i),b_i=0 \; or \;1
b=∑0j(bi×2i),bi=0or1
再利用加法的交换律和结合律
a
×
b
=
a
×
∑
0
j
(
b
i
×
2
i
)
=
a
×
∑
1
j
(
b
i
×
2
i
)
+
a
×
b
0
×
2
0
=
2
×
a
×
∑
0
j
−
1
(
b
i
×
2
i
)
+
a
×
b
0
×
2
0
a \times b = a \times \sum_{0}^{j}(b_i \times 2^i) = a \times \sum_{1}^{j}(b_i \times 2^i) + a \times b_0 \times 2^0 = 2 \times a \times \sum_{0}^{j-1}(b_i \times 2^i) + a \times b_0 \times 2^0
a×b=a×0∑j(bi×2i)=a×1∑j(bi×2i)+a×b0×20=2×a×0∑j−1(bi×2i)+a×b0×20
乘2左移1位,除2右移1位,将两个乘数符号位统一提取出来,统一使用正数移位运算,再讲符号位填至运算结果。
相应代码
# 乘法
def multi(a, b):
sign_a = 0 # a的符号位
sign_b = 0 # b的符号位
# 提取符号位
if a < 0:
sign_a = 1
a = -a
if b < 0:
sign_b = 1
b = -b
res = 0
while b != 0:
if b & 1 == 1:
res = add(res, a)
a = a << 1
b = b >> 1
if sign_a ^ sign_b == 1:
res = -res
return res
#简单测试
if __name__ == '__main__':
a = 1
b = -1
print(multi(a, b)) # -1
a = 2
b = -3
print(multi(a, b)) # -6
a = -5
b = 100
print(multi(a, b)) # -500
除法
除法是乘法的逆运算
a
/
/
b
=
∑
0
j
(
b
i
×
2
i
)
a
/
/
b
=
∑
0
j
(
b
i
×
2
i
)
=
b
j
×
2
j
+
∑
0
j
−
1
(
b
i
×
2
i
)
(
a
−
b
j
×
2
j
×
b
)
/
/
b
=
∑
0
j
−
1
(
b
i
×
2
i
)
=
b
j
−
1
×
2
j
−
1
+
∑
0
j
−
2
(
b
i
×
2
i
)
∴
∑
0
j
(
b
i
×
2
i
)
即
为
商
a // b = \sum_{0}^{j}(b_i \times 2^i) \\ a // b = \sum_{0}^{j}(b_i \times 2^i)= b_j \times 2^j +\sum_{0}^{j-1}(b_i \times 2^i) \\ (a - b_j \times 2^j \times b) // b = \sum_{0}^{j-1}(b_i \times 2^i) = b_{j-1} \times 2^{j-1} +\sum_{0}^{j-2}(b_i \times 2^i) \\ \therefore \sum_{0}^{j}(b_i \times 2^i)即为商
a//b=0∑j(bi×2i)a//b=0∑j(bi×2i)=bj×2j+0∑j−1(bi×2i)(a−bj×2j×b)//b=0∑j−1(bi×2i)=bj−1×2j−1+0∑j−2(bi×2i)∴0∑j(bi×2i)即为商
先转换为正数相除,再根据符号位判断结果的正负
负数的范围比正数的范围多1,因此转换为正数(abs
),存在溢出问题,java需要考虑整型溢出问题,python无需考虑。
相应代码
# 完整除法
# 先转换为正数相除,再根据符号位判断结果的正负
# 负数的范围比正数的范围多1,因此转换为正数(`abs`),存在溢出问题,java需要考虑整型溢出问题,python无需考虑
def div(a, b):
# 除数为0
if b == 0:
raise ZeroDivisionError("除0错误")
sign_a = 0 # a的符号位
sign_b = 0 # b的符号位
# 提取符号位
if a < 0:
sign_a = 1
a = -a
if b < 0:
sign_b = 1
b = -b
res = 0
fac = 1 << 31
for i in range(0, 32):
if a >= multi(b, fac):
res = add(res, fac)
a = sub(a, multi(b, fac))
fac = fac >> 1
if sign_a ^ sign_b == 1:
res = -res
return res
#简单测试
if __name__ == '__main__':
a = 1
b = -1
print(div(a, b)) # -1
a = 4
b = 2
print(div(a, b)) # 2
a = 100
b = -5
print(div(a, b)) # -20
# a = 1
# b = 0
# print(div(a, b)) # ZeroDivisionError
有任何疑问和建议,欢迎在评论区留言和指正!
感谢您所花费的时间与精力!