利用位运算实现加减乘除

文章介绍了位运算的基本概念,包括与(&),或(|),异或(^),取反(~),左移(<<)和右移(>>)等运算,并详细阐述了如何使用位运算实现加法、减法、乘法和除法的算法。此外,还讨论了如何利用位运算来对比两个字符串是否相同,特别是在字符串只包含特定字符类型(如数字或字母)的情况下。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

位运算基本概念

runoob 位运算

位运算运算规则说明
& 与运算0 & x = 01. 清零:数n与0按位与
2. 取指定位:这些位为1,其余为全为0
3. 判断奇偶:a & 1 === 0
I 或运算1 I x = 1设置指定位为1:这些位为1,其余位全为0
^ 异或运算相同为0,不同为1
x ^ x = 0
x ^ 0 = 0
1. 无进位加法
2. 翻转指定位:这些位为1,其他位全为0
3. a ^ b可判断a,b是否同号
~ 取反运算补码的计算:取反+1
<< 左移运算低位补0,高位溢出相当于 *2
>> 右移运算低位溢出,无符号数高位补0,有符号数在逻辑右移中高位补0、在算数右移中高位补符号位相当于 /2

运算

加法

位运算实现加减乘除运算

  • 算法描述:
    1. 求无进位加法结果 sum【按位异或^】
    2. 求进位 carry【按位与& 并左移一位<<】
    3. 如果carry为0,sum为最终结果;否则重复步骤1~3,求和sum + carry
function add (a, b) {
    let sum = a ^ b
    let carry = (a & b) << 1
    while (carry) {
        a = sum
        b = carry
        sum = a ^ b
        carry = (a & b) << 1
    }
    return sum
}

减法

  • 算法描述:
    • a - b = a + (-b)
    • 负数在计算机中以补码的形式保存,对b按位取反后+1
function substract (a, b) {
    b = add(~b, 1) // -b
    return add(a, b)
}

乘法

  • 算法描述:
    • a * b,a 加b次的自己即可
    • 在二进制中,由于只有0和1,这种运算可以转化为:如果b[i]=0,跳过本次加法操作(加 0 );如果为1,加数为a左移一位
      在这里插入图片描述
function mutiply (a, b) {
    // 计算最终符号,并取绝对值
    let x = a < 0 ? add(~a, 1) : a
    let y = b < 0 ? add(~b, 1) : b

    let product = 0
    while (y) {
    
    	// product = add(product, x)
        // y = substract(y, 1)
        // 等效于
        
        if (y & 0x1) { // 最后一位为1
            product += x
        }
        x = x << 1

        // 在10进制中,下一个循环前,b需要-1
        // 在2进制中,表示该位已经处理完成,右移将最后一位更新为下一位
        y = y >> 1
    }

    // 还原符号位
    if ((a ^ b) < 0) {
        product = add(~product, 1)
    }

    return product

}

除法

  • 算法描述:
    • 思路和乘法一致,a减b,直到a < b
function divide(a, b) {
    let x = a > 0 ? a : add(~a, 1)
    let y = b > 0 ? b : add(~b, 1)

    let quotient = 0 // 商
    let remainder = 0 // 余数

    while (x >= y) {
        quotient = add(quotient, 1)
        x = substract(x, y)
    }

    // 确定商符号
    if ((a ^ b) < 0) {
        quotient = add(~quotient, 1)
    }

    // 计算余数, 余数符号只和除数符号相关
    remainder = b < 0 ? y : add(~y, 1)

    return quotient
}
  • 算法优化:
    • 上述算法,如果被除数很大,除数很小,每次减一个除数,会导致循环很多次。
    • 可以加大减法步长,即每次减一个小于被除数的最大倍数除数,在二进制中,可以通过左移运算扩大倍数。
function divide_v2(a, b) {
    let x = a > 0 ? a : add(~a, 1)
    let y = b > 0 ? b : add(~b, 1)

    let quotient = 0 // 商
    let remainder = 0 // 余数

    for(let i = 31; i >= 0; i--) {
        /* 这里要比较 x 与 y * (2^i)的关系
         * 但是y * (2^i)可能会溢出,所以比较 x / (2^i) 与 y的关系
         * 效果是一致的
         */
        if ((x >> i) >= y) {
            quotient = add(quotient, 1 << i) // 步长为 1 << i
            x = substract(x, y << i)
        }
    }

    // 确定商符号
    if ((a ^ b) < 0) {
        quotient = add(~quotient, 1)
    }
    // 计算余数
    remainder = b < 0 ? add(~y, 1) : y

    return quotient
}

ECMAScript 位运算符

  • 在使用js语言时,要注意:
    • js中使用64位保存数据,但是进行位运算时,会强制转化为32位。
    • 在上述代码中,如果a或b取值为 1 >> 31,求其补码后会得到 a = add(~a, 1)的异常结果,所以要对这种情况进行特殊讨论:
      • 如果b = 1 >> 31
        • a = 1 >> 31,则结果为1
        • 其余情况均为0
      if (b === MIN_NUM) {
          if (a === b) {
              return {
                  isSpecialCase: true,
                  result: 1
              }
          } else {
              return {
                  isSpecialCase: true,
                  result: 0
              }
          }
      }
      
      • 如果a = 1 >> 31且b !== 1 >> 31,先进行一次减法/加法运算,减小a的绝对值
      if (a === MIN_NUM) {
          a = b < 0 ? substract(a, b) : add(a, b)
          quotient = add(quotient, 1)
      }
      
function specialCases (a, b, MIN_NUM, MAX_NUM) {
    switch(a) {
        case 0:
            return {
                isSpecialCase: true,
                result: 0
            }
        case 1:
            if (b === 1 || b === -1) {
                return {
                    isSpecialCase: true,
                    result: b
                }
            } else {
                return {
                    isSpecialCase: true,
                    result: 0
                }
            }
        case MIN_NUM:
            if (b === 1) {
                return {
                    isSpecialCase: true,
                    result: a
                }
            } else if (b === -1) {
                return {
                    isSpecialCase: true,
                    result: MAX_NUM
                }
            }
            break
    }
    if (b === MIN_NUM) {
        if (a === b) {
            return {
                isSpecialCase: true,
                result: 1
            }
        } else {
            return {
                isSpecialCase: true,
                result: 0
            }
        }    
    } else if (b === 1) {
        return {
            isSpecialCase: true,
            result: a
        } 
    }
    return {
        isSpecialCase: false,
        result: undefined
    }
}
function divide(a, b) {
    const MIN_NUM = 1 << 31
    const MAX_NUM = add(~add(MIN_NUM, 1), 1)
    const {isSpecialCase, result} = specialCases(a, b, MIN_NUM, MAX_NUM)
    if (isSpecialCase) {
        return result
    }

    let quotient = 0 // 商

    if (a === MIN_NUM) {
        a = b < 0 ? substract(a, b) : add(a, b)
        quotient = add(quotient, 1)
    }

    let x = a > 0 ? a : add(~a, 1)
    let y = b > 0 ? b : add(~b, 1)

    for(let i = 31; i >= 0; i--) {
        if ((x >> i) >= y) {
            quotient = add(quotient, 1 << i) // 步长为 1 << i
            x = substract(x, y << i)
        }
    }

    // 确定商符号
    if ((a ^ b) < 0) {
        quotient = add(~quotient, 1)
    }

    return quotient
}

总结

加减乘除位运算思路小结

  • 加法:异或运算计算无进位的和(新加数1),按位与并左移一位计算进位(新加数2),直到进位为0。
  • 减法:对减数求补码(取反+1),转化为加法。
  • 乘法:
    • 统一两个乘数为正数
    • 乘数最后一位为1,结果中加上左移后的被乘数
    • 最后计算积的符号
  • 除法:注意js
    • 统一两个参数为正数
    • 每次减操作,利用移位运算(i = 31)找到小于被除数的除数最大倍数(if((x >> i) >= y))
    • 最后计算商的符号,余数符号只和除数符号有关

对比字符串是否相同

使用位运算求解单词长度的最大乘积

  • 如果字符串中只含有数字、或只含有小写字母、或只含有大写字母,可以把该字符串转换为掩码后,使用位运算进行对比。
  • 例如只含有数字,就是10位掩码;只含有小写字母或只含有大写字母,就是26位掩码。
  • 判断两个字符串是否相同,即判断两个掩码按位与后是否为0,即 if (!(mask1 & mask2))。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值