温故知新:位运算、原码、反码和补码及妙用

位运算、补码、反码和原码是计算机中用于表示和处理二进制数的概念。掌握位运算、补码、反码和原码这些概念对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位补码表示有符号整数,考虑以下示例:

  1. 原码表示:

    -3的原码为 10000011 +3的原码为 00000011

    零的表示不唯一,+0为 00000000,-0为 10000000。

  2. 反码表示:

    -3的反码为 11111100 +3的反码为 00000011

    零的表示唯一,+0和-0都为 00000000。

  3. 补码表示:

    -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)

上述示例展示了位移操作的应用。通过左移和右移操作,我们可以快速进行乘法和除法的计算,并且可以将二进制位向左或向右移动。

总的来说,位运算、原码、反码和补码这些知识点是我们大学计算机基础课程中必修课程,我相信多数人跟我一样还给老师了。我们在实际业务开发中主要是位运算会使用到,尤其是在框架源码中会被经常看到使用。掌握这些知识点及妙用是可以在某些特定情况赋能业务的,作为知识储备也是可以的,保持学习,共勉~

相关推荐
minDuck几秒前
ruoyi-vue集成tianai-captcha验证码
java·前端·vue.js
小政爱学习!21 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。26 分钟前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼32 分钟前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k093336 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning1 小时前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人1 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
超雄代码狂1 小时前
ajax关于axios库的运用小案例
前端·javascript·ajax
长弓三石2 小时前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙