0%

Java中的数与位

世界上只有10种人,一种是认识二进制的,一种是不认识的。认识程序要从二进制开始,了解Java中的二进制计算规则也必不可少。

自打计算机问世开始,高低电平就一直伴随至今,低电平表示0,高电平表示1,这便构成了计算机中数据存储的最小单位:位。位,记为b,也叫比特(bit),每个0或者1就是一个位。

自然生活中接触最多的是十进制数,但是计算机只认识0和1,只能用二进制来表示一个数,所以计算机在计算和存储时,会将所有的数据都转换成二进制,也叫做机器数。为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。

计算机中的数有三种表示方法:原码、反码和补码,以下都以一个字节(byte)的有符号数为例。

原码

原码是十进制数到二进制的直接转换,是人最容易接受和理解的一种表示法。最高位是符号位,表示正负,其余位是二进制表示的数,因为第一位表示符号位,所以byte的取值范围是[-127,127]。

反码

反码是在原码的基础上的一种变化,反码的应用比较少:

  • 对于正数,反码和原码一致;
  • 对于负数,在原码的基础上,保持符号位不变,其余位取反。

补码

补码是在补码上的有一种改进,计算机中的数值都是以补码的形式来计算和存储的

  • 对于正数,补码和原码一致;
  • 对于负数,在原码的基础上,保持符号位不变,其余位取反,然后+1,即反码+1,规定-128的补码是[10000000]。

示例

真值 +5 -5
原码 00000101 10000101
反码 00000101 11111010
补码 00000101 11111011

计算

原码,这是最符合直觉的:

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

反码:

用反码进行计算,虽然真值部分计算正确,但是存在+0和-0两个0的表示总是有点让人困惑的。

补码:

正负数的加减法可以用补码正确计算,且使用[10000000]来表示-128,也解决了0的表示问题,所以最后计算机采用补码来对数值进行计算和存储。

同余:补码的另一种理解方式,8位只能表示256个数,如果想用其中的一部分表示负数怎么办,可以使用与该负数同余的正数来表示,比如,-1=255,-4=252,转换成二进制就是负数的补码表示形式。

位运算

计算机对二进制是友好的,二进制运算有时候可以极大的提升运算的速度。Java中数值都是以补码的形式参与位运算的,并且除~外,只能用于整型。当用于byte、short和char时,会发生类型提升,会先转换成int再进行运算。

按位与

按位与&是二目运算符,将两个数对应的二进制位进行与操作,同1为1,其余为0。

1 0
1 1 0
0 0 0

按位或

按位或|是二目运算符,将两个数对应的二进制位进行或操作,同0为0,其余为1。

1 0
1 1 1
0 1 0

按位异或

按位异或^是二目运算符,将两个数对应的二进制位进行异或操作,不同为1,相同为0。

1 0
1 0 1
0 1 0

按位非

按位非~是一目运算符,将数值的二进制所有位进行取反操作,1变为0,0变为1。

取反前 取反后
1 0
0 1

左移

左移<<是二目运算符,符号位不变,将数值的其他二进制位整体向左移动指定的位数,空白位填充0。

右移

右移>>是二目运算符,符号位不变,将数值的其他二进制位整体向右移动指定的位数,空白位填充0。

无符号右移

无符号右移>>>是二目运算符,将数值的所有二进制位(包括符号位)整体向右移动指定的位数,空白位填充0。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class Test {
public static void main(String[] args) {
int a = 6;
int b = -5;
System.out.printf("a= %32s(%d)%n", Integer.toBinaryString(a), a);
System.out.printf("b= %32s(%d)%n", Integer.toBinaryString(b), b);
int c = a & b;
System.out.printf("c= %32s(%d)%n", Integer.toBinaryString(c), c);
int d = a | b;
System.out.printf("d= %32s(%d)%n", Integer.toBinaryString(d), d);
int e = a ^ b;
System.out.printf("e= %32s(%d)%n", Integer.toBinaryString(e), e);
int f = ~a;
System.out.printf("f= %32s(%d)%n", Integer.toBinaryString(f), f);
int g = a << 2;
System.out.printf("g= %32s(%d)%n", Integer.toBinaryString(g), g);
int h = b >> 6;
System.out.printf("h= %32s(%d)%n", Integer.toBinaryString(h), h);
int i = b >>> 10;
System.out.printf("i= %32s(%d)%n", Integer.toBinaryString(i), i);
}
}
// 输出
// a= 110(6)
// b= 11111111111111111111111111111011(-5)
// c= 10(2)
// d= 11111111111111111111111111111111(-1)
// e= 11111111111111111111111111111101(-3)
// f= 11111111111111111111111111111001(-7)
// g= 11000(24)
// h= 11111111111111111111111111111111(-1)
// i= 1111111111111111111111(4194303)