JS 位运算

718 阅读10分钟

按位操作符

按位操作符 - JavaScript | MDN

将其操作数当作32位的比特序列(二进制表示)

运算符 用法 描述
按位与(AND) a & b 对于每一个比特位,只有两个操作数相应的比特位都是1时,结果才为1,否则为0
按位或(OR) a | b 对于每一个比特位,当两个操作数相应的比特位至少有一个1时,结果为1,否则为0
按位异或(XOR) a ^ b 对于每一个比特位,当两个操作数相应的比特位有且只有一个1时,结果为1,否则为0
按位非(NOT) ~ a 反转操作数的比特位,即0变成1,1变成0
左移 a << b 将 a 的二进制形式向左移 b (< 32) 比特位,右边用0填充
有符号右移 a >> b 将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位
无符号右移 a >>> b 将 a 的二进制表示向右移 b (< 32) 位,丢弃被移出的位,并使用 0 在左侧填充

有符号32位整数

所有的按位操作符的操作数都会被转成补码形式的有符号32位整数。
补码形式是指一个数的负对应值(如 5和-5)为数值的所有比特位反转后,再加1。
反转比特位即该数值进行’非‘位运算,也即该数值的反码。

314
00000000000000000000000100111010
~314
11111111111111111111111011000101
-314
11111111111111111111111011000110

补码保证了当一个数是正数时,其最左的比特位是0,当一个数是负数时,其最左的比特位是1。
因此,最左边的比特位被称为符号位。

0
00000000000000000000000000000000
-1
11111111111111111111111111111111
-2147483648 (-0x80000000)
10000000000000000000000000000000
2147483647 (0x7fffffff)
01111111111111111111111111111111

数字-2147483648 和 2147483647 是32位有符号数字所能表示的最小和最大整数。

& (按位与)

a b a & b
0 0 0
0 1 0
1 0 0
1 1 1
x & 0 = 0
x & -1 = x

9 & 14 = 8 // 1001 & 1110 => 1000

| (按位或)

a b a | b
0 0 0
0 1 1
1 0 1
1 1 1
x | 0 = x
x | -1 = -1

9 | 14 = 15 // 1001 | 1110 => 1111

1 | 0 // 1
1.6 | 0 // 1
0 | 0 // 0
-1 | 0 // -1
-1.9 | 0 // -1
[] | 0 // 0
{} | 0 // 0
'123456' | 0 // 123456
'abcdef' | 0 // 0
1.23e2 | 0 // 123
-1.23e3 | 0 // -1230

^ (按位异或)

a b a ^ b
0 0 0
0 1 1
1 0 1
1 1 0
x ^ 0 = x
x ^ -1 = ~x
x ^ x = 0

9 ^ 14 = 7 // 1001 ^ 1110 => 0111

~ (按位非)

a ~a
0 1
1 0
~x = -(x + 1)

9 // 00000000000000000000000000001001
~9 = -10 // ~00000000000000000000000000001001 => 11111111111111111111111111110110
10  // 00000000000000000000000000001010
-10 // 11111111111111111111111111110101 + 1 => 11111111111111111111111111110110

~str.indexOf(searchFor) // ~-1 === 0

<< (左移)

x << y = x * (2 ** y)

9 << 2 // 36  00000000000000000000000000001001 => 00000000000000000000000000100100
9 << 3 // 72

>> (有符号右移)

// 向右被移出的位被丢弃,拷贝最左侧的位以填充左侧
9 >> 2 // 2    00000000000000000000000000001001 => 00000000000000000000000000000010
-9 >> 2 // -3  11111111111111111111111111110111 => 11111111111111111111111111111101
-9 >> 0 // -9

>>> (无符号右移)

// 向右被移出的位被丢弃,左侧用0填充
9 >>> 2 // 2            00000000000000000000000000001001 => 00000000000000000000000000000010
-9 >>> 2 // 1073741821  11111111111111111111111111110111 => 00111111111111111111111111111101
-9 >>> 0 // 4294967287

应用

标志位和掩码

let flags = 5 // 0101

const FLAG_A = 1 // 0001
const FLAG_B = 2 // 0010
const FLAG_C = 4 // 0100
const FLAG_D = 8 // 1000

// | 设置新的掩码
let mask = FLAG_A | FLAG_B | FLAG_D // 0001 | 0010 | 1000 => 1011

// & 检查标志位
if (flags & FLAG_C) {}

if ((flags & FLAG_B) || (flags & FLAG_C)) {}
mask = FLAG_B | FLAG_C // 0010 | 0100 => 0110
if (flags & mask) {}

// | 设置标志位
mask = FLAG_C | FLAG_D // 0100 | 1000 => 1100
flags |= mask // 0101 | 1100 => 1101

// &~ 清除标志位
mask = ~(FLAG_A | FLAG_C) // ~0101 => 1010
flags &= mask // 1101 & 1010 => 1000
// 德摩根定律 ~(FLAG_A | FLAG_C) = ~FLAG_A & ~FLAG_C
mask = ~FLAG_A & ~FLAG_C // ~0001 & ~0100 => 1110 & 1011 => 1010

// ^ 切换标志位
mask = FLAG_B | FLAG_C // 0110
flags = 12 // 1100
flags ^= mask // 1100 ^ 0110 => 1010
flags ^= mask // 1010 ^ 0110 => 1100

flags = ~flags // ~1100 => 0011

转换片段

// 将一个二进制数的 String 转换为十进制的 Number
parseInt('1011', 2) // 11
// 将一个十进制的 Number 转换为二进制数的 String
(11).toString(2) // '1011'

自动化掩码创建

function createMask(...args) {
  const len = args.length < 32 ? args.length : 32
  let mask = 0
  for (let i = 0; i < len; i++) {
    mask |= args[i] << i
  }
  return mask
}

createMask(true, true, false, true) // 1011 => 11
createMask(false, false, true) // 100 => 4
createMask(true) // 1
createMask() // 0

逆算法:从掩码得到布尔数组

function arrayFromMask(mask) {
  // 0x7fffffff  => 01111111111111111111111111111111
  // -0x80000000 => 10000000000000000000000000000000
  if (mask > 0x7fffffff || mask < -0x80000000) {
    throw new TypeError('arrayFromMask - out of range')
  }
  const arr = []
  let shifted = mask
  while (shifted) {
    arr.push(Boolean(shifted & 1))
    shifted >>>= 1
  }
  return arr
}

arrayFromMask(11) // 1011 => [true, true, false, true]
arrayFromMask(4)  // 0100 => [false, false, true]
arrayFromMask(1)  // 1 => [true]
arrayFromMask()   // []
// 将掩码转换成二进制表示
function createBinaryString(mask = 0) {
  // 2147483647  => 01111111111111111111111111111111
  // -2147483648 => 10000000000000000000000000000000
  if (mask > 2147483647 || mask < -2147483648) {
    throw new TypeError('arrayFromMask - out of range')
  }
  let sMask = ''
  let shifted = mask
  for (let i = 0; i < 32; i++) {
    sMask += String(shifted >>> 31) // 记录最左侧一位的值 0|1
    shifted <<= 1 // 再删除最左侧一位
  }
  return sMask;
}

createBinaryString(11) // 00000000000000000000000000001011
createBinaryString(4)  // 00000000000000000000000000000100
createBinaryString(1)  // 00000000000000000000000000000001
createBinaryString()   // 00000000000000000000000000000000

判断奇偶性

  • 奇数 & 1 = 1
  • 偶数 & 1 = 0
  • 奇数 % 2 = 1
  • 偶数 % 2 = 0

取整 ~~ >> << >>> |

~~3.14 // 3
3.14 | 0 // 3
3.14 << 0 // 3
3.14 >> 0 // 3
3.14 >>> 0 // 3

~~3.9 // 3
3.9 | 0 // 3
3.9 << 0 // 3
3.9 >> 0 // 3
3.9 >>> 0 // 3

~~-3.14 // -3
-3.14 | 0 // -3
-3.14 << 0 // -3
-3.14 >> 0 // -3
-3.14 >>> 0 // 4294967293

~~-3.9 // -3
-3.9 | 0 // -3
-3.9 << 0 // -3
-3.9 >> 0 // -3
-3.9 >>> 0 // 4294967293

~~'abc' // 0
'abc' | 0 // 0
'abc' << 0 // 0
'abc' >> 0 // 0
'abc' >>> 0 // 0

~~[] // 0
[] | 0 // 0
[] << 0 // 0
[] >> 0 // 0
[] >>> 0 // 0

~~{} // 0
({}) | 0 // 0
({}) << 0 // 0
({}) >> 0 // 0
({}) >>> 0 // 0

计算

Math.pow(2, 3) // 8
2 ** 3 // 8
2 << 2 // 8 <= 2 * (2 ** 2)

3 * Math.pow(2, 3) // 24
3 << 3 // 24 <= 3 * (2 ** 3)

9 / 2 // 4.5
9 >> 1 // 4 <= ~~(9 / 2 ** 1)
9 / 4 // 2.25
9 >> 2 // 2 <= ~~(9 / 2 ** 2)

比较两个数是否相等

1100 ^ 1100 // === 0
1100 ^ 非1100 // !== 0

值交换

let a = 1
let b = 2
a ^= b
b ^= a
a ^= b
// -> a = 2, b = 1

判断值的正负

function isPos(n) {
  return n === (n >>> 0)
}

色值转换

function rgbToHex(rgb) {
  const arr = rgb.split(/[^\d]+/)
  const clr = arr[1] << 16 | arr[2] << 8 | arr[3]
  return '#' + clr.toString(16)
}

rgbToHex('rgb(255, 68, 0)') // #ff4400
rgbToHex('rgb(255, 255, 0)') // #ffff00
function hexToRGB(hex) {
  hex = hex.replace('#', '0x')
  const r = hex >> 16
  const g = hex >> 8 & 0xff
  const b = hex & 0xff
  return `rgb(${r}, ${g}, ${b})`
}

hexToRGB('#ff4400') // rgb(255, 68, 0)
hexToRGB('#ffff00') // rgb(255, 255, 0)

权限系统

  • | 赋予权限
  • & 校验权限
  • &~ 删除权限
  • ^ 切换权限
const r = 0b100 // 4
const w = 0b010 // 2
const x = 0b001 // 1
// 赋予权限 |
let user = r | w // 100 | 010 => 110
console.log(user.toString(2)) // 110
// 校验权限 &
console.log((user & r) === r) // true
console.log((user & w) === w) // true
console.log((user & x) === x) // false
// 删除权限 & (~)
user = user & (~r) // 110 & 011 = 010
console.log(user.toString(2)) // 10
console.log((user & r) === r) // false
// 切换权限 ^
user = user ^ r // 010 ^ 100 = 110
console.log(user.toString(2)) // 110
console.log((user & r) === r) // true
user = user ^ r // 110 ^ 100 = 010
console.log(user.toString(2)) // 10
console.log((user & r) === r) // false