有这样一个思考题:
byte是有符号整数,最高位是符号位,有效数字位只有7位,用二进制原码表示其范围为:11111111 ~ 01111111,换算成二进制是-127 ~ 127,所以byte的范围是[-127 ~ 127]。
但是,这个结论,对吗?
显然是不对的,众所周知,byte表示的范围是-128~127
。
但是,-128是从哪里来的呢?
这和计算机存储数字的方式有关,计算机并不是直接存储数字的二进制原码,而是以补码的形式进行存储。
本文以byte类型为例,通过详细分析其范围,深入理解补码。
一,原码、反码、补码
在深入补码之前,我们先回顾一下原码、反码和补码的概念,它们是计算机表示负数的关键。
-
原码:直接将数值转换为二进制,最高位用作符号位,0表示正数,1表示负数。例如,+5的8位原码是
00000101
,而-5则是10000101
。 -
反码:负数的反码是在其原码基础上,除了符号位以外的所有位取反(0变1,1变0)。+5的反码仍是原码,而-5的8位反码是
11111010
。 -
补码:补码是反码加1。对负数而言,补码是其反码加1的结果。因此,-5的8位补码是
11111011
。补码的引入简化了计算机中的加减运算,使得加法运算可以统一处理正数和负数。
二,Java中的byte与补码
Java的byte
类型使用补码来表示所有的数值,包括负数。
为了方便理解,考虑到byte表示的整数是有限的,我们用一个圆圈来辅助理解,假设圆每隔一个固定的距离的点代表一个byte数值。
1,正数在圆上的位置
因为正数的补码就是原码,所以先确定正数在圆上的位置,byte类型正数最小值是+0
,补码是原码0000 0000
,因为最高位是符号位,所以正数最大值是 0111 1111,即127。
2,负数在圆上的位置
确定负数在圆上位置的时候,要注意,负数的补码要通过原码、反码计算得到,比较麻烦,我们可以通过如下Java API来获取数字的补码(注意:打印出来的是Integer的补码,最右边8位是byte补码):
byte b = -1; // 最小的byte值
System.out.println("-1的补码: " + Integer.toBinaryString(b));
从上面圆上可知,数字0
左侧是负数,第一个负数是-1
,根据上图调用API的结果线上,-1
补码是1111 1111
。
因为正数最大值是127
,假设最小值是 -127
,根据上述API,-127
的补码是1000 0001
。
很明显,-127
的补码1000 0001
尾数是1,还可以减1,得到二进制1000 0000
,这个二进制非常特别,我们找不出其对应的反码和原码。1000 0000
减1的结果是最大值127
的补码0111 1111
,加1的结果是-127
的补码1000 0001
:
把这个补码1000 0000
对应的真值定为 -128
,这是篇首问题(-128是怎么来的
)的答案,byte的范围是[-128, 127]
就是这样确定下来的。
三,补码的应用与注意事项
补码不仅简化了计算机中负数的运算,还解决了原码和反码中存在的一些问题,比如多个0的表示(在原码和反码中,+0和-0都被表示为全0,而补码中只有唯一的0)。此外,补码的使用使得加法运算可以统一处理正数和负数,无需区分加数的正负,极大地提高了运算效率。
在编写涉及byte类型数据的Java程序时,需要注意其有限的数值范围,避免超出界限导致的数据溢出错误。例如,对一个byte类型的变量进行运算时,如果结果超出了-128至127的范围,Java会自动将结果截断,只保留低8位,这可能导致意外的结果。