只使用位运算实现加减乘除

这篇具有很好参考价值的文章主要介绍了只使用位运算实现加减乘除。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

在线OJ: LeetCode 29. 两数相除

原题目的要求是不能使用乘法, 除法和取余运算符实现除法.

在本篇博客中把题目要求提高一点, 这里只使用位运算来实现, 顺便的也就把只使用位运算实现加减乘除实现了.

只使用位运算实现加减乘除

1 . 实现加法

首先我们需要知道两数之和可以是两个数位相加和不进位相加之和, 而两数进行异或(^)运算其实就是两个数对应二进制值的无进位相加.

我们可以把思路可以转换一下, 把加法用异或替换, 如果我们能够得到两个数相加的二进制进位信息为0, 那么此时的无进位结果就是两数相加的最终结果了.

只有二进制无进位信息, 相加的结果; 然后把这个结果加上进位信息, 就是两个数相加的最终结果.

抽象一下:

我们要计算 a + b

先算 a ^ b = a' 得到的结果就是无进位相加的结果, 然后我们再得到 a 和 b 相加的进位信息 b’, 那么此时 a + b = a' + b' 就是两数之和, 但我们这里是不能用加号, 所以我们需要逐个把进位信息再继续叠加 (即让a’ 和 b’ 再继续运算, 得到进位信息和不进位信息…), 直到进位信息为 0 结束.

那么进位信息如何计算?

只有当 a 和 b 的二进制对应位置上都是1, 才会产生进位, 只需要 通过 (a & b) << 1 就可得到进位结果.

所以, 只使用位运算实现加法的代码如下:

// 不使用算数运算实现两数相加
public static int add (int a, int b) {
    // 两个数的和等于两个数不进位相加和进位相加的和
    // a ^ b 得到的就是两数不进位相加的和
    // (a & b) << 1 得到的就是两数只相加进位的值

    // 一直循环至进位值为0计算结束
    int sum = a;
    while (b != 0) {
        sum = a ^ b;
        b = (a & b) << 1;
        a = sum;
    }
    return sum;
}

2 . 实现减法

两数相减, 等价于一个数加上另一个数的相反数, 即 a - b = a + (-b), 但这里是不能不能出现减号的, 所以, 可以用加法来模拟一个数的相反数, 因为 x 的相反数等于 x 取反再加 1 (~x + 1), 即 add(~x, 1).

所以, 只使用位运算实现减法可以在加法代码的基础上进行:

// 计算一个数字的相反数
public static int oppNum (int num) {
    return add(~num, 1);
}

// 不使用算数运算实现两数相减
public static int minus(int a, int b) {
    // a - b 就相当于 a + (-b)
    // 一个数的相反数等于这个数取反再加1
    return add(a, oppNum(b));
}

3 . 实现乘法

看下面计算两个十进制数的乘法用的方法是不是很熟悉, 以 a = 12, b = 22 为例, a * b 通过如下方式计算;

  19
x 22
------
  38
 38
------
 418

这样的方法也适用于二进制, 19 的二进制是 10011, 22 的二进制是 10110, 计算过程如下;

     10011
x    10110
-------------
     00000
    10011
   10011
  00000
 10011
------------
 110100010

得到的计算结果 110100010 就是 418.

其实这里的二进制计算就对应着这样一个过程, 要求 a * b 的结果, 就从右往左开始遍历 b 的每一位二进制值, 如果 b 的第 i 位是 1, 则把 a 左移 i 位的累值加到结果中; 如果 b 的第 i 位是 0, 不需要处理, 最后累加完的结果就是 a * b 的答案.

只使用位运算实现乘法的代码如下:

// 不使用算数运算实现两数相乘
public static int mulit (int a, int b) {
    // 就小学手算十进制类似
    // 让 a 的每一位去乘 b 的每一位
    int res = 0;
    while (b != 0) {
        if ((b & 1) != 0) {
            res = add(res, a);
        }
        a <<= 1;
        // 要注意 b 必须是无符号右移
        b >>>= 1;
    }
    return res;
}

4 . 实现除法

在实现除法的时候, 为了防止溢出, 我们可以先把两数转换成正数来进行计算; 最后在计算末尾再判断两个数的符号决定是否把由正数计算出来的结果取其相反数.

假设 a / b = c, 则 a = b * c, 使用位运算就需要结合二进制来思考, 这里假设:

a = b * (2^7) + b * (2^4) + b * (2^1), 则 c 的二进制一定是10010010.

抽象一下, 如果a = b * (2 ^ m1) + b * (2 ^ m2) + b * (2 ^ m3), 则 c 的 m1 位置, m2 位置, m3 位置一定是 1, 其他位置都是 0.

所以, 我们实现除法的思路可以转换成 a 是由几个 ( b * 2 的某次方) 的结果组成.

所以, 只使用位运算实现除法的核心代码如下:

// 判断是不是负数
public static boolean isNegavit(int num) {
    return num < 0;
}
// 这个适用于a和b都不是系统最小值的情况下
public static int div(int a, int b) {
    // 这里的除法一定要保证两个数都是正数, 如果有负数在计算逻辑最后加上即可
    int x = isNegavit(a) ? oppNum(a) : a;
    int y = isNegavit(b) ? oppNum(b) : b;
    int res = 0;
    // 计算的是 x / y
    // 每次循环 y 向左移动到和 x 最接近的值, 但要满足 y <= x, 如果是这个条件下, 也就是让 y 左移, 可能会溢出到符号位, 就可能会出错
    // 实际上将 x 向右移动到和 y 最接近的值, 移动的位数和上面也是一样的, 不过要满足 x >= y;
    for (int i = 30; i >= 0; i = minus(i, 1)) {
        if ((x >> i) >= y) {
            // 这个比特位一定是1
            res |= (1 << i);
            // x 减去 y << i;
            x = minus(x, y << i);
        }
    }
    // isNegavit(a) != isNegavit(b) 这个也可以用 isNegavit(a) ^ isNegavit(b) 来实现效果
    return isNegavit(a) != isNegavit(b) ? oppNum(res) : res;
}

其中

if ((x >> i) >= y) {
    // 这个比特位一定是1
    res |= (1 << i);
    // x 减去 y << i;
    x = minus(x, y << i);
}

就是让 a 不断尝试其值是否由 (b * 2的某个次方) 相加得到.

但只有上面的实现还不够, 这里是有一些特殊情况需要考虑的, 比如在 Java 中, int 类型的系统最小值Integer.MIN_VALUE取相反数的结果依然是Integer.MIN_VALUE.

所以, 除法的两个操作数如果有系统最小值的话需要单独的进行计算处理.

  1. 如果两操作数都是系统最小值, 结果就是 1;
  2. 如果只有除数 (b) 为系统最小值, 结果就是 0;
  3. 如果被除数 (a) 为系统最小值, 除数为 -1 时, 根据 LeetCode 题目要求, 结果就是Integer.MIN_VALUE / (-1) == Integer.MAX_VALUE;
  4. 如果被除数 (a) 为系统最小值, 除数为 -1Integer.MIN_VALUE时 (即a = Integer.MIN_VALUEb != -1 && b != Integer.MIN_VALUE), a / b应该通过如下方式来计算;
  • 可以先让先让系统最小值 +1 (a + 1), 此时就可以按照正常上面的正常流程去计算除法了, 即(a + 1) / b = c.
  • 接着计算a - (b * c) = d, 得到由于 a + 1 少/多算的.
  • 然后d / b = e
  • 最后将 ce 相加就得到了 a / b 的值, c + e = ((a + 1)/b) + ((a - (b * c)) / b) = a / b

除了这些特殊, 就是正常的流程了, 所以, 加上针对系统最小值的特殊判断的代码如下:

// 除法的计算如果有系统最小值需要进行特殊判断(因为Integer.MAX_VALUE取反再+1得到的还是原来值), 也就是没办法计算相反数
public static int divide(int a, int b) {
    if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
        // 如果两数都是系统最小值
        return 1;
    } else if (b == Integer.MIN_VALUE){
        // 如果除数为系统最小值
        return 0;
    } else if (a == Integer.MIN_VALUE) {
        // 被除数为系统最小值

        // 且除数为-1
        if (b == oppNum(1)) {
            // 答案应该是 Integer.MAX_VALUE + 1 的这个值的, 但力扣系统返回 Integer.MAX_VALUE 就行了
            return Integer.MAX_VALUE;
        } else {
            // 系统最小值没法取相反数计算
            // 1. c = (Integer.MAX_VALUE + 1) / b , 先让系统最小值 +1 后再除以 b
            // 2. (Integer.MAX_VALUE - c * b) / b
            // 3. 再将 1 和 2 的结果相加节课
            int c = div(add(a, 1), b);
            return add(c, div(minus(a, mulit(c, b)), b));
        }
    } else {
        // 两数都不是系统最小值
        return div(a, b);
    }
}

完整实现的代码如下:

class Solution {
    // 不使用算数运算实现两数相加
    public static int add (int a, int b) {
        // 两个数的和等于两个数不进位相加和进位相加的和
        // a ^ b 得到的就是两数不进位相加的和
        // (a & b) << 1 得到的就是两数只相加进位的值

        // 一直循环至进位值为0计算结束
        int sum = a;
        while (b != 0) {
            sum = a ^ b;
            b = (a & b) << 1;
            a = sum;
        }
        return sum;
    }

    // 计算一个数字的相反数
    public static int oppNum (int num) {
        return add(~num, 1);
    }


    // 不使用算数运算实现两数相减
    public static int minus(int a, int b) {
        // a - b 就相当于 a + (-b)
        // 一个数的相反数等于这个数取反再加1
        return add(a, oppNum(b));
    }

    // 不使用算数运算实现两数相乘
    public static int mulit (int a, int b) {
        // 就和小学手算十进制类似
        // 让 a 的每一位去乘 b 的每一位
        int res = 0;
        while (b != 0) {
            if ((b & 1) != 0) {
                res = add(res, a);
            }
            a <<= 1;
            // 要注意 b 必须是无符号右移
            b >>>= 1;
        }
        return res;
    }

    // 不使用算数运算实现除法

    // 判断是不是负数
    public static boolean isNegavit(int num) {
        return num < 0;
    }
    // 这个适用于a和b都不是系统最小值的情况下
    public static int div(int a, int b) {
        // 这里的除法一定要保证两个数都是正数, 如果有负数在计算逻辑最后加上即可
        int x = isNegavit(a) ? oppNum(a) : a;
        int y = isNegavit(b) ? oppNum(b) : b;
        int res = 0;
        // 计算的是 x / y
        // 每次循环 y 向左移动到和 x 最接近的值, 但要满足 y <= x, 如果是这个条件下, 也就是让 y 左移, 可能会溢出到符号位, 就可能会出错
        // 实际上将 x 向右移动到和 y 最接近的值, 移动的位数和上面也是一样的, 不过要满足 x >= y;
        for (int i = 30; i >= 0; i = minus(i, 1)) {
            if ((x >> i) >= y) {
                // 这个比特位一定是1
                res |= (1 << i);
                // x 减去 y << i;
                x = minus(x, y << i);
            }
        }
        // isNegavit(a) != isNegavit(b) 这个也可以用 isNegavit(a) ^ isNegavit(b) 来实现效果
        return isNegavit(a) != isNegavit(b) ? oppNum(res) : res;
    }
    // 除法的计算如果有系统最小值需要进行特殊判断(因为Integer.MAX_VALUE取反再+1得到的还是原来值), 也就是没办法计算相反数
    public static int divide(int a, int b) {
        if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
            // 如果两数都是系统最小值
            return 1;
        } else if (b == Integer.MIN_VALUE){
            // 如果除数为系统最小值
            return 0;
        } else if (a == Integer.MIN_VALUE) {
            // 被除数为系统最小值

            // 且除数为-1
            if (b == oppNum(1)) {
                // 答案应该是 Integer.MAX_VALUE + 1 的这个值的, 但力扣系统返回 Integer.MAX_VALUE 就行了
                return Integer.MAX_VALUE;
            } else {
                // 系统最小值没法取相反数计算
                // 1. c = (Integer.MAX_VALUE + 1) / b , 先让系统最小值 +1 后再除以 b
                // 2. (Integer.MAX_VALUE - c * b) / b
                // 3. 再将 1 和 2 的结果相加节课
                int c = div(add(a, 1), b);
                return add(c, div(minus(a, mulit(c, b)), b));
            }
        } else {
            // 两数都不是系统最小值
            return div(a, b);
        }
    }
}

当然, 按照原本的题意, 只是不能使用乘法, 除法和取余运算, 其他可以正常使用, 代码就简单了不少, 思路是一样的, 代码实现如下:

class Solution29 {
    public static boolean isNegavit(int num) {
        return num < 0;
    }
    public static int oppNum (int num) {
        return (~num) + 1;
    }

    public static int mulit (int a, int b) {
        // 就和小学手算十进制类似
        // 让 a 的每一位去乘 b 的每一位
        int res = 0;
        while (b != 0) {
            if ((b & 1) != 0) {
                res += a;
            }
            a <<= 1;
            // 要注意 b 必须是无符号右移
            b >>>= 1;
        }
        return res;
    }
    public static int div(int a, int b) {
        // 这里的除法一定要保证两个数都是正数, 如果有负数在计算逻辑最后加上即可
        int x = isNegavit(a) ? oppNum(a) : a;
        int y = isNegavit(b) ? oppNum(b) : b;
        int res = 0;
        // 计算的是 x / y
        // 每次循环 y 向左移动到和 x 最接近的值, 但要满足 y <= x, 如果是这个条件下, 也就是让 y 左移, 可能会溢出到符号位, 就可能会出错
        // 实际上将 x 向右移动到和 y 最接近的值, 移动的位数和上面也是一样的, 不过要满足 x >= y;
        for (int i = 30; i >= 0; i--) {
            if ((x >> i) >= y) {
                // 这个比特位一定是1
                res |= (1 << i);
                // x 减去 y << i;
                x -= (y << i);
            }
        }
        // isNegavit(a) != isNegavit(b) 这个也可以用 isNegavit(a) ^ isNegavit(b) 来实现效果
        return isNegavit(a) != isNegavit(b) ? oppNum(res) : res;
    }

    // 除法的计算如果有系统最小值需要进行特殊判断(因为Integer.MAX_VALUE取反再+1得到的还是原来值), 也就是没办法计算相反数
    public static int divide(int a, int b) {
        if (a == Integer.MIN_VALUE && b == Integer.MIN_VALUE) {
            // 如果两数都是系统最小值
            return 1;
        } else if (b == Integer.MIN_VALUE) {
            // 如果除数为系统最小值
            return 0;
        } else if (a == Integer.MIN_VALUE) {
            // 被除数为系统最小值

            // 且除数为-1
            if (b == -1) {
                // 答案应该是 Integer.MAX_VALUE + 1 的这个值的, 但力扣系统返回 Integer.MAX_VALUE 就行了
                return Integer.MAX_VALUE;
            } else {
                // 系统最小值没法取相反数计算
                // 1. c = (Integer.MAX_VALUE + 1) / b , 先让系统最小值 +1 后再除以 b
                // 2. (Integer.MAX_VALUE - c * b) / b
                // 3. 再将 1 和 2 的结果相加节课
                int c = div(a + 1, b);
                return c + ((a - mulit(c, b)) / b);
            }
        } else {
            // 两数都不是系统最小值
            return div(a, b);
        }
    }
}

上述代码都是可以通过OJ的

只使用位运算实现加减乘除文章来源地址https://www.toymoban.com/news/detail-461139.html

到了这里,关于只使用位运算实现加减乘除的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • linux|shell编程|shell脚本内的加减乘除运算实现示例

    shell脚本内的加减乘除是由于在编写kubernetes巡检脚本的时候,某些部分需要做一点简单的运算,突然发现我其实对这些不太熟悉。 因此,查阅了一些资料,现在就加减乘除运算在shell脚本内如何应用做一个简单的总结,写的不对的地方请各位轻点喷 首先,我们看一个错误的示

    2024年02月17日
    浏览(45)
  • 前端vue项目使用Decimal.js做加减乘除求余运算

    运算结果是Decimal对象,需要使用.toNumber()转为数字

    2024年04月13日
    浏览(60)
  • Rust 复数运算,重载加减乘除运算

    复数定义 由实数部分和虚数部分所组成的数,形如a+bi 。 其中a、b为实数,i 为“虚数单位”,i² = -1,即虚数单位的平方等于-1。 a、b分别叫做复数a+bi的实部和虚部。 当b=0时,a+bi=a 为实数; 当b≠0时,a+bi 又称虚数; 当b≠0、a=0时,bi 称为纯虚数。 实数和虚数都是复

    2024年02月13日
    浏览(38)
  • C语言加减乘除运算

    加减乘除是常见的数学运算,C语言当然支持,不过,C语言中的运算符号与数学中的略有不同,请见下表。 加法 减法 乘法 除法 求余数(取余) 数学 + - × ÷ 无 C语言 + - * / % C语言中的加号、减号与数学中的一样,乘号、除号不同;另外C语言还多了一个求余数的运算符,就是

    2024年02月06日
    浏览(44)
  • 图像四则运算(加减乘除)

    实验目的: 1.了解图像的算术运算在数字图像处理中的初步应用。 2.体会图像算术运算处理的过程和处理前后图像的变化。 3.能够实现简单的图像处理 实验原理: 图像的代数运算包括加,减,乘,除,这些运算的主要对象是图像数据块中的数据。这四种代数运算可以由如

    2024年02月08日
    浏览(52)
  • Pytorch入门:Tensor加减乘除矩阵运算

    若张量维数大于2,则对最后两维进行matmul。进行此运算的要求是张量a与b除最后两维外的其他维必须一致:

    2024年02月12日
    浏览(46)
  • Rust 重载运算符|复数结构的“加减乘除”四则运算

    复数定义 由实数部分和虚数部分所组成的数,形如a+bi 。 其中a、b为实数,i 为“虚数单位”,i² = -1,即虚数单位的平方等于-1。 a、b分别叫做复数a+bi的实部和虚部。 当b=0时,a+bi=a 为实数; 当b≠0时,a+bi 又称虚数; 当b≠0、a=0时,bi 称为纯虚数。 实数和虚数都是复

    2024年02月13日
    浏览(56)
  • JAVA中char类型加减乘除运算表达式返回类型

    我们都知道java中,如果char类型和int类型做加减法,那么char类型会被精度提升至int类型然后参与运算,返回的也是int类型的数据。 那么如果表达式中参与运算的 均为char类型 ,那么表达式返回的类型是什么呢? 经过简单测试,是 int类型 。 这个问题是在调用StringBuilder.appen

    2024年02月08日
    浏览(59)
  • 【加强版】小学数学出题,加减乘除混合运算,支持自定义数字,一键打印

    在线预览:在线HTML代码预览和运行工具 - UU在线工具   复制下面代码后到该地址预览即可  注意: 在线预览不能打印 。如需打印,在电脑本地上新建文本文档,粘贴代码后保存,然后把文件后缀改为.html运行,出题点击打印就可以了 新增功能: 1、支持加减乘除运算混合多

    2024年01月17日
    浏览(47)
  • 【ARMv8 SIMD和浮点指令编程】浮点加减乘除指令——四则运算

    浮点指令有专门的加减乘除四则运算指令,比如 FADD、FSUB、FMUL、FDIV 等。 1 FADD (scalar) 浮点加法(标量)。该指令将两个源 SIMDFP 寄存器的浮点值相加,并将结果写入目标 SIMDFP 寄存器。 该指令可以产生浮点异常。根据 FPCR 中的设置,异常会导致在 FPSR 中设置标志,或者生成同

    2024年02月05日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包