位运算、补码、反码和原码是计算机中用于表示和处理二进制数的概念。掌握位运算、补码、反码和原码这些概念对WEB开发人员来说虽然不是必需的,但了解它们可以在某些特定情况下提供一些优势和应用,比如位运算优化、图像处理、数据加密和编码、网络协议和数据传输等。
位运算
位运算是对二进制数的位进行操作的运算方式,包括按位与(&)、按位或(|)、按位异或(^)、按位取反(~)、左移(<<)和右移(>>)操作符等。
按位与(&)
将两个数的对应位进行与操作,结果为1的位表示两个数对应位都为1。
javascript
let a = 5; // 二进制表示为 0101
let b = 3; // 二进制表示为 0011
let result1 = a & b; // 结果为 0001,即十进制的 1
console.log(result1);
按位或(|)
将两个数的对应位进行或操作,结果为1的位表示两个数对应位至少有一个为1。
javascript
let a = 5; // 二进制表示为 0101
let b = 3; // 二进制表示为 0011
let result2 = a | b; // 结果为 0111,即十进制的 7
console.log(result2);
按位异或(^)
将两个数的对应位进行异或操作,结果为1的位表示两个数对应位不相同。
javascript
let a = 5; // 二进制表示为 0101
let b = 3; // 二进制表示为 0011
let result3 = a ^ b; // 结果为 0110,即十进制的 6
console.log(result3);
按位取反(~)
对一个数的每个位取反,即0变为1,1变为0。
javascript
let a = 5; // 二进制表示为 0101
let result4 = ~a; // 结果为 1010,即十进制的 -6
console.log(result4);
左移(<<)操作符
左移操作符将一个数的所有位向左移动指定的位数,右侧空出的位用0填充。
javascript
let num = 5; // 二进制表示为 00000101
let result = num << 2; // 左移2位,结果为 00010100,即十进制的 20
console.log(result);
左移(<<)操作符的规律
- 对于一个数 x,左移 n 位相当于 x 乘以 2 的 n 次方。 如:
x << n
等价于x * (2 ** n)
- 左移(<<)操作可以用于快速计算一个数的2的指定次幂的结果,相当于进行乘法运算。
右移(>>)操作符
右移操作符将一个数的所有位向右移动指定的位数,左侧空出的位根据数的符号进行填充:
- 对于正数,空出的位用0填充。
- 对于负数,空出的位用1填充。
javascript
let num = -10; // 二进制表示为 11110110
let result = num >> 2; // 右移2位,结果为 11111101,即十进制的 -3
console.log(result);
右移(>>)操作符的规律
-
对于一个数 x,右移 n 位相当于 x 除以 2 的 n 次方并向下取整。如:
x >> n
等价于Math.floor(x / (2 ** n))
-
右移(>>)操作可以用于快速计算一个数除以2的指定次幂并向下取整的结果,相当于进行除法运算。
原码、反码和补码
原码
原码是一种表示有符号整数的方式,其中最高位表示符号位(0表示正数,1表示负数),其余位表示数值的大小。
- 正数的原码和二进制表示相同,例如:+5 的原码是 00000101。
- 负数的原码表示在正数的原码基础上,将符号位改为1,例如:-5 的原码是 10000101。
反码
反码是在原码的基础上,对负数进行按位取反操作(符号位除外)。
- 正数的反码和原码相同,例如:+5 的反码是 00000101。
- 负数的反码是在对应的**正数反码的基础上,将符号位保持不变,其余位按位取反,**例如:-5 的反码是 11111010。
补码
补码是在反码的基础上,将负数的最高位加1。
- 正数的补码和原码相同,例如:+5 的补码是 00000101。
- 负数的补码是在对应的原码的基础上,将符号位保持不变,其余位按位取反后再加1,例如:-5 的补码是 11111011。
对于正数,原码、反码和补码是相同的,而对于负数,原码、反码和补码是不同的。
正负数的表达方式
- 正数的原码、反码和补码相同,符号位为0。
- 负数的原码、反码和补码不同,符号位为1。
计算负数的补码的步骤
当处理负数的补码时,可以按照以下步骤进行计算,下面是使用数字示例说明每个步骤:
1. 将负数的绝对值转换为二进制表示形式的反码
假设我们要计算 -5 的补码。
负数的绝对值转换为二进制表示形式的反码:5 的二进制表示形式为 00000101,因为是负数,所以取反得到 11111010。
2. 对反码的每一位(包括符号位)进行按位取反,得到补码
将反码的每一位进行按位取反:11111010 取反得到 00000101。
3. 补码的最高位(符号位)加1
将补码的最高位(符号位)加1:00000101 加1 得到 00000110。
因此,-5 的补码为 00000110。
为什么会有反码和补码
反码的出现主要是为了解决原码表示中存在的符号位处理问题。
在原码表示中,最高位表示符号位,0表示正数,1表示负数。但原码表示存在以下问题:
1. 零的表示不唯一:在原码中,+0和-0有不同的表示,即全零的原码和最高位为1的原码。这导致了零的表示不唯一,增加了运算和比较的复杂性。
2. 加法和减法运算需要额外的处理:在原码表示中,加法和减法运算需要对符号位和数值部分分别进行处理。这增加了运算的复杂性和成本。
反码的出现解决了上述问题:
1. 零的表示唯一:在反码中,+0和-0都表示为全零的反码,这消除了零的表示不唯一的问题。
2. 加法和减法运算的一致性:在反码中,加法和减法运算可以直接进行,无需额外的处理步骤。减法可以通过将减数取反(按位取反)转换为加法运算,从而使运算变得更加简洁和一致。
然而,反码表示仍然存在一个问题,即负数的表示不唯一。为了解决这个问题,补码的出现变得必要。
补码的出现主要是为了解决反码表示中负数表示不唯一的问题。
在补码表示中,负数的表示是通过对反码加1得到的。补码的出现解决了负数表示不唯一的问题,同时也带来了以下优势:
1. 零的表示唯一:在补码中,+0和-0都表示为全零的补码,消除了零的表示不唯一的问题。
2. 运算的一致性:在补码中,加法和减法运算可以直接进行,无需额外的处理步骤。补码的加法运算可以直接使用计算机的加法器,而减法运算可以通过将减数取反(按位取反,然后加1)转换为加法运算,从而使运算变得更加简洁和一致。
3. 负数的表示唯一:在补码中,负数的表示是唯一的,没有多个表示方式。这简化了负数的处理和运算。
示例:
假设使用8位补码表示有符号整数,考虑以下示例:
-
原码表示:
-3的原码为 10000011 +3的原码为 00000011
零的表示不唯一,+0为 00000000,-0为 10000000。
-
反码表示:
-3的反码为 11111100 +3的反码为 00000011
零的表示唯一,+0和-0都为 00000000。
-
补码表示:
-3的补码为 11111101 +3的补码为 00000011
零的表示唯一,+0和-0都为 00000000。负数的表示也是唯一的。
在补码表示中,加法和减法运算可以直接进行,例如:
+3 + (-3) 的补码表示为 00000011 + 11111101 = 00000000,得到正确的结果 0。
总结:反码的出现解决了原码表示中的符号位处理问题,消除了零的表示不唯一的问题。补码的出现进一步解决了反码表示中负数表示不唯一的问题,并带来了零的唯一表示和运算的一致性。
位运算的应用场景
位运算在JavaScript编程中有很多妙用的场景,列举了几个web框架源码和我们业务需求开发中比较可能用到的例子。
快速计算2的乘方
位运算可以快速判断一个数是否是2的乘方,并进行相关计算。
javascript
function isPowerOfTwo(num) {
return (num & (num - 1)) === 0;
}
function calculatePowerOfTwo(n) {
return 1 << n;
}
// 判断一个数是否是2的乘方
console.log(isPowerOfTwo(16)); // 输出: true
// 计算2的n次幂
console.log(calculatePowerOfTwo(4)); // 输出: 16
在上述示例中,使用位运算来判断一个数是否是2的乘方。通过 (num & (num - 1)) === 0
的判断条件,如果结果为true,则表示这个数是2的乘方。另外,通过 1 << n
可以快速计算出2的n次幂的值。
交换两个变量的值
位运算可以在不使用额外变量的情况下,交换两个变量的值。
javascript
function swapVariables(a, b) {
a = a ^ b;
b = a ^ b;
a = a ^ b;
return [a, b];
}
let x = 5;
let y = 10;
[x, y] = swapVariables(x, y);
console.log(x, y); // 输出: 10 5
在上述示例中,使用异或运算 ^
来实现变量值的交换。通过连续进行三次异或操作,可以在不使用额外变量的情况下交换两个变量的值。
判断奇偶性
位运算可以快速判断一个整数是奇数还是偶数。
javascript
function isEven(num) {
return (num & 1) === 0;
}
console.log(isEven(4)); // 输出: true
console.log(isEven(7)); // 输出: false
在上述示例中,使用位与运算 &
将整数与1进行与运算,如果结果为0,则表示该数是偶数,否则为奇数。
位运算在计算机编程中有许多应用场景,如以下示例所示:
位掩码
使用位运算来操作二进制位,掩码技术可以用于标志位的设置、清除和判断。
javascript
// 使用位掩码设置标志位
const FLAG_A = 1; // 00000001
const FLAG_B = 2; // 00000010
const FLAG_C = 4; // 00000100
let flags = 0; // 00000000
// 设置标志位
flags |= FLAG_A; // 00000001
flags |= FLAG_C; // 00000101
// 检查标志位是否被设置
if ((flags & FLAG_B) !== 0) {
console.log("标志位 B 被设置");
} else {
console.log("标志位 B 未被设置");
}
// 清除标志位
flags &= ~FLAG_A; // 00000100
在上述示例中,我们使用位运算来设置、检查和清除标志位。通过使用位掩码,我们可以在一个整数变量中存储多个标志位的状态。
位移操作
通过位移操作,可以快速进行乘法和除法的计算,或者进行二进制位的移动。
javascript
// 位移操作示例:乘法和除法
let num = 8;
// 左移相当于乘以2的幂
let result1 = num << 2; // 32 (8 * 2^2)
// 右移相当于除以2的幂
let result2 = num >> 1; // 4 (8 / 2^1)
// 位移操作示例:二进制位的移动
let value = 0b1010;
// 向左移动两位
let shiftedLeft = value << 2; // 0b101000 (40)
// 向右移动一位
let shiftedRight = value >> 1; // 0b101 (5)
上述示例展示了位移操作的应用。通过左移和右移操作,我们可以快速进行乘法和除法的计算,并且可以将二进制位向左或向右移动。
总的来说,位运算、原码、反码和补码这些知识点是我们大学计算机基础课程中必修课程,我相信多数人跟我一样还给老师了。我们在实际业务开发中主要是位运算会使用到,尤其是在框架源码中会被经常看到使用。掌握这些知识点及妙用是可以在某些特定情况赋能业务的,作为知识储备也是可以的,保持学习,共勉~