通过位运算做整数乘法

416 阅读5分钟

「这是我参与2022首次更文挑战的第13天,活动详情查看:2022首次更文挑战

前言

笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。

系列文章收录《算法》专栏中。

力扣上目前没有此题,是笔者自己的扩展。

问题描述

通过位运算做整数乘法,求两个整数的乘积,要求完全使用“&“、“|”、“^”、“~”、“<<”、“>>”位运算。

示例:

输入: a = 2, b = 2
输出: 4

提示:

  • ab 均可能是负数或 0
  • 结果不会溢出 32 位整数

确定学习目标

对《通过位运算做整数乘法》的算法过程进行剖析。

剖析

原始十进制分析

这个算法也是没有学习成本的,除了简单的位运算没有其他新知识。可以先想下我们平时进行十进制乘法运算的时候是怎么处理的,比如53*15:

  1. 先用5*53=265,先记下暂时结果为265。
  2. 再用1*53=53,这时候因为1是在十位上所以53需要往右移动一位,其实就当于530,把前面的暂时结果265和530相加为795得到新的暂时结果。
  3. 最后发现乘数15没有位可以和乘数进行相乘了,那么就停止,暂时结果直接作为正式结果返回。

下面我们总结成通用的步骤

总结

  1. 从乘数的个位开始,去乘以被乘数,把乘积作为暂时结果。
  2. 把乘数往右移动一位(舍去低位上面一步已经拿到了暂时结果),去乘以被乘数,把乘积往右移动一位(就相当于上面的53往右移动一位就是530)和第一步的暂时结果相加得到新的暂时结果。
  3. 重复步骤2,直到乘数右移没有位可以舍弃,那么就停止,暂时结果直接作为正式结果返回。

上面是正常情况下的乘法步骤。如果乘数和被乘数有一个存在0就直接返回结果0。如果存在一个是负数就先用正数进行,最后结果取反。如果两个都是负数,那么直接用对应的正数进行,最后结果不用取反。

二进制

image.png

如上图二进制的步骤和十进制类似,甚至更好的地方不用和b的各个位进行相乘直接对被乘数进行移位,加法参考我的《二进制加法》,我们直接给出总结:

  1. 从乘数的个位开始,把被乘数作为暂时结果。
  2. 把乘数往右移动一位(舍去低位上面一步已经拿到了暂时结果),把被乘数往右移动一位,如果乘数的最低位不为0那么把移动好的被乘数和第一步的暂时结果相加得到新的暂时结果。
  3. 重复步骤2,直到乘数右移没有位可以舍弃,那么就停止,暂时结果直接作为正式结果返回。

上面是正常情况下的乘法步骤。如果乘数和被乘数有一个存在0就直接返回结果0。如果存在一个是负数就先用正数进行,最后结果取反。如果两个都是负数,那么直接用对应的正数进行,最后结果不用取反。

下面直接上代码

代码

/**
 * 1. a和b异或位运算得出每位抛去进位的结果。
 * 2. a和b与位运算再左移1位,得出进位结果
 * 3. 设进位结果为b,a为每位抛去进位的结果,如果b!=0就重复步骤1和2。
 * <p>
 * 溢出的话就是只表示允许表示的位的结果(比如int 直接从右往左取32位)
 *
 * @param a
 * @param b
 * @return
 */
public static int add(int a, int b) {
    int sum = a;
    while (b != 0) {
        sum = a ^ b;
        int carry = (a & b) << 1;
        a = sum;
        b = carry;
    }

    return sum;
}

/**
 * 乘法:
 * 1. 从乘数的个位开始,把被乘数作为暂时结果。
 * 2. 把乘数往右移动一位(舍去低位上面一步已经拿到了暂时结果),把被乘数往右移动一位,如果乘数的最低位不为0那么把移动好的被乘数和第一步的暂时结果相加得到新的暂时结果。
 * 3. 重复步骤2,直到乘数右移没有位可以舍弃,那么就停止,暂时结果直接作为正式结果返回。
 * <p>
 * 上面是正常情况下的乘法步骤。如果乘数和被乘数有一个存在0就直接返回结果0。如果存在一个是负数就先用正数进行,最后结果取反。如果两个都是负数,那么直接用对应的正数进行,最后结果不用取反。
 *
 * @param a 被乘数
 * @param b 乘数
 * @return
 */
public static int multiply(int a, int b) {
    // 结果先设为0
    int result = 0;
    // a和b其中一个位0就直接返回0
    if (a == 0 || b == 0) {
        return result;
    }
    //结果先设为不用取反
    boolean isNegate = false;
    //如果a或者b位0就把isNegate置位true
    if (a < 0 || b < 0) {
        isNegate = true;
    }
    //如果a小于0就取反,我们最终需要对正数进行乘法运算
    if (a < 0) {
        a = -a;
    }
    //如果b小于0就取反,我们最终需要对正数进行乘法运算
    if (b < 0) {
        b = -b;
    }

    //这里需要判断b是否等于0,下面会对b进行右移舍弃低位,最终b会等于0停止更新"暂时结果"
    while (b != 0) {
        //只有b的最低位不等于0才对结果进行更新,不然a,b只移位
        if ((b & 0x1) != 0) {
            result = add(a, result);
        }
        //乘数向右移动,逐渐舍弃低位
        b >>= 1;
        // 被乘数需要向右边移动,配合乘数向右移动,需要进位
        a <<= 1;
    }

    //结果需要取反就取反
    if (isNegate) {
        result = -result;
    }
    return result;
}