1. 疑问
最近在刷Leetcode的时候,遇到这样一个题:
给出一个 32 位的有符号整数,你需要将这个整数中每位上的数字进行反转。
假设我们的环境只能存储得下 32 位的有符号整数,则其数值范围[,]。请根据这个假设,如果反转后整数溢出那么就返回 0。
读完题后问了自己几个问题,发现自己都不清楚:
- 32位整数是多少?32位是指什么?
- 32为有符号整数的范围为什么是[,]?
- 二进制怎么表示?
掏出计算器用二进制算了一下,=4294967295,=2147483647
到这里,上面的问题基本上搞明白了,新的问题来了:
- 二进制怎么表示正负数
- 二进制的正负数怎么计算,计算机是怎么处理的
- 什么是原码?反码?补码?
- Java中是如何处理Integer的?
2. 开始入正题
2.1. 二进制、原码、反码、补码
为什么要有补码?
这里以4位bit为例,二进制最高位0代表+,1代表-
| 整数 | 原码 |
|---|---|
| 4 | 0 100 |
| -4 | 1 100 |
| 0 | 0000 |
| 5 | 0 101 |
| -5 | 1 101 |
| 2 | 0010 |
| 7 | 0 111 |
| -7 | 1 111 |
| 5 | 0110 |
因为计算机中没有减法,我们发现,这些数的正负数之和,只有4的二进制计算是正确的,结果为0。为了解决这个问题,所以就有了反码、补码的概念。
| 整数 | 原码 | 反码 | 补码 |
|---|---|---|---|
| 4 | 0 100 | 0 100 | 0 100 |
| -4 | 1 100 | 1 011 | 1 100 |
| 0 | - | - | 0000 |
| 5 | 0 101 | 0 101 | 0 101 |
| -5 | 1 101 | 1 010 | 1 011 |
| 0 | - | - | 0000 |
| 7 | 0 111 | 0 111 | 0 111 |
| -7 | 1 111 | 1 000 | 1 001 |
| 0 | - | - | 0000 |
通过补码进行正负数的计算,结果就对了
2.1.1. 原码
最高位表示正负号,0代表整数,1代表负数,其它位表示数值的二进制
2.1.2. 反码
- 正数的反码:与原码一致
- 负数的反码:最高位符号位不变,其它位取反
2.1.3. 补码
- 正数的补码:与原码一致
- 负数的补码:反码 + 1
2.2. Java中的Integer
@Test
void testLocal() {
Integer maxInt = Integer.MAX_VALUE;
Integer minInt = Integer.MIN_VALUE;
log.info("十进制:min = {}, max = {}", minInt, maxInt);
String minBinary = Integer.toBinaryString(minInt);
String maxBinary = Integer.toBinaryString(maxInt);
log.info("二进制:minBinary {}位 = {}, maxBinary {}位 = {}", minBinary.length(), minBinary, maxBinary.length(), maxBinary);
log.info("int 0 = binary {}", Integer.toBinaryString(0));
log.info("{} = {}, {} = {}", minInt + 1, Integer.toBinaryString(minInt + 1), maxInt - 1, Integer.toBinaryString(maxInt - 1));
log.info("越界intMax + 1 = {}", maxInt + 1);
}
Java中的Integer的大小是32位bit,最小值和最大值范围就是[,],数值大小是31位,最高位0/1表示正负。
从二进制的角度来看,对正整数的最大值是31位1,加1后所有位进1变为0,最后结果就是最高位由0 -> 1,得到的二进制结果和负数的最小值相同,这样就容易理解java中的整数越界,以及Integer的范围。
2.3 int最小值补充解释(2021.3.2)
发现之前对java里Integer最大值最小值解释的不是很清楚,这里做一下补充。
此处有个注意点,负数的符号位用1来表示,那么int负数的最小值应该是32个1,为什么java中是1后面31个0呢。其实java里转为二进制后取的是负数的反码,我们看下转换过程
// 0 ~ 1 ~ 最大正整数2,147,483,647,一共2,147,483,648个数字,对应的二进制分别如下
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 = 0
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 = 1
0111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 = 2,147,483,647
// 最小负整数-2,147,483,648 ~ -1,同样是2,147,483,648个数字
1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 = -2,147,483,648
1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 = -2,147,483,647
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 = -1
// Java中负数的二进制是有符号二进制的反码
// 1的二进制
0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 = 1
// -1的转换过程
1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0001 = -1
// 负数的反码:最高位符号位不变,其它位取反
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1110
// 负数的补码:反码 + 1
1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 = -1
通过这种计算方式,可以发现负数的二进制是补码。其中0比较特殊可以看做是[+0]和[-0],[+0]=0,[-0]=-2,147,483,648。这些都可以通过二进制的形式推导出来。
3. 总结
- 计算机中的计算只有加法和二进制,为了原码、反码、补码就是为了解决计算机中的运算
- 整数越界,就是在固定32位长度下,对二进制计算结果转为整数的过程
- 通过一个问题引申出一系列的问题,从数值范围 -> 二进制表示 -> 有/无符号 -> 二进制计算 -> 原码/反码/补码 -> 整数越界...
4. 最后
了解的二进制后,Java中的移位运算符("<<"、">>"、">>>")是怎么做运算的?