Appearance
JavaScript 二进制与位运算详解
导航目录
核心概念
二进制是计算机的基础,理解二进制和位运算对于前端开发者来说非常重要。JavaScript 中的数字都是以双精度浮点数(64 位)的形式存储,但在进行位运算时会转换为32 位有符号整数。
位运算符速查表
| 符号 | 名称 | 运算规则 | 示例 |
|---|---|---|---|
& | 与(AND) | 两个位都为 1 时,结果才为 1 | 5 & 3 = 1 (101 & 011 = 001) |
| | 或(OR) | 两个位都为 0 时,结果才为 0 | 5 | 3 = 7 (101 | 011 = 111) |
^ | 异或(XOR) | 两个位相同为 0,相异为 1 | 5 ^ 3 = 6 (101 ^ 011 = 110) |
~ | 取反(NOT) | 0 变 1,1 变 0(包括符号位) | ~5 = -6 |
<< | 左移 | 各二进制位全部左移若干位,高位丢弃,低位补 0 | 5 << 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; // 0xFFAA553. 哈希函数
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")); // 991623224. 判断 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)为什么使用补码?
- 统一加减法:减法可以转换为加法
- 解决 +0 和 -0 问题:补码中 0 的表示唯一
- 多表示一个最小负数:例如 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原因分析
- 十进制小数转换为二进制时,有些数会变成无限循环小数
- 0.1 和 0.2 在二进制中都是无限循环的
- 存储时会被截断到 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...0JavaScript 数值范围
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最佳实践与注意事项
- 位运算会截断小数:位运算前会先将数字转换为 32 位整数,小数部分会被舍弃
- 注意有符号和无符号的区别:特别是处理负数时
- 权限系统使用位运算:可以高效地组合和检查多个布尔标志
- 浮点数比较使用误差范围:不要直接比较浮点数是否相等
javascript
// 浮点数比较的正确方式
function isEqual(num1, num2, epsilon = 1e-10) {
return Math.abs(num1 - num2) < epsilon;
}
console.log(isEqual(0.1 + 0.2, 0.3)); // true