【回顾】原码、反码和补码

重新梳理回忆下原码、反码和补码相关的知识,自己总是记混

基本概念

  • 机器数
  • 真值
  • 原码
  • 反码
  • 补码

机器数

一个数在计算机中的表示形式, 叫做这个数的机器数。计算机是由二进制数字电路构成的,所以机器数只能由二进制数码0和1来表示。

机器数带有符号,符号存放在最高位,正数用 0 表示,负数用 1 表示。

例:

1
2
3
4
5
6
7
8
// [] 中的表示符号位
18 的二进制格式为:[0]0000000000000000000000000010010

-18 的二进制格式为:[1]0000000000000000000000000010010

// 计算机中,负数的二进制通常以「补码」的形式存储,
// 所以 -18 通常见到的是下面的形式
[1]1111111111111111111111111101110

真值

机器数对应的真实数值即为机器数的真值

例:

1
2
3
4
// [] 中的表示符号位
[0]0000000000000000000000000010010 对应的真值为:18

[1]0000000000000000000000000010010 对应的真值为:-18

原码

人最容易理解和计算的方式

符号位 + 真值的绝对值 = 原码

以 8 位 二进制为例

1
2
3
// [] 中的表示符号,剩下的为真值的绝对值
+1 的原码 = [0]0000001
-1 的原码 = [1]0000001

反码

正数的反码是其本身。

1
+1 的原码:[0]0000001,反码:[0]0000001

负数的反码:在原码的基础上,符号位不变,其余位取反(即0变1,1变0)

1
-1 的原码:[1]0000001,反码:[1]1111110

负数的反码:也可以由对应的补码减1得到

1
-1 的补码:[1]1111111,反码:[1]1111110

如果一个反码表示负数,通常需要先转换成原码

补码

正数的补码就是其本身

1
+1 的原码:[0]0000001,反码:[0]0000001,补码:[0]0000001

负数的补码:在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

1
-1 的原码:[1]0000001,反码:[1]1111110,补码:[1]1111111

如果一个补码表示负数,通常需要先转换成原码

JS 中的位操作符

ECMAScript 中的所有数值都以 IEEE 754 64 位格式存储,但位操作并不直接应用到 64 位表示,而是先把值转换为 32 位整数,再进行位操作,之后再把结果转换为 64 位。

有符号整数使用 32 位的前 31 位表示整数值,第 32 位表示数值的符号。

按位非( ~ )

返回数值的补码

1
2
3
let num1 = 25;      // 二进制 00000000000000000000000000011001 
let num2 = ~num1; // 二进制 11111111111111111111111111100110
console.log(num2); // -26

由上看出:按位非的最终效果是对 数值取反并减 1,即 -25 - 1 = -26;
(虽然最后结果相同,但是位操作速度快得多)

按位与( & )

按位与需要有两个操作数

将两个操作数的每一位对齐,然后按下表的规则,对每一位执行「与」操作

第一个数值的位 第二个数值的位 结果
1 1 1
1 0 0
0 1 0
0 0 0

例:

1
2
3
4
5
6
7
let result = 25 & 3;
console.log(result); // 1

25 = 0000 0000 0000 0000 0000 0000 0001 1001
3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
AND = 0000 0000 0000 0000 0000 0000 0000 0001

结论按位与操作在两个位都是 1 时返回 1,在任何一位是 0 时返回 0。

按位或( | )

按位或需要有两个操作数

将两个操作数的每一位对齐,然后按下表的规则,对每一位执行「或」操作

第一个数值的位 第二个数值的位 结果
1 1 1
1 0 1
0 1 1
0 0 0
1
2
3
4
5
6
7
let result = 25 | 3;
console.log(result); // 27

25 = 0000 0000 0000 0000 0000 0000 0001 1001
3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
OR = 0000 0000 0000 0000 0000 0000 0001 1011

结论按位或操作在两个位都是 0 时返回 0,在任何一位是 1 时返回 1。

按位异或( ^ )

按位异或需要有两个操作数

将两个操作数的每一位对齐,然后按下表的规则,对每一位执行「异或」操作

第一个数值的位 第二个数值的位 结果
1 1 0
1 0 1
0 1 1
0 0 0

例:

1
2
3
4
 25 = 0000 0000 0000 0000 0000 0000 0001 1001
3 = 0000 0000 0000 0000 0000 0000 0000 0011
---------------------------------------------
XOR = 0000 0000 0000 0000 0000 0000 0001 1010

结论按位异或操作只在一位上是 1 的时候返回 1(两位都是 1 或 0,则返回 0)。

左移( << )

左移多少位就在这个二进制数右边加多少个0来补位

注意:左移会保留所操作数值的符号,即 -2 左移 5 位,得到的是 -64,而不是 +64

例:

1
2
let v = 2 // 二进制数为 10
let newV = v << 5 // 左移五位,二进制数为 1000000 ,即十进制数 64

有符号右移 ( >> )

右移多少位就在这个二进制数左边「符号位后面」加多少个0来补位

注意:符号位不变,补位的 0 添加在符号位后面

例:

1
2
let v = 64 // 二进制数为 00000000 00000000 00000000 01000000
let newV = v >> 5 // 左移五位,二进制数为 10 ,即十进制数 2

无符号右移 ( >>> )

对于正数,无符号右移与 有符号右移结果相同。

对于负数右移多少位就在这个二进制数左边加多少个0来补位,符号位会被忽略而被0补上。

注意:对于负数,符号位是会改变的,补位的 0 添加在符号位前面

1
2
3
4
5
let v = -64 // 二进制数为 11111111 11111111 11111111 11000000
let newV = v >>> 5
// 左移五位,
// 二进制数为 00000111 11111111 11111111 11111110 ,
// 即十进制数 134 217 726