一、补码
补码:是一种在计算机中表示有符号整数的方法。在计算机系统中,数值一律用补码来表示和存储。
补码的最高位(最左边的位)用作符号位,0表示正数,1表示负数。
如:int类型的补码,共32位(从第0位到第31位)。最左边一位(第31位)是符号位,其余31位是数值存储位(全是0或1)。
在补码运算中,无论正数还是负数,符号位都会参与运算。这是补码表示法的核心特性之一,也是其硬件实现简单高效的关键原因。
正数的补码:等于其二进制表示本身(也叫原码),就是整数的二进制是多少,它的补码也是多少。
负数的补码:通过将其对应正数的补码(原码)表示按位取反,然后再加1得到负数的补码。负数补码 = 反码(正数补码按位取反) + 1。
如下,计算机对于int类型 -5 取补码的过程:
1.先取5的原码(5的二进制):
00000000 00000000 00000000 00000101
2.对原码按位取反(得到反码):
^5 : 11111111 11111111 11111111 11111010
3.反码 + 1 = 补码
11111111 11111111 11111111 11111010
+1 : 00000000 00000000 00000000 00000001
--------------------------------------------
-5 : 11111111 11111111 11111111 11111011
最后得到-5的补码:11111111 11111111 11111111 11111011 (即 -5 的存储形式)
0 的补码全是0,-1的补码全是1。
在Java中,整数的加减法运算完全基于补码,计算机硬件直接对补码进行二进制计算。
补码加减法的核心规则:
加法:直接按位相加,超出位数的高位丢弃。
减法:转换为加法。如:A - B = A + (-B)【减法转换为加法是由硬件去做的】
以下以 5 + (-3) 示例,等价于5 - 3(减法)
5: 00000000 00000000 00000000 00000101
-3: 11111111 11111111 11111111 11111101
----------------------------------------
00000000 00000000 00000000 00000010
得到的补码就是2的补码。结果正确。
下面详细讲解 5 + (-3) 补码的运算过程:
5和-3的最低位(从右向左,第0位)都为1。位运算规则:1 + 1 = 10,当前(第0位)为0,向高一位进1。
因此第一位是:0 + 0 + (进位)1 = 1。第二位都是1。因此结果为:1 + 1 = 0(当前位) + 1(进位)。
后面从第3位起到第31位(最高位)都是 0(5产生的) + 1(3的产生的) + 1(进位) = 0(当前位) + 1(进位)。
因此不断的向高位进1,然后自身位为0。所以产生的结果的高位全是0。对于最高位第31位自身为0,但进1的问题。
(第31位产生的进位1)它会被CPU的硬件电路自动丢弃,因为超出了int的32位存储范围(因为寄存器只有32位)。
以下是详细的运算拆解,方便大家直观理解:
位序 5的位 -3的位 进位输入 结果位 进位输出 计算逻辑
--------------------------------------------------------------------------------
0(第0位) 1 1 0 0 1 1 + 1 + 0 = 10 (写 0 进 1)
1 0 0 1 1 0 0 + 0 + 1 = 01 (写 1 进 0)
2 1 1 0 0 1 1 + 1 + 0 = 10 (写 0 进 1)
3~30 0 1 1 0 1 0 + 1 + 1 = 10 (写 0 进 1)
31(符号位) 0 1 1 0 1 0 + 1 + 1 = 10 (写 0 进 1)
---------------------------------------------------------------------------------
二、溢出处理
溢出处理:整数溢出在补码体系下类似于"环绕"(循环)。
补码溢出"环绕"原理:以int类型为例:
int的范围:最小值,Integer.MIN_VALUE = -2147483648; 最大值,Integer.MAX_VALUE = 2147483647;
溢出后:
上溢(Overflow):超过 MAX_VALUE ——> 跳转到 MIN_VALUE 继续增加。
如:
int a = Integer.MAX_VALUE;
a = a + 1;
System.out.println(a);
二进制解释:
MAX_VALUE: 01111111 11111111 11111111 11111111
+1: 00000000 00000000 00000000 00000001
-----------------------------------------------
Result: 10000000 00000000 00000000 00000000 (MIN_VALUE)
======================================================================================
下溢(Underflow):低于 MIN_VALUE ——> 跳转到 MAX_VALUE 继续减少。
如:
int b = Integer.MIN_VALUE;
b = b - 1;
System.out.println(b);
二进制解释:
MIN_VALUE: 10000000 00000000 00000000 00000000
+(-1): 11111111 11111111 11111111 11111111
-----------------------------------------------
Result: 01111111 11111111 11111111 11111111 (MAX_VALUE) 【提示:最高位的进位1直接被丢弃】
补码运算有着数学封闭性,这是计算机底层采取补码的重要原因之一。
Java遵循补码规则,明确允许溢出静默环绕。
三、Java移位运算
Java位运算( << 、>>、>>> )的补码深度解析:
左移( << ):
规则:所有位向左移动,低位补 0 ,高位直接丢弃(包括符号位)。
数学意义:等价于乘以(2的位移数的次方)。【可能溢出】
示例: -5 << 2
-5 : 11111111 11111111 11111111 11111011
左移2位后的补码:
11111111 11111111 11111111 11101100
结果就是: -20 (-5 * 4)
【如果想要求证也很简单:负数补码 = 反码(正数补码取反) + 1。那么:(负数补码 - 1)取反就等于对应正数的补码】
如下:
-20 : 11111111 11111111 11111111 11101100
+(-1) : 11111111 11111111 11111111 11111111
-------------------------------------------
11111111 11111111 11111111 11101011
^(取反) : 00000000 00000000 00000000 00010100 = 20
---------------------------------------------------
注意点:符号位可能被覆盖,若左移后符号位从 1 变 0,负数会变正数(溢出)。
如: -1 << 31 = -2147483648(Int_MIN_VALUE)
【Java位移操作规定:】
Java对位移操作的处理有一个关键规则:位移(即移动的位数)会先进行模运算。
Java规范规定:对于int类型的位移操作( << 、 >> 、 >>>)实际的位移数是 n % 32。
如: -1 << 32 = -1(实际未移动)。【32 % 32 = 0】
1 << 35 == 1 << 3。【35 % 32 = 3】
避免无意义的超范围位移,保持运算效率。
右移( >> ):
规则:所有位向右移动,高位补符号位(正数补0,负数补1)。
数学意义:等价于除以2^n并向负无穷取整(对负数友好)。
示例: -20 >> 2
-20 : 11111111 11111111 11111111 11101100
-20 >> 2 : 11111111 11111111 11111111 11111011 = -5
【注意点:负数右移结果比除法更小】
如:System.out.println(-15 >> 2);
无符号右移( >>> ):
规则:所有位向右移动,高位强制补0(无论正负)
数学意义:逻辑右移,无符号除法(负数会变正数)
示例: -20 >>> 2
-20 : 11111111 11111111 11111111 11101100
-20 >>>2 : 00111111 11111111 11111111 11111011 = 1073741819
用途:处理无符号数据(如哈希算法、读取网络协议的字段)。