Skip to content

JavaScript 二进制与位运算详解

导航目录

核心概念

二进制是计算机的基础,理解二进制和位运算对于前端开发者来说非常重要。JavaScript 中的数字都是以双精度浮点数(64 位)的形式存储,但在进行位运算时会转换为32 位有符号整数

位运算符速查表

符号名称运算规则示例
&与(AND)两个位都为 1 时,结果才为 15 & 3 = 1 (101 & 011 = 001)
|或(OR)两个位都为 0 时,结果才为 05 | 3 = 7 (101 | 011 = 111)
^异或(XOR)两个位相同为 0,相异为 15 ^ 3 = 6 (101 ^ 011 = 110)
~取反(NOT)0 变 1,1 变 0(包括符号位)~5 = -6
<<左移各二进制位全部左移若干位,高位丢弃,低位补 05 << 2 = 20 (101 → 10100)
>>有符号右移各二进制位全部右移若干位,低位丢弃,高位补符号位-20 >> 2 = -5
>>>无符号右移各二进制位全部右移若干位,低位丢弃,高位补 0-20 >>> 2 = 1073741819

位运算详解

& 与运算(AND)

运算规则:两个位都为 1 时,结果才为 1

javascript
// 真值表
// 0 & 0 = 0
// 0 & 1 = 0
// 1 & 0 = 0
// 1 & 1 = 1

// 实际计算
console.log(5 & 3); // 1
// 5 = 101 (二进制)
// 3 = 011 (二进制)
// --------
//     001 = 1

// 实用技巧:判断奇偶性
// 如果最低位是 1,则是奇数;如果是 0,则是偶数
console.log(12 & 1); // 0,偶数
console.log(13 & 1); // 1,奇数

// 实用技巧:取低位掩码
console.log(0b10101111 & 0b00001111); // 15 (取低4位)

| 或运算(OR)

运算规则:两个位都为 0 时,结果才为 0

javascript
// 真值表
// 0 | 0 = 0
// 0 | 1 = 1
// 1 | 0 = 1
// 1 | 1 = 1

// 实际计算
console.log(5 | 3); // 7
// 5 = 101 (二进制)
// 3 = 011 (二进制)
// --------
//     111 = 7

// 实用技巧:取整(向下取整)
console.log(100.11 | 0); // 100
console.log(-100.11 | 0); // -100

// 实用技巧:设置标志位
const READ = 1; // 001
const WRITE = 2; // 010
const EXECUTE = 4; // 100

const permission = READ | WRITE; // 011 = 3

^ 异或运算(XOR)

运算规则:两个位相同为 0,相异为 1

javascript
// 真值表
// 0 ^ 0 = 0
// 0 ^ 1 = 1
// 1 ^ 0 = 1
// 1 ^ 1 = 0

// 实际计算
console.log(5 ^ 3); // 6
// 5 = 101 (二进制)
// 3 = 011 (二进制)
// --------
//     110 = 6

// 重要性质
// 1. 归零律:a ^ a = 0
console.log(100 ^ 100); // 0

// 2. 恒等律:a ^ 0 = a
console.log(100 ^ 0); // 100

// 3. 交换律:a ^ b = b ^ a
// 4. 结合律:(a ^ b) ^ c = a ^ (b ^ c)

// 实用技巧:交换两个变量的值(不使用临时变量)
let a = 10;
let b = 20;

a = a ^ b; // a = 10 ^ 20
b = b ^ a; // b = 20 ^ (10 ^ 20) = 10
a = a ^ b; // a = (10 ^ 20) ^ 10 = 20

console.log(a); // 20
console.log(b); // 10

// ES6 更简洁的写法
// [a, b] = [b, a];

~ 取反运算(NOT)

运算规则:0 变 1,1 变 0(包括符号位)

javascript
// 取反运算等于 -(n + 1)
console.log(~0); // -1
console.log(~1); // -2
console.log(~5); // -6
console.log(~-1); // 0

// 实用技巧:判断数组中是否存在某个值
const arr = [1, 2, 3];
if (~arr.indexOf(2)) {
  console.log("存在"); // 执行
}
// 等价于
if (arr.indexOf(2) !== -1) {
  console.log("存在");
}

// 现代推荐写法
if (arr.includes(2)) {
  console.log("存在");
}

位移运算

<< 左移运算符

运算规则:各二进制位全部左移若干位,高位丢弃,低位补 0

javascript
// 左移 n 位相当于乘以 2^n
console.log(5 << 2); // 20  (5 * 4)
console.log(3 << 3); // 24  (3 * 8)
console.log(10 << 1); // 20  (10 * 2)

// 5 的二进制: 00000000000000000000000000000101
// 左移2位后:  00000000000000000000000000010100 = 20

>> 有符号右移运算符

运算规则:各二进制位全部右移若干位,低位丢弃,高位补符号位(正数补 0,负数补 1)

javascript
// 右移 n 位相当于除以 2^n(向下取整)
console.log(20 >> 2); // 5   (20 / 4)
console.log(100 >> 3); // 12  (100 / 8,向下取整)

// 负数右移
console.log(-20 >> 2); // -5
// -20 的二进制(补码): 11111111111111111111111111101100
// 右移2位后:            11111111111111111111111111111011 = -5

>>> 无符号右移运算符

运算规则:各二进制位全部右移若干位,低位丢弃,高位补 0

javascript
// 正数的有符号右移和无符号右移结果相同
console.log(1000 >> 8); // 3
console.log(1000 >>> 8); // 3

// 负数的结果不同
console.log(-1000 >> 8); // -4
console.log(-1000 >>> 8); // 16777212

// -1000 的二进制(补码): 11111111111111111111110000011000
// 无符号右移8位后:        00000000111111111111111111110000 = 16777212

注意

无符号右移会将负数变成很大的正数,因为高位补 0。


进制转换

十进制转二进制

整数部分:除 2 取余,直到商为 0,最先得到的余数是最低位,最后得到的余数是最高位。

小数部分:乘 2 取整,直到积为 0 或者达到精度要求为止,最先得到的整数是高位。

示例:将 7.75 转换为二进制

整数部分 7:
7 ÷ 2 = 3 余 1
3 ÷ 2 = 1 余 1
1 ÷ 2 = 0 余 1
从下往上读:111

小数部分 0.75:
0.75 × 2 = 1.5  取整 1
0.5  × 2 = 1.0  取整 1
从上往下读:.11

结果:(7.75)₁₀ = (111.11)₂

二进制转十进制

方法:按权展开,加权求和

示例:将 111.11 转换为十进制

整数部分:
1×2² + 1×2¹ + 1×2⁰ = 4 + 2 + 1 = 7

小数部分:
1×2⁻¹ + 1×2⁻² = 0.5 + 0.25 = 0.75

结果:7 + 0.75 = 7.75

八进制与二进制互转

规则:一个八进制位对应三个二进制位

javascript
// 八进制转二进制
let a = 016; // 八进制 16
// 1 = 001, 6 = 110
// 结果:001 110

let b = 0123; // 八进制 123
// 1 = 001, 2 = 010, 3 = 011
// 结果:001 010 011

// 二进制转八进制
let c = 0b001110; // 001 110 => 016
let d = 0b001010011; // 001 010 011 => 0123

十六进制与二进制互转

规则:一个十六进制位对应四个二进制位

javascript
// 十六进制转二进制
let a = 0x1a; // 十六进制 1a
// 1 = 0001, a(10) = 1010
// 结果:0001 1010

let b = 0x1f; // 十六进制 1f
// 1 = 0001, f(15) = 1111
// 结果:0001 1111

// 二进制转十六进制
let c = 0b00011010; // 0001 1010 => 0x1a
let d = 0b00011111; // 0001 1111 => 0x1f

实际应用场景

1. 权限控制系统

使用位运算可以高效地管理权限组合:

javascript
// 定义权限常量(2的幂次方,确保只有一位是1)
const Permission = {
  READ: 1 << 0, // 0001 = 1
  WRITE: 1 << 1, // 0010 = 2
  EXECUTE: 1 << 2, // 0100 = 4
  DELETE: 1 << 3, // 1000 = 8
};

// 组合权限(使用或运算)
const userPermission = Permission.READ | Permission.WRITE; // 0011 = 3
const adminPermission =
  Permission.READ | Permission.WRITE | Permission.EXECUTE | Permission.DELETE; // 1111 = 15

// 检查权限(使用与运算)
function hasPermission(userPerm, perm) {
  return (userPerm & perm) === perm;
}

console.log(hasPermission(userPermission, Permission.READ)); // true
console.log(hasPermission(userPermission, Permission.EXECUTE)); // false

// 添加权限(使用或运算)
let zhangsan = Permission.READ;
zhangsan = zhangsan | Permission.WRITE; // 添加写权限

// 移除权限(使用与运算+取反)
zhangsan = zhangsan & ~Permission.READ; // 移除读权限

// 切换权限(使用异或运算)
zhangsan = zhangsan ^ Permission.WRITE; // 切换写权限(有则移除,无则添加)

2. 颜色值处理

javascript
// 提取 RGB 颜色分量
const color = 0xffaa55; // 橙色

const red = (color >> 16) & 0xff; // 255
const green = (color >> 8) & 0xff; // 170
const blue = color & 0xff; // 85

// 组合 RGB 颜色
const newColor = (red << 16) | (green << 8) | blue; // 0xFFAA55

3. 哈希函数

javascript
// 简单的字符串哈希函数
function hashCode(str) {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i);
    hash = (hash << 5) - hash + char;
    hash = hash & hash; // 转换为32位整数
  }
  return hash;
}

console.log(hashCode("hello")); // 99162322

4. 判断 2 的幂次方

javascript
// 2的幂次方的二进制特点:只有一位是1
// 4  = 100
// 8  = 1000
// 16 = 10000

function isPowerOfTwo(n) {
  return n > 0 && (n & (n - 1)) === 0;
}

console.log(isPowerOfTwo(4)); // true
console.log(isPowerOfTwo(8)); // true
console.log(isPowerOfTwo(10)); // false

计算机中的数值表示

原码、反码、补码

计算机使用补码来表示有符号数,这样可以统一加减法运算。

表示方式正数负数
原码符号位为 0,数值位不变符号位为 1,数值位不变
反码与原码相同符号位不变,数值位取反
补码与原码相同反码 + 1
示例:+7 和 -7 的表示(假设4位)

+7:
原码:0111
反码:0111
补码:0111

-7:
原码:1111(符号位1,数值位111)
反码:1000(符号位不变,数值位取反)
补码:1001(反码 + 1)

为什么使用补码?

  1. 统一加减法:减法可以转换为加法
  2. 解决 +0 和 -0 问题:补码中 0 的表示唯一
  3. 多表示一个最小负数:例如 8 位可以表示 -128 ~ 127
javascript
// 验证:-1 的补码表示
// -1 的原码:10000001
// -1 的反码:11111110
// -1 的补码:11111111 = 255(无符号)

const uint8 = new Uint8Array(1);
uint8[0] = -1;
console.log(uint8[0]); // 255

浮点数精度问题

0.1 + 0.2 ≠ 0.3

JavaScript 使用 IEEE 754 双精度浮点数(64 位)表示数字,这会导致精度问题。

javascript
console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false

原因分析

  1. 十进制小数转换为二进制时,有些数会变成无限循环小数
  2. 0.1 和 0.2 在二进制中都是无限循环的
  3. 存储时会被截断到 52 位有效数字,导致精度丢失
0.1 的二进制表示:
0.1 × 2 = 0.2  → 0
0.2 × 2 = 0.4  → 0
0.4 × 2 = 0.8  → 0
0.8 × 2 = 1.6  → 1
0.6 × 2 = 1.2  → 1
0.2 × 2 = 0.4  → 0 (开始循环)
...

0.1 = 0.0001100110011001100110011001100110011001100110011001101...(无限循环)

解决方案

javascript
// 方法1:使用 toFixed 后转数字
const sum = 0.1 + 0.2;
console.log(parseFloat(sum.toFixed(10))); // 0.3

// 方法2:使用整数运算
function add(num1, num2) {
  const precision = Math.max(
    (num1.toString().split(".")[1] || "").length,
    (num2.toString().split(".")[1] || "").length
  );
  const multiplier = Math.pow(10, precision);
  return (num1 * multiplier + num2 * multiplier) / multiplier;
}

console.log(add(0.1, 0.2)); // 0.3

// 方法3:使用专门的库(如 decimal.js、big.js)

IEEE 754 双精度浮点数结构

JavaScript 的 Number 类型使用 64 位存储:

部分位数说明
符号位1 位0 表示正数,1 表示负数
指数位11 位偏移量为 1023,范围 -1022 ~ +1023
尾数位52 位实际精度为 53 位(隐含前导 1)
实际值 = (-1)^符号位 × 1.尾数位 × 2^(指数位-1023)

示例:5.5 的存储
5.5 = 101.1 = 1.011 × 2²
符号位:0(正数)
指数位:2 + 1023 = 1025 = 10000000001
尾数位:011000...0

JavaScript 数值范围

javascript
// 最大安全整数
console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991 (2^53 - 1)

// 最小安全整数
console.log(Number.MIN_SAFE_INTEGER); // -9007199254740991 (-(2^53 - 1))

// 最大正数
console.log(Number.MAX_VALUE); // 1.7976931348623157e+308

// 最小正数
console.log(Number.MIN_VALUE); // 5e-324

// 判断是否为安全整数
console.log(Number.isSafeInteger(9007199254740991)); // true
console.log(Number.isSafeInteger(9007199254740992)); // false

最佳实践与注意事项

  1. 位运算会截断小数:位运算前会先将数字转换为 32 位整数,小数部分会被舍弃
  2. 注意有符号和无符号的区别:特别是处理负数时
  3. 权限系统使用位运算:可以高效地组合和检查多个布尔标志
  4. 浮点数比较使用误差范围:不要直接比较浮点数是否相等
javascript
// 浮点数比较的正确方式
function isEqual(num1, num2, epsilon = 1e-10) {
  return Math.abs(num1 - num2) < epsilon;
}

console.log(isEqual(0.1 + 0.2, 0.3)); // true