前言
因为CPU运算器中只有加法器,所有要把减法转换加法来运算,同时也是为了节约成本。
我们知道,根据运算法则减去一个正数等于加上一个负数,即:1-1 = 1 + (-1) = 0 ,所以机器可以只有加法而没有减法,这样计算机运算的设计就更简单了。
于是人们想出了将符号位也参与运算的方法。
对于有符号数,符号的“正”“负”机器无法识别,因为正负刚好是截然两种状态,用”0“表示”正“,用”1“表示”负“,这样符号就被数字化了,规定将它们放在有效数字前面,组成有符号数。
原码
人们开始探索 将符号位参与运算,并且只保留加法的方法。首先来看原码:
一个正数,按照绝对值大小转换成的二进制数;一个负数按照绝对值大小转换成的二进制数,然后最高位补1,称为原码。
比如00000000 00000000 00000000 00000101
是 5的 原码。10000000 00000000 00000000 00000101
是-5的 原码。
计算十进制的表达式:1-1=01 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [1000 0010]原 = -2
1 - 1
= 1 + (-1)
= [0000 0001]原 + [1000 0001]原
= [1000 0010]原
= -2
如果用原码表示,让符号位也参与计算,显然对于减法来说,结果是不正确的。这也就是为何计算机内部不使用原码表示一个数。
备注:
比如byte类型,用2^8来表示
无符号整数
的话,是0 - 255了;
如果有符号, 最高位表示符号,0为正,1为负,那么,正常的理解就是 -127 至 +127 了,这就是原码了。
值得一提的是,原码的弱点,有2个0,即+0
和-0
(0000 0000
和1000 0000
);
还有就是,进行异号相加或同号相减时,比较麻烦,先要判断2个数的绝对值大小,然后进行加减操作,最后运算结果的符号还要与大的符号相同。
于是,反码产生了。
反码
为了解决原码做减法的问题,出现了反码:
- 正数的反码与原码相同。
-
负数的反码
为对该数的原码
除符号位外各位取反[每一位取反(除符号位)]。
取反操作指:原为1,得0;原为0,得1。(1变0; 0变1)
比如:
- 正数 5
00000000 00000000 00000000 00000101
的反码
还是—00000000 00000000 00000000 00000101
; - 负数 -5
10000000 00000000 00000000 00000101
的反码
则是—11111111 11111111 11111111 11111010
;
反码是相互的,所以也可称:10000000 00000000 00000000 00000101
和11111111 11111111 11111111 11111010
互为反码。
计算十进制的表达式: 1-1=0
1 - 1
= 1 + (-1)
= [0000 0001]原 + [1000 0001]原
= [0000 0001]反 + [1111 1110]反
= [1111 1111]反
= [1000 0000]原
= -0
发现用反码计算减法,结果的真值部分是正确的
。而唯一的问题其实就出现在0
这个特殊的数值上。
虽然人们理解上+0
和-0
是一样的,但是0
带符号是没有任何意义的,而且会有[0000 0000]原
和[1000 0000]原
两个编码表示0
。
补码
于是补码的出现, 解决了0的符号以及两个编码的问题:
- 正数的补码与原码相同;
- 负数的补码为对该数的原码除符号位外各位取反,然后在最后一位加1;
而使用补码表示时又可以多保存一个最小值。
.
比如:-5
的原码是10000000 00000000 00000000 00000101
反码是:11111111 11111111 11111111 11111010
。
那么,补码为:11111111 11111111 11111111 11111010 + 1 = 11111111 11111111 11111111 11111011
计算:
1 - 1
= 1 + (-1)
= [0000 0001]原 + [1000 0001]原
= [0000 0001]补 + [1111 1111]补
= [0000 0000]补 = [0000 0000]原
这样0用[0000 0000]
表示, 而以前出现问题的-0
则不存在
了,而且可以用[1000 0000]
表示-128
:
(-1) + (-127)
= [1000 0001]原 + [1111 1111]原
= [1111 1111]补 + [1000 0001]补
= [1000 0000]补
-1 - 127
的结果应该是-128
,在用补码运算的结果中,[1000 0000]补
就是-128
。
但是注意,因为实际上是使用以前的-0的补码来表示-128,所以 -128并没有原码和反码表示。(对-128的补码表示[1000 0000]补
算出来的原码是[0000 0000]原
,这是不正确的)
使用补码,不仅仅修复了0的符号以及存在两个编码的问题,而且还能够多表示一个最低数。这就是为什么8位二进制,使用原码或反码表示的范围为[-127, +127]
,而使用补码表示的范围为[-128, 127]
。
因为机器使用补码,所以对于编程中常用到的32位int类型,可以表示范围是: [-2^31, 2^31-1]
因为第一位表示的是符号位。
备注:
1、从补码求原码的方法跟原码求补码是一样的 ,也可以通过完全逆运算来做(除了-128也就是[1000 0000]补
),先减一,再取反。
2、补码却规定0没有正负之分。
总结
- 三种机械数的最高位均为符号位。
- 符号位和数值位可以用
.
或者,
隔开。 - 当真值为正时,原码、补码、反码的表现形式均相同,符号位为0,数值部分和真值相同。
- 当真值为负时,原码、补码、反码的表现形式均相同,符号位为1,数值部分,补码是原码的”求反加1’,反码是原码的“每位求反”。