fi3ework's Studio.

原码补码异或与位运算移位知识点

Word count: 2.2kReading time: 8 min
2019/10/17 Share

异或,英文为exclusive OR,缩写成xor
异或(xor)是一个数学运算符。它应用于逻辑运算。异或的数学符号为“⊕”,计算机符号为“xor”。其运算法则为:
a⊕b = (¬a ∧ b) ∨ (a ∧¬b)
如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
异或也叫半加运算,其运算法则相当于不带进位的二进制加法:二进制下用1表示真,0表示假,则异或的运算法则为:0⊕0=0,1⊕0=1,0⊕1=1,1⊕1=0(同为0,异为1),这些法则与加法是相同的,只是不带进位,所以异或常被认作不进位加法。

byte、bit

字节(Byte)/比特位(bit)

1
2
3
4
5
6
7
8
9
B是Byte的缩写,B就是Byte,也就是字节(Byte);b是bit的缩写,b就是bit,也就是比特位(bit)。
B与b不同,注意区分,KB是千字节,Kb是千比特位。

1MB(兆字节) = 1024KB(千字节)= 1024*1024B(字节) = 1048576B(字节);
8bit(比特位)= 1Byte(字节);
1024Byte(字节)= 1KB(千字节);
1024KB(千字节)= 1MB(兆字节);
1024MB = 1GB;
1024GB = 1TB;

原码、反码、补码

其实数据存储在内存中都是存储的二进制,二进制又可分为原码、反码、补码。最终存储在内存中的是“补码”。

一个正数的原码、反码、补码都是它的二进制表现形式。(无符号数没有原码、反码和补码一说。只有带符号数才存在不同的编码方式。)

1
2
3
4
5
6
7
例如:9 == int == 4个字节 == 1个字节等于8位 == 整形有32
正数的原码:
0000 0000 0000 0000 0000 0000 0000 1001
正数的反码就是正数的原码:
0000 0000 0000 0000 0000 0000 0000 1001
正数的补码也是整数的原码:
0000 0000 0000 0000 0000 0000 0000 1001

二进制的第一位是符号位,0代表整数,1代表负数。

一个负数的原码是首位为1的二进制数。反码是符号位不变,其他位取反。补码是反码加1。

1
2
3
4
5
6
7
例如:-9
原码为首位为1的二进制数:
1000 0000 0000 0000 0000 0000 0000 1001
反码为符号位不变,其他位取反:
1111 1111 1111 1111 1111 1111 1111 0110
补码是反码加1
1111 1111 1111 1111 1111 1111 1111 0111

为什么会有原码反码补码?

  1. 由于最高位是符号位,如果是0就代表正数,1就代表是负数。
  2. 那么直接存储原码,计算机在计算的时候还需要先判断最高位才能计算,效率比较低
  3. 为了方便计算机计算,所以有了反码和补码,然后计算机就不需要判断符号位了,只做加法运算就可以了。

计算十进制的表达式:1 - 1 = 0

1
2
3
4
5
1 - 1 == 1 + (-1)
0000 0001(原码)
+1000 0001(原码)
----------------
1000 0010(原码) == -2

如果用原码表示,让符号位参与计算,显然对于减法来说是不正确的。这也是计算机内部不以原码表示的原因。

  • 为了解决原码做减法的问题,出现了反码:
1
2
3
4
5
1 - 1 == 1 + (-1)
0000 0001(反码)
+1111 1110(反码)
----------------
1111 1111(反码) 转成原码 -> 1000 0000 == -0

通常我们用最高的有效位来表示数的符号(当用8位来表示一个整数时,第8位即为最高有效位,当用16位来表示一个整数时,第16位即为最高有效位。)0表示正号、1表示负号,这种正负号数字化的机内表示形式就称为“机器数”,而相应的机器外部用正负号表示的数称为“真值”。将一个真值表示成二进制字串的机器数的过程就称为编码。

  • 于是补码出现了,解决了0的符号问题以及两个编码的问题,0的编码就只有+0,-0已经等于-128了。
1
2
3
4
5
6
1 - 1 == 1 + (-1)
0000 0001(补码)
+1111 1111(补码)
----------------
10000 0000(补码)
第一位溢出后转成原码 -> 0000 0000 == 0

与、或、异或运算

1.与运算(&)

参加运算的两个数据,按二进制位进行“与”运算。

1
2
3
4
5
6
7
运算规则:0&0=0;   0&1=0;    1&0=0;     1&1=1;

即:两位同时为“1”,结果才为“1”,否则为0

例如:3&50000 0011 & 0000 0101 = 0000 0001 因此,3&5的值得1

例如:9&50000 1001 (9的二进制补码)&00000101 (5的二进制补码) =00000001 (1的二进制补码)可见9&5=1

2.或运算(|)

参加运算的两个对象,按二进制位进行“或”运算。

1
2
3
4
5
	运算规则:0|0=00|1=11|0=11|1=1
即 :参加运算的两个对象只要有一个为1,其值为1
例如:3|5 即 0000 0011 | 0000 0101 = 0000 0111 因此,3|5的值得7。 

例如:9|5可写算式如下: 00001001|00000101 =00001101 (十进制为13)可见9|5=13

3.异或运算(^)

参加运算的两个数据,按二进制位进行“异或”运算。

1
运算规则:0^0=00^1=11^0=11^1=0

即:参加运算的两个对象,如果两个相应位为“异”(值不同),则该位结果为1,否则为0。

1
例如:9^5可写成算式如下: 00001001^00000101=00001100 (十进制为12)可见9^5=12

位、移位运算

一个字节数占 8 位,将 ab 转换为二进制:

1
2
a = 0000 1111
b = 1111 0001

Note:计算机使用补码表示

位与运算符:仅当两个操作数同一下标的值均为 1 时,结果才为 1

1
a & b = 0000 1111 & 1111 0001 = 0000 0001(补) = 0000 0001(原) = 1

位或运算符:只要两个操作数同一下标的值有一个为 1 时,结果就为 1

1
a | b = 0000 1111 & 1111 0001 = 1111 1111(补) = 1000 0001(原) = -1

位异或运算符:只有两个操作数同意下标的值不相等时,结果才为 1

1
a ^ b = 0000 1111 ^ 1111 0001 = 1111 1110(补) = 1000 0010(原) = -2

位取反运算符:按位取反每一位

1
2
~a = ~0000 1111 = 1111 0000(补) = 1001 0000(原) = -16
~b = ~1111 0001 = 0000 1110(补) = 0000 1110(原) = 14

Note 1:byte 或者 short 类型数值进行位运算后,返回的是 int 类型数值(没有找到资料说明在位运算之前是否已经进行了转换,不过先将 a,b 转换为 int 类型二进制再进行计算的结果和上面一致)

Note 2:位运算符的操作不排除符号位

移位运算符

Java 提供了 3 种移位运算符

  • 左移运算符(left shift operator):<<
  • 右移运算符(right shift operator):>>
  • 无符号右移运算符(unsigned right shift operator):>>>

对于移位运算符而言,左侧操作数表示要移动的二进制数,右侧操作数表示要移动的位数

进行移位操作时,需要注意以下几点:

对于 byte 或者 short 类型数值,进行移位操作时,会先转换为 int 类型,然后进行移位(如果是 long 类型,则不变)

对于右侧操作数而言,在进行移位之前,先转换为二进制数(补码)。如果左侧数是 int 类型,则取右侧操作数最右端 5 位数值进行移动;如果是 long 类型数值,则取右侧操作数最右端 6 位数值进行移动

左移运算符:数值位向左移动指定位数

1
2
3
4
5
6
7
15 << 3 = 0x0000000f << 3 = 0x00000078(补,原) = 120
15 << -61 = 0x0000000f << 0xffffffc3(左侧是 int 类型,取右侧 5 位) = 0x0000000f << 3 = 0x00000078(补,原) = 120
15 << 35 = 0x0000000f << 0x00000023(左侧是 int 类型,取右侧 5 位) = 0x0000000f << 3 = 0x00000078(补,原) = 120

-15 << 3 = 0xfffffff1 << 3 = 0xffffff88(补) = 0x80000078(原) = -120
-15 << -61 = 0xfffffff1 << 0xffffffc3(左侧是 int 类型,取右侧 5 位) = 0xfffffff1 << 3 = 0xffffff88(补) = 0x80000078(原) = -120
-15 << 35 = 0xfffffff1 << 0x00000023(左侧是 int 类型,取右侧 5 位) = 0xfffffff1 << 3 = 0xffffff88(补) = 0x80000078(原) = -120

右移运算符:数字位向右移动指定位数(如果左操作数是正数,高位补 0 ;如果是负数,高位补 1)

1
2
15 >> 3 = 0x0000000f >> 3 = 0x00000001 = 1
-15 >> 3 = 0xfffffff1 >> 3 = 0xfffffffe(补) = 0x80000002(原) = -2

无符号右移运算符:功能和右移运算符一样,不过无论正负,高位均补 0

1
2
15 >>> 3 = 0x0000000f >>> 3 = 0x00000001 = 1
-15 >> 3 = 0xfffffff1 >>> 3 = 0x1ffffffe(补,原) = 2^29 - 2 = 536870910

Note 1:移位运算时,从符号位开始操作

Note 2:由结果可知,左移一位相当于乘以2,右移一位相当于除以 2

参考文章:
参考链接1
参考链接2

CATALOG
  1. 1. byte、bit
  2. 2. 原码、反码、补码
    1. 2.1. 为什么会有原码反码补码?
  3. 3. 与、或、异或运算
    1. 3.1. 1.与运算(&)
    2. 3.2. 2.或运算(|)
    3. 3.3. 3.异或运算(^)
  4. 4. 位、移位运算
    1. 4.1. 移位运算符
    2. 4.2. 左移运算符:数值位向左移动指定位数
    3. 4.3. 右移运算符:数字位向右移动指定位数(如果左操作数是正数,高位补 0 ;如果是负数,高位补 1)
    4. 4.4. 无符号右移运算符:功能和右移运算符一样,不过无论正负,高位均补 0