深入理解计算机系统(3)
本文我们主要讲关于数据的的表示方式:原码,反码和补码。
本文在写作过程中,参考了园中的这篇文章《原码,反码,补码详解》,特此声明。
一原码
计算机中是使用二进制来表示数据的,对于C语言这样的强类型语言,每一个数值类型,都有其范围,例如一个int类型,在32位机器上,其表示的范围如下:
|
最小值 |
最大值 |
有符号整数 |
-2147483648 |
217483647 |
无符号整数 |
0 |
4294967295 |
而如果我们定义了一个int类型,给其的赋值,超出了这个范围,则会出现问题。一般编译器会检查这个问题,会出现编译错误。
为了简单,我们以一个char类型(8位)来进行举例:
|
最小值 |
最大值 |
有符号char |
-128 |
127 |
无符号char |
0 |
255 |
那么什么是原码呢:原码是二进制的一种表示方式,其规特点是:
无符号数的每一位都是数字位,
有符号数的最高位是符号位,0表示正数,1表示负数,其余各位表示数值。
1.1无符号数的原码
先说无符号的情况,我们选择几个值
十进制 |
二进制原码 |
0 |
00000000 |
127 |
01111111 |
128 |
10000000 |
255 |
11111111 |
可见最高位为1,表示十进制的数字128,8位全部都是1,表示255。
例如计算两个数字1和1相加:
|
十进制 |
无符号二进制原码表示 |
加数1 |
1 |
00000001 |
加数2 |
1 |
00000001 |
求和 |
2 |
00000010 |
因此,对于一个无符号数,使用原码表示是正常的。
1.2有符号数的原码
对于有符号数,用原码表示负数,要怎么表示呢?
因为是二进制,也就只有0和1两种符号,于是就定义最高位为符号位:最高位为0的原码,表示一个非负数;最高位为1的原码,表示一个负数。
十进制 |
无符号原码 |
有符号原码 |
0 |
00000000 |
00000000 |
127 |
01111111 |
01111111 |
-127 |
超出范围,无法表示 |
11111111 |
255 |
11111111 |
超出范围,无法表示 |
由上面的表格,我们可以发现原码表示的特点:
1对于一个非负数,在不超出其表示范围的前提下,无符号数和有符号数的原码表示形式是一样的;
2对于一个负数(有符号数),在不超出其表示范围的前提下,其最高位为1,而其余各位与无符号数一样。
那么如果我们在进行计算的时候,使用原码来对两个有符号数进行二进制加法运算会怎么样呢?
两个数都非负数,计算结果是是正确的;
如果有一个数是负数,就会出现问题:
例如:计算如下的两个数字,1和-1相加:
|
十进制 |
有符号二进制原码表示 |
加数1 |
1 |
00000001 |
加数2 |
-1 |
10000001 |
求和 |
-2 |
10000010 |
1+(-1) = -2
这显然,不是我们想要的结果。
如果想用原码表示,就需要计算机对负数的原码进行特殊的处理。如上面的例子,加数2的值是10000001,那么最高位不参与计算,只用于标记这是一个负数。然后将一个正数和另一个负数的加法,转变为两个正数的减法。
即将1+(-1)变为1-1,按照二进制的减法运算法则,得到
|
十进制 |
有符号二进制原码表示 |
被减数 |
1 |
00000001 |
减数 |
1 |
00000001 |
求差 |
0 |
00000000 |
1-1=0
这样就得到了,我们想要的结果。
这样看起来似乎也是可以接受的,那我们再算一些其他的数字,例如1+(-2),按照上面的规则,我们要将其变为1-2
|
十进制 |
有符号二进制原码表示 |
被减数 |
1 |
00000001 |
减数 |
2 |
00000002 |
求差 |
? |
? |
由于1小于2,因此1减2,等于2减1,再对结果求其相反数。根据减法的规则,1-2= - (2-1)
|
十进制 |
有符号二进制原码表示 |
被减数 |
2 |
00000002 |
减数 |
1 |
00000001 |
求差 |
1 |
00000001 |
求反 |
-1 |
10000001 |
这样我们也得到了,正确的结果。
但是我们要注意以下几点问题:
1要判断运算数中是否有负数
2可能将加法运算变为减法运算
3可能要求其相反数
可见,本来一个简单的加法的运算,由于负数的问题,变得复杂了。而要处理这些问题,计算机需要一些额外存储空间,以及额外的计算步骤。相应的电路的设计就要更加复杂。
结论:复杂度高,不适合。
二反码
如果使用原码可以很方面的进行计算的话,我也就不会使用反码的方式来表示二进制了。
从上一节可以发现,使用原码的方式在做负数的加法时,比较麻烦。
而负数,表示的是一个正数的相反数,那么我们将一个二进制数按位取反,会有怎样的现象呢?
我们看以下这些数字。
原始值 |
无符号数原码 |
原码按位取反 |
去反后的值 |
0 |
00000000 |
11111111 |
255 |
127 |
01111111 |
10000000 |
128 |
255 |
11111111 |
00000000 |
0 |
可以看出,无符号数,二进制按位取反,其结果为原值被最大值(255)减,即:
0按位取反表示255 – 0 = 255
127按位取反表示 255 – 127 = 128
255按位取反表示 255 – 255 = 0
那么对于有符号数有是什么样呢?我们看以下有符号数字,原码符号位不变,数字位按位取反的计算结果
原始值 |
有符号数原码 |
原码按位取反 |
取反后的值 |
-127 |
11111111 |
10000000 |
-0 |
-3 |
10000011 |
11111100 |
-124 |
-1 |
10000001 |
11111110 |
-126 |
0 |
00000000 |
01111111 |
127 |
1 |
00000001 |
01111110 |
126 |
3 |
00000011 |
01111100 |
124 |
127 |
01111111 |
00000000 |
0 |
可以看出,有符号数,除了符号位,二进制按位取反,其结果的绝对值为原值被最大值(127)减,即:
-127按位取反表示- (127 – 127 ) = -0
-3按位取反表示 – (127 – 3 ) = - 124
0按位取反表示 +(127 – 0 )= 127
3按位取反表示+(127 – 3) = 124
127按位取反表示+(127 – 127 )= 0
那么如果我们在计算有符号数加法时,我们可以得到如下的四种情况
|
使用原码 |
使用原码取反 |
正数 |
|
|
负数 |
|
|
我们按照这四种情况分别计算1 + (-1),使用二进制加法,看看有什么有趣的现象
1正数使用机器码,负数使用机器码
在计算符号不同的两个数值的加法时,最终结果的符合与绝对值大的那个相同。
但是如果两个数值不为0,且互为相反数,那最终的结果的符号就不一定了。
|
十进制 |
有符号二进制原码表示 |
加数1 |
1 |
00000001 |
加数2 |
-1 |
10000001 |
求和 |
? |
x0000010 |
x=0 |
2 |
00000010 |
X=1 |
-2 |
10000010 |
无论x是正还是负,结果似乎都不正确。
2正数使用机器码,负数使用机器码按位取反
|
十进制 |
有符号二进制原码表示 |
加数1 |
1 |
00000001 |
加数2(按位取反) |
-2 |
11111101 |
求和 |
-1 |
10000001 |
我们定义最高位符号位的值是x,
对于1 + (-2) ,由于负数的绝对值(2)大于正数的绝对值(1),因此可以确定,x的值是1。
也就是首先能确定最终结果是个负数,然后计算其后面的数值位,
七位的:0000001
加上
七位的:1111101
得到
七位的:1111110
再加上符号位,得到
八位的:11111110
按照前提,这是一个负数,其真值要对其数值为按位取反,得到10000001,即 -1。
这个结果,貌似很好。但是如果计算1+ (-1)会怎么样呢?
|
十进制 |
有符号二进制原码表示 |
加数1 |
1 |
00000001 |
加数2(按位取反) |
-1 |
11111110 |
求和 |
?? |
X1111111 |
X=0 |
127 |
01111111 |
X=1(按位取反) |
-0 |
10000000 |
这里在计算00000001和11111110时,由于1和-1的绝对值相等,那么其符号就无法确定。那么我们先不考虑符号位,将数值位进行二进制加法,然后得到x1111111。
那么这时候,x的值有两种可能:
X = 0 ,那这个数字是正数,正数值为:127
X = 1,那这个数字是负数,负数在本环境下是要按位取反来得到其真值的,因此
11111111按位取反,得到10000000,即真值为- 0
那么到底x等于几呢?这个似乎也是个不太好说的问题。
对于 -0 和 127,貌似 -0更加贴近最终的答案,那可以人为的规定,如果负数按位取反,在计算时,符号位优先使用1,即x值无法确定时,优先取1。
我们再计算一下-1 + (-1)
|
十进制 |
有符号二进制原码表示 |
加数1(按位取反) |
-1 |
11111110 |
加数2(按位取反) |
-1 |
11111110 |
求和 |
-124 |
11111100(溢出) |
按位取反 |
-3 |
10000011 |
3正数使用机器码按位取反,负数使用机器码
|
十进制 |
有符号二进制原码表示 |
加数1(按位取反) |
2 |
01111101 |
加数2 |
-1 |
10000001 |
求和 |
126 |
01111110 |
按位取反 |
1 |
00000001 |
|
十进制 |
有符号二进制原码表示 |
加数1(按位取反) |
1 |
01111110 |
加数2 |
-1 |
10000001 |
求和 |
?? |
X1111111 |
X=0(按位取反) |
0 |
00000000 |
X=1 |
-127 |
11111111 |
根据第2中情况的分析,我们可以得到上表的结果。
此时,x的值仍然有两种情况,
X优先取0,则得到结果 0
X优先取1,则得到结果-127
那么这种情况下可以规定,x值优先取0。
我们计算1 + 1
|
十进制 |
有符号二进制原码表示 |
加数1(按位取反) |
1 |
01111110 |
加数2(按位取反) |
1 |
01111110 |
求和 |
124 |
01111100(溢出) |
按位取反 |
3 |
00000011 |
4正数使用机器码取反,负数使用机器码取反
|
十进制 |
有符号二进制原码表示 |
加数1(按位取反) |
1 |
01111110 |
加数2(按位取反) |
-1 |
11111110 |
求和 |
?? |
X1111100(溢出) |
X=0(按位取反) |
3 |
00000011 |
X=1(按位取反) |
-3 |
10000011 |
归纳上面的规律
序号 |
名称 |
1+1 |
2+2 |
1+(-1) |
1+(-2) |
-1+(-1) |
-2+(-2) |
1 |
正原负原码 |
2 |
4 |
2或-2 |
-3 |
-2 |
-4 |
2 |
正原负反码 |
2 |
4 |
-0或127 |
-1 |
-3 |
-1 |
3 |
正反负原码 |
3 |
1 |
0或-127 |
-0 |
-2 |
-4 |
4 |
正反负反码 |
3 |
|
3或-3 |
|
-3 |
|
可以看到,有价值的是1和2,将两种组合,应该可以计算出正确的结果。
|
正+正 |
正+负 |
负+负 |
结论 |
1 |
Y |
N |
Y |
符号相同成立 |
2 |
Y |
Y |
N |
符号不同成立,双正成立 |
我们使用1和2结合,得到如下二进制加法计算规则:
在符号相同时,符号位不变,数值位直接进行二进制加法。
在符号不同时,符号位由绝对值大的数字的符号决定,如果绝对值相同,优先使用负值,而负数将数值部分,用机器码按位取反后,再与正数进行二进制加法。
那么按照这个规则,在进行计算机的运算处理时,需要考虑哪些问题呢?
1要判断数字的符号
2要根据符号相同和不同采取不同的策略
这里面,1是必须要考虑的,因为有符号数字,必须要有一个位来标识符号。
有了这一点,就可以解决同符号的加法问题了。
对于不同符号的加法,如果正数和负数绝对值相同,仍然是个不好处理的问题。
结论:复杂度低,但是还有待改善
三补码
上节讲的,正数用机器码表示,负数用机器码按位取反的表示方法,就是反码。
而且,一个数字的反码所表示的数字,与自身的和,是固定值——这个数值所能表示的最大值。
对于8位无符号数值类型的变量,其最大值是255。
而7位的话,最大值是127。
因此,对于1+(-2),可以先确定符号位是1,然后计算000 0001和000 0010(取反)的和
即,000 0001 + 111 1101,结果是111 1110。再取反,得到000 0001。再补上最高位的1
得到,1000 0001,结果就是-1。
当正值和负值的绝对值相同的时候,例如 1+(-1),我们规定此时符号0,然后计算
000 0001和000 0001(取反)的和,即000 0001 + 111 1110,结果是111 1111。由于这个数字式正数,因此其值为127。注意,这里的前提是,正数保持原样,负数按位取反。
那么127,代表什么呢?
对于一个char字符,除去其最高位符号位,还剩下7位。
7位二进制的表示范围是000 0000到111 1111,即0到127,共128个数字。
从000 0000开始加1,加127次达到最大值。
达到最大值之后,如果再加1,就得到1 000 0000。而这里的最高位因为溢出而会被舍去,因此只存储了000 0000这7位值,这个值就回到了最初。
对于钟表来讲,从1开始,依次加1,加到12时,达到了最大值。再加1的话,就得到了13,而13由于溢出,因此只保留了1。
因此,我们可以得到这个结果,如果最小值从1开始,那么最大值就是周期。
因此钟表的周期是12。
那么7位二进制数的周期是多少呢?从0到127,为了和钟表统一,我们将其整体加1:
从1开始,最大到128。这样可以推出,7位二进制的周期是128。
也就是说,7位二进制数,表示的数字,具备这样的特性。
数字N与其按位取反的数字M,必有N+M=周期-1。
因此N=周期-1-M。
而周期会溢出,因此在N上增加若干整数倍周期,与没有增加周期,其结果是一样的。
因此有:周期+N = 周期 – 1 – M。注意这里的相等,是去掉溢出位之后相等。
那么所有两边都去掉一个周期。
就得到
N = -1 – M即N= -(M+1)
因此就有 -N = M + 1
根据同余的特性,对于任意数字X,X在7位二进制数字表示范围内:
有X+(-N) = X+ M+1
而M为N的按位取反(反码)。所以对于一个负数,在其二进制反码的基础上在加上数字1,就得到了其同余的数值。这就是现在计算机中实际表示数据的形式——二进制的补码。
以上的推导过程,不是严格的数学证明,只是为了方便理解。
而我们用补码方式来进行计算1 + (-1)
首先确定符号位的值是0
因此计算000 0001 和 000 0001(求补)的和,即
000 0001 + 111 1111 因此,得到000 0000,再补充上最高位0,
得到0 000 0000,即数字0。
完美解决了,在使用反码时,正数情况下的127而负数情况下-0的问题。
结论:使用补码存储数值,可以用加法的运算处理减法。计算相反数的加法,也正确。
可以使用。
四总结
数学是计算机的基础,从计算机的二进制存储可见一斑。因此我们在学习计算机技术的同时,加强数学知识的学习,是非常重要的。