对补码一直存在疑惑,查了好些资料算是略知一二,简单总结一下。
原码、反码与补码
Java中整型都是带符号的,int是【首位符号位+31位数字】,short是【首位符号位+15位数字】,byte是【首位符号位+7位数字】。
为了偷懒,以下皆以8位的byte为例。
原码
- 正数首位为0
0 ~ 0000 0000
1 ~ 0000 0001
2 ~ 0000 0010
…
31 ~ 0111 1111 - 负数首位为1
-32 ~ 1000 0000
-1 ~ 1000 0001
…
-31 ~ 1111 1111 - 除了将1000 0000(负0)表示-32,负数就是正数编码首位置1,其他没啥好讲的。
反码
- 正数反码就是本身
- 负数反码为原码按位取反(翻转)
反码也没啥,容易理解。
补码
- 正数补码就是本身
- 负数补码为其反码+1
补码其实用到了同余的概念,负数 -x % 64 = (-x+64) % 64,这样就把减法转换为了加法:
a - x = a + ( -x ) = a + ( 64 - x ) //有可能会溢出,但舍弃溢出位之后,结果是正确的
至于为什么负数补码为其反码+1,可以当成是个巧合。 - 试着解释下
64 ~ [1] 0000 0000 ,可看作带溢出位的0。
-x + 64 = (-x + 63) + 1,前半部分相当于按位取反,首位符号位1+1溢出舍弃。
例如:-12(1000 1100)补码为 0111 0100,转为10进制是20;
-12+64=52,去掉溢出位(-32)是20,结果相同。
得出-x的补码为其反码+1。 - 为什么要+1
我的理解是,问题出在编码上。如果1000 0000表示负0而不是-32,则不需要+1。但这样的话byte仅能表示63个数而不是64个数,估计当初设计者有所考量,就做了这么一个改动。
改完之后相当于把负半轴整体左移1个单位,所以取反码加1才能完全覆盖正半轴。
减法计算过程
例:1 -30
0000 0001 + (1110 1010)[^取反码]=0000 0001 + 0001 0110 =0001 0111
反码转回原码,是减一取反,为1110 1001 ,即-29。
移位操作
<<和>>是带符号的移位,左移相当于乘以2,右移相当于除以2(舍弃小数位),这两个都会保留符号位。
负数移位存在【取绝对值>>取反码>>移位>>转回原码】操作,最后将首位置为1。
所以-1>>1=-1 (不是0也不是负0:-2147483648)。
需要注意的是,溢出之后移位结果将会出错,需要特别当心。
“>>>” 是无符号的右移,负数移动后就也变成正数了,所以如果负数这么移位,要乘以-1才正确。