位运算基本概念
位运算 | 运算规则 | 说明 |
---|---|---|
& 与运算 | 0 & x = 0 | 1. 清零:数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 |
运算
加法
- 算法描述:
- 求无进位加法结果 sum【按位异或^】
- 求进位 carry【按位与& 并左移一位<<】
- 如果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
}
- 在使用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) }
- 如果b = 1 >> 31
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))。