位运算实现加、减、乘、除

一、加法

原理\(a\) + \(b\) = 无进位加法 + 进位加法 = \(a\)^\(b\) + (\(a\) & \(b\)) << \(1\) = ...

int add(int a, int b) {
   while (b != 0) {
        int no_c = a ^ b; // 无进位加法
        int c = (unsigned int)(a & b) << 1; // 进位加法,防止越界
        a = no_c;
        b = c;
    }
    return a;
}

二、减法

原理:转换为加法实现,\(a\) - \(b\) = \(a\) + \((\)-\(b\)\()\),在计算机中,整数通过补码的形式储存,而补码的相反数等于符号位取反,数值位取反加1,相当于整体取反加1,即 \(-b\) = ~\(b\) + \(1\),然后使用上方加法运算。

int sub(int a, int b) {
   b = add(~b, 1); // ~b + 1
   return add(a, b); // a + (-b)
}

三、乘法

方法一(累加)\(a\) * \(b\) = \(b\) + \(b\) + ... + \(b\)\(a\)个),需要注意的是 \(a\)\(b\) 可能为负数,我们可以先计算两个乘积的绝对值,然后再根据两个数的符号位确定最终的符号。

int mul(int a, int b) {
    int absA = a < 0 ? add(~a, 1) : a;
    int absB = b < 0 ? add(~b, 1) : b;
    if (absB < absA) swap(absA, absB); // 减小循环次数
    int cnt = 0, res = 0;
    while (cnt < absA) {
        res = add(res, absA);
        cnt = add(cnt, 1);
    }
    if ((a & 0x80000000) != (b & 0x80000000)) res = add(~res, 1);
    return res;
}

方法二(二进制乘法):对于 \(5\) * \(7\),其二进制为 \(101\) * \(111\),相当于后面的每一位 \(1\) 乘上 \(101\) 然后左移相应位数加到答案上,需要注意的是两个数可能为负数,我们可以先计算两个乘积的绝对值,然后再根据两个数的符号位确定最终的符号。

int mul(int a, int b) {
    int absA = a < 0 ? add(~a, 1) : a;
    int absB = b < 0 ? add(~b, 1) : b;
    if (absA < absB) swap(absA, absB); // 减小循环次数
    int res = 0;
    while (absB) {
        if ((absB & 1) != 0)
            res = add(res, absA);
        absA <<= 1;
        absB >>= 1;
    }
    if ((a & 0x80000000) != (b & 0x80000000)) res = add(~res, 1);
    return res;
}

四、除法

方法一(累减)\(a\) / \(b\) = \(c\) ... \(d\) => \(a\) = \(b\) * \(c\) + \(d\),那么用 \(a\) 多次减去 \(b\) 使得结果小于 \(b\) 时,此时的次数就是 \(c\),结果就是 \(d\),和乘法类似,首先排除符号的影响,最后再考虑符号位。

int div(int a, int b) {
    int absA = a < 0 ? add(~a, 1) : a;
    int absB = b < 0 ? add(~b, 1) : b;
    int res = 0;
    while (absA >= absB) {
        absA = sub(absA, absB);
        res = add(res, 1);
    }
    if ((a & 0x80000000) != (b & 0x80000000)) res = add(~res, 1);
    return res;
}

方法二\(a\) / \(b\) = \(c\) => \(a\) = \(b\) * \(c\),假设:a = b * (2^7) + b * (2^4) + b * (2^1),则 \(c\) 的二进制一定是 10010010,因此可以转换成 \(a\) 是由几个 \(b * 2^i\) 的结果组成。

int div(int a, int b) {
    int absA = a < 0 ? add(~a, 1) : a;
    int absB = b < 0 ? add(~b, 1) : b;
    int res = 0;
    for (int i = 31; i >= 0; i = sub(i, 1) ) {
        if ((absA >> i) >= absB) {
            res |= (1 << i);
            absA = sub(absA, absB << i);
        }
    }
    if ((a & 0x80000000) != (b & 0x80000000)) res = add(~res, 1);
    return res;
}

注意:若题目有溢出相关要求,根据要求处理溢出情况。

参考题目:剑指 Offer II 001. 整数除法

 对于特殊情况,a = 0x80000000 && b != -1 && b != 0x80000000,则 \(a / b\) 应该通过如下方式来计算,先让 \(a + 1\),然后算 \((a + 1) / b = c\),接着 \(a - (b * c) = d\),然后 \(d / b = e\),最后 \(c + e = ((a + 1)/b) + ((a - (b * c)) / b) = a / b\),即得到 \(a / b\) 的值。

class Solution {
public:
    int add(int a, int b) {
        while (b != 0) {
            int no_c = a ^ b; // 无进位加法
            int c = (unsigned int)(a & b) << 1; // 进位加法,防止越界
            a = no_c;
            b = c;
        }
        return a;
    }
    int sub(int a, int b) {
        b = add(~b, 1); // ~b + 1
        return add(a, b); // a + (-b)
    }
    int mul(int a, int b) {
        int absA = a < 0 ? add(~a, 1) : a;
        int absB = b < 0 ? add(~b, 1) : b;
        if (absA < absB) swap(absA, absB); // 减小循环次数
        int res = 0;
        while (absB) {
            if ((absB & 1) != 0)
                res = add(res, absA);
            absA <<= 1;
            absB >>= 1;
        }
        if ((a & 0x80000000) != (b & 0x80000000)) res = add(~res, 1);
        return res;
    }
    int div(int a, int b) {
        int absA = a < 0 ? add(~a, 1) : a;
        int absB = b < 0 ? add(~b, 1) : b;
        int res = 0;
        for (int i = 31; i >= 0; i = sub(i, 1) ) {
            if ((absA >> i) >= absB) {
                res |= (1 << i);
                absA = sub(absA, absB << i);
            }
        }
        if ((a & 0x80000000) != (b & 0x80000000)) res = add(~res, 1);
        return res;
    }
    int divide(int a, int b) {
        if (a == 0 || (a != 0x80000000 && b == 0x80000000)) return 0;
        if (a == 0x80000000) {
            if (b == 1) return 0x80000000;
            else if (b == -1) return 0x7fffffff;
            else if (b == 0x80000000) return 1;
        }
        if (a == 0x80000000) {
            int res = div(add(a, 1), b);
            return add(res, div(sub(a, mul(res, b)), b));
        }
        return div(a, b);
    }
};

参考

使用位运算技巧实现加减乘除

posted @ 2023-04-20 01:12  lixycc  阅读(1147)  评论(0)    收藏  举报