原码
原码:是最简单的机器数表示法,用最高位表示符号位(0正 1负),其他位存放该数的二进制的绝对值。
以带符号位的四位二进制数为例:1010,最高位为1表示这是一个负数,其它三位010,即0 * 2^2 + 1 * 2^1 + 0 * 2^0 = 2,所以1010表示十进制数-2。
9 在8位二进制的表示为:00001001
-9 在8位二进制的表示为:10001001
这样用8个bit表示整数的取值范围是-27-1~27-1,即-127~127。
采用这种表示法,计算机做加法运算需要处理以下逻辑:
- 如果两数符号位相同,就把它们的低7位相加,符号位不变。如果低7位相加时在最高位产生进位,说明结果的绝对值大于127,超出7位所能表示的数值范围,这称为溢出(Overflow),这时通常把计算机中的一个标志位置1表示当前运算产生了溢出。
- 如果两数符号位不同,首先比较它们的低7位谁大,然后用大数减小数,结果的符号位和大数相同。
那么减法如何计算呢?由于我们规定了负数的表示,可以把减法转换成加法来计算,要计算a-b,可以先把b变号然后和a相加,相当于计算a+(-b)。但如果两个加数的符号位不同就要用大数的绝对值减小数的绝对值,这一步减法计算仍然是免不了的。我们知道加法要进位,减法要借位,计算过程是不同的,所以除了要有第 1 节 “为什么计算机用二进制计数”提到的加法器电路之外,还要另外有一套减法器电路。
如果采用Sign and Magnitude(符号和大小)表示法,计算机做加减运算需要处理很多逻辑:比较符号位,比较绝对值,加法改减法,减法改加法,小数减大数改成大数减小数……这是非常低效率的。还有一个缺点是0的表示不唯一,既可以表示成10000000也可以表示成00000000,这进一步增加了逻辑的复杂性,所以我们迫切需要重新设计整数的表示方法使计算过程更简单。
原码的表示法很简单,虽然出现了+0和-0,但是直观易懂。于是开始运算——
0001+0010=0011,1+2=3;
0000+1000=1000,+0+(-0)=-0;
0001+1001=1010,1+(-1)=-2。(错误)
于是可以看到其实正数之间的加法通常是不会出错的,因为它就是一个很简单的二进制加法,而正数与负数相加,或负数与负数相加,就要引起莫名其妙的结果,这都是符号位引起的。0分为+0和-0也是因它而起。
原码的特点:
- 原码表示直观、易懂,与真值转换容易。
- 原码中0有两种不同的表示形式,给使用带来了不便。
通常0的原码用+0表示,若在计算过程中出现了-0,则需要用硬件将-0变成+0。
- 原码表示加减运算复杂。
利用原码进行两数相加运算时,首先要判别两数符号,若同号则做加法,若异号则做减法。在利用原码进行两数相减运算时,不仅要判别两数符号,使得同号相减,异号相加;还要判别两数绝对值的大小,用绝对值大的数减去绝对值小的数,取绝对值大的数的符号为结果的符号。可见,原码表示不便于实现加减运算。
反码
反码:正数的反码还是等于原码;负数的反码就是它的原码除符号位外,按位取反。
9 在8位二进制的原码表示为:10001001,反码为10001001 (正数的反码还是等于原码)
-9 在8位二进制的原码表示为:10001001,反码为11110110 (负数的反码就是它的原码除符号位外,按位取反)
再试着用反码的方式解决一下原码的问题——
0001+1110=1111,1+(-1)=-0;
1110+1100=1010,(-1)+(-3)=-5。
互为相反数相加等于0,虽然的到的结果是1111也就是-0。但是两个负数相加的出错了。
反码的特点
- 在反码表示中,用符号位表示数值的正负,形式与原码表示相同,即0为正;1为负。
- 在反码表示中,数值0有两种表示方法。
- 反码的表示范围与原码的表示范围相同。
反码表示在计算机中往往作为数码变换的中间环节。并不会直接参与结算
补码
补码:正数的补码等于它的原码;负数的补码等于反码+1(这只是一种算补码的方式,多数书对于补码就是这句话)。
9 在8位二进制的原码表示为:10001001,反码为10001001,补码为10001001 (不变)
-9 在8位二进制的原码表示为:10001001,反码为11110110 ,补码为11110111
其实负数的补码等于反码+1只是补码的求法,而不是补码的定义,很多人以为求补码就要先求反码,其实并不是,那些计算机学家并不会心血来潮的把反码+1就定义为补码,只不过补码正好就等于反码+1而已。
如果有兴趣了解补码的严格说法,建议可以看一下《计算机组成原理》,它会用“模”和“同余”的概念,严谨地解释补码。
综上所述,正数的源码、反码、补码都是不变的,负数的反码、补码与源码都是不一致的,所以在计算时,只有负数需要进行反码或者补码。
两个正数的加法运算
示例:16 + 5
16的补码:00010000
5的补码: 00000101
加法运算:00010000 + 00000101 = 00010101
00010101的十进制是:21 —>16 + 5=21
8bit的正数的最大值为:01111111,转为十进制为127。当进行两个正数加法运算时,可能会出现溢出的情况。
126的二进制为01111110
2的二进制为00000010
01111110 + 00000010 = 10000000
最高变成了1,就说明产生了溢出。
所以,两个正数相加时,如果最高为变成了1,就说明了产生了溢出。
一正一负的运算
示例1
16 - 9 也可看做是16 + (-9)
16的补码为:00010000
-9的补码为11110111
00010000 + 11110111 = 100000111
100000111超出了8位,需要去除一位,就去除最高位的1,就成了00000111,00000111的十进制为7
所谓16 - 9 = 7
示例二
5-9,可看做是5 + (-9)
5的补码为:00000101
-9的补码为:11110111
00000101 + 11110111 = 11111100
11111100为-4的补码,即5-9=-4
根据上述,可以看出,无论是正数的加法运算,还是正负数的加法运算,其实是对他们的补码进行运算。
如何判断产生了溢出呢?我们还是分四种情况讨论:如果两个正数相加溢出,结果一定是负数;如果两个负数相加溢出,结果一定是正数;一正一负相加,无论结果是正是负都不可能溢出。
从上图可以得出结论:在相加过程中最高位产生的进位和次高位产生的进位如果相同则没有溢出,如果不同则表示有溢出。逻辑电路的实现可以把这两个进位连接到一个异或门,把异或门的输出连接到溢出标志位。