1.5 原码、反码、补码
计算机为了区分数值的正负提出了符号位的设定,计算机用最高位存放符号,这个被称为符号位。正数的符号位为0, 负数的符号位为1。
例如,1的二进制表示形式为0000 0001
,而-1的二进制表示形式为1000 0001
。在计算机中,一个字节为8个位,最大值为0111 1111,十进制为127。最小值为1000 0000,十进制为-128。因此一个字节的取值范围为-128~127之间。
1.5.1 原码
原码是最直观的表示方法,它直接将数值转换为二进制数,是十进制数在二进制中的体现,该二进制包含符号位。原码就是我们之前使用的二进制的格式,只不过我们之前一直没有使用二进制描述过负数,使用原码描述负数只要将符号位设定为1即可。
但仅仅存在原码对于计算机进行运算时有着巨大缺陷;那就是对于负数的计算。
- 对于正数的计算,原码不会有问题,例如计算2+6:
0000 0010
+ 0000 0110
-----------------
0000 1000
0000 1000的十进制为8,计算正确。
- 对于负数的运算,发现结果与目标值不对,例如计算2+(-6):
0000 0011 (2)
+ 1000 0110 (-6)
--------------
1000 1001
1000 1001的十进制为-9,计算错误。
为了解决原码不能用于计算负数的这种问题,因此迫切需要一种新的表示方法来解决这个巨大的缺陷,于是反码出现了。
Tips:有些人在这里肯定会有一个疑问,为什么要计算2+(-6),而不是2-6这样的操作呢?这是因为CPU中只设计了加法器,没有所谓的“减法器”,因此2-6只能看做成2+(-6)这样的操作。
1.5.2 反码
反码提供了一种便捷的方法来执行减法操作,这样的设计允许CPU利用已有的加法逻辑来完成减法运算,无需额外的减法器硬件,从而简化了处理器的设计并减少了硬件资源的需求。
1. 反码的转换规则
有了原码之后我们可以根据规则转换成对应的反码,其规则如下:
- 正数的反码:原码本身。
- 负数的反码:原码的符号位不变,其余位取反。
一些数的原码、反码的表示如表所示。
十进制数字 | 原码 | 反码 |
---|---|---|
1 | 0000 0001 | 0000 0001 |
-1 | 1000 0001 | 1111 1110 |
2 | 0000 0010 | 0000 0010 |
-2 | 1000 0010 | 1111 1101 |
原码转换为反码后,使用反码来进行计算。对于通常的负数计算,使用反码可以完成,例如我们计算2-6。
2的原码为0000 0010,反码为0000 0010。-6的原码为1000 0110,反码为1111 1001,计算如下:
0000 0010 (2)
+ 1111 1001 (-6)
-----------
1111 1011 (-4反码)
1000 0100 (-4原码)
上述案例的计算结果为:1111 1011(反码),转换为原码为:1000 0100,结果为-4,刚好是正确的。
2. 反码的进位
需要注意的是,使用反码计算时,高位进位时需要作用到低位。例如我们换一个数进行计算,计算-4+(-1)。
-4的原码为1000 0100,反码为1111 1011。-1的原码为1000 0001,反码为1111 1110,计算如下:
1111 1011 (-4)
+ 1111 1110 (-1)
-----------
1111 1001 (-6)
1 (高位进位之后作用到低位,在低位进1位)
-----------
1111 1010 (-5反码)
1000 0101 (-5原码)
3. 反码的缺陷
反码也有它的缺陷,那就是计算结果为0时。
我们来计算2+(-2),-2的原码为:1000 0010,反码为:1111 1101。
0000 0010 (2)
+ 1111 1101 (-2)
-----------------
1111 1111 (反码)
1000 0000 (原码)
上述案例计算的结果为1111 1111转回原码为1000 0000,结果为-0。那么问题来了,-0是0吗?在二进制中出现了两个可以描述“0”这个数值的二进制,一个为1000 0000,另一个为0000 0000,“0”这个数字在计算机中的编码就不是唯一的了,这很显然有问题。
1.5.3 补码
使用反码表示零时存在两种形式(+0和-0),这在逻辑上不严谨。计算机科学家们意识到这个问题后决定,把0当成正数,即“+0”,也就是0000 0000,此时八位二进制表示的正数范围为:+0到+127。对负数做调整,不能使用1111 1111来表示“-0”,因为不存在“-0”这个数。
1111 1111既然不表示“-0”,也就意味着可以表示其他的数值,因而八位二进制能够表述的数字又多了一位。那如何利用这一位数值呢?我们把基于反码的八位二进制表示的负数整体“向后挪动1位”,负数的取值范围就从-0到-127变为-1到-128。1111 1111不再表示-0了,而是表示-1,以此类推1111 1110表述的就是-2。
那么如何只对负数整体“向后挪动1位”呢?那就是对一个数的反码进行+1操作,如下:
- 1111 1110(-1的反码)+1就是1111 1111,这样1111 1111就不再表示-0而是表示-1。
- 1111 1101(-2的反码)+1就是1111 1110,这样1111 1110就不再表示-1而是表示-2。
就这样,八位二进制数的最小取值范围从-0到127变为了-1到-128。这种基于反码+1的操作就是补码。
1. 补码的转换规则
计算机在进行运算时,先将原码转回为对应的补码。其转换规则如下:
- 正数的补码:原码本身,正数的原码、反码、补码全都一致。
- 负数的补码:原码的反码+1。
一些数的原码、反码、补码的表示如表所示。
十进制数字 | 原码 | 反码 | 补码 |
---|---|---|---|
1 | 0000 0001 | 0000 0001 | 0000 0001 |
-1 | 1000 0001 | 1111 1110 | 1111 1111 |
2 | 0000 0010 | 0000 0010 | 0000 0010 |
-2 | 1000 0010 | 1111 1101 | 1111 1110 |
127 | 0111 1111 | 0111 1111 | 0111 1111 |
-127 | 1111 1111 | 1000 0000 | 1000 0001 |
-128 | 超出表示范围 | 超出表示范围 | 10000000 |
因为补码把“-0”这个位置利用起来了,所以在一个字节下的补码还可以记录一个特殊的值:-128,这个数据在1个字节下是没有原码和反码的。
2. 使用补码计算0
我们使用补码再来计算2+(-2)。
-2的补码为1111 1110。2的原码、反码、补码都为本身,即:0000 0010。计算过程如下:
1111 1110 (-2)
+ 0000 0010 (2)
------------
0000 0000 (0)
可以看到,上述的计算结果为0。补码很好的解决了反码相加结果为0的问题。
Tips:计算机底层存储数据时普遍使用补码形式。补码不仅能够表示正数和负数,还能够让加法和减法运算统一处理,简化了计算流程,并且能够自然地处理溢出情况。在现代计算机体系结构中,无论是整数还是浮点数,通常都采用补码表示法进行存储和运算。因此,无论是内存中的数据存储还是处理器进行的计算,补码都是被广泛采用的表示方法。
3. 补码的注意事项
八位二进制数的取值范围为:-128到+127,那如果计算的结果超出了这个范围又当如何呢?我们思考一个问题:127+1=?。
127的补码为0111 1111,1的补码:0000 0001,计算如下:
0111 1111 (127)
+ 0000 0001 (1)
-------------
1000 0000 (-128)
正常来说127+1的结果为128,1个字节的取值范围为-128到127,并不能存储128,因而在1个字节内进行127+1的结果为-128。
不仅127+1=-128,另外-128-1的结果也会发生改变。
-128的补码:1000 0000,-1的补码:1111 1111,相加的运算结果如下:
1000 0000 (-128)
+ 1111 1111 (-1)
-------------
111 1111 (127)
上述代码最终计算的结果为127。