用位运算不用算术运算实现整数的加减乘除运算

只用位运算不用算术运算实现整数的加减乘除运算

位运算实现整型算术运算

【题目】
给定两个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×0j(bi×2i)=a×1j(bi×2i)+a×b0×20=2×a×0j1(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=0j(bi×2i)a//b=0j(bi×2i)=bj×2j+0j1(bi×2i)(abj×2j×b)//b=0j1(bi×2i)=bj1×2j1+0j2(bi×2i)0j(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

有任何疑问和建议,欢迎在评论区留言和指正!

感谢您所花费的时间与精力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值