原码反码补码 一切都是表象
1.负数为什么用补码表示?
对于十进制数转二进制数,我们并不陌生。考虑8位二进制数,将8表示成二进制数是00001000(原码),-8表示成二进制数是10001000(原码),其中最高位为符号位,0表示正数,1表示负数。
然而在计算机中,负数是用补码来表示的。
必须明确,正数的原码、反码和补码都是它本身。需要额外讨论的,只有负数。除符号位外,对原码按位取反,得到反码;将反码加1,得到补码。
以-8为例,计算方法如下:
原码10001000
反码11110111
补码11111000
为什么不采用更加直观的原码呢,用补码表示负数意义何在?
还是以-8为例,现在有两种表示方法,一种是原码表示法,即10001000;另一种是补码表示法,即11111000。请问,哪一种表示法在加法运算中更方便?
随便写一个计算式,16 + (-8) = ?
16的二进制表示是 00010000,用原码表示法,加法就要写成:
00010000
+10001000
---------
10011000
可以看到,如果按照正常的加法规则,就会得到10011000的结果,转成十进制就是-24。显然,这是错误的答案。也就是说,涉及到负数的加法运算,正常的加法规则不适用。怎么办呢?一种策略是分而治之,即制定两套运算规则,一套用于正数加正数,另一套用于正数加负数。一种策略是合而为一实现一统,即使用补码。
现在,请看补码表示法。
00010000
+11111000
---------
100001000
可以看到,按照正常的加法规则,得到的结果是100001000。注意,这是一个9位的二进制数。我们已经假定这是一台8位机,因此最高位即第9位是一个溢出位,会被自动舍去。所以,结果就变成了00001000,转成十进制正好是8,也就是16 + (-8) 的正确答案。这说明了,补码表示法可以将加法运算规则扩展到全体整数集。为什么负数用补码来表示?为了实现加法运算法则的统一。
2. 时钟与模、补码本质与正确性
我们先看一下模的概念。模是指一个计量系统的计量范围。下图时钟可以看做一个计量器,这是改版的时钟,刻度12改成了刻度0。
时钟的计量范围是0~11,模=12。模”实质上是计量器产生“溢出”的量,它的值无法在计量器上表示出来,计量器上只能表示出模的余数。任何有模的计量器,均可化减法为加法运算。假设当前时针指向10点,而准确时间是2点,调整时间可有以下两种拨法:可以往回拨8个小时,也可以向前拨4个小时。在以12模的系统中,加4和减8是同样的效果。因此,凡是减 8运算,都可以用加 4来代替。相对于模而言,8和4互为补数,原数 +补数=模。实际上以12模的系统中,11和1,10和2,9和3,7和5,6和6都有这个特性。显而易见,两者相加等于模。
计算机也可以看成一个计量器,它也有一个计量范围,即存在一个模。对于n位计算机,假设n=8,每个比特位(bite)可以为0或1,从00000000到11111111,能表示从0~255之间的整数,一共256个,所以8位二进制系统的模为2^8=256。若将n位计算机看成一个计量系统,它的模等于2的n次方。在此系统中,减法问题也可以化成加法问题。相对于模而言,原码和补码互为补码,原码 +补码 =模。
以下数学关系式不言而喻:
原数 +补数=模
原码 +补码 =模
原码 +反码 +1=模
补码 =反码 +1
3.负数需要原码吗?
十进制数 原码 反码 补码
-1 10000001 11111110 11111111
-2 10000010 11111101 11111110
… … … …
-127 11111111 10000000 10000001
-128 - - 10000000
表格中有两个数据是空着的,十进制数-128,原码写成10000000 、反码写成11111111合适吗?实际上,-128并没有原码和反码,而-128的补码是怎么来的呢?观察补码这一列,从上到下,要得出-128的补码是10000000这一结论,应该比较自然。似乎是这样的,补码必须得有,原码有没有无所谓。原码→反码→补码的推导逻辑真的没问题吗,到底谁在前,谁在后?原码+补码→反码的推导逻辑似乎更合理。
理顺原码、反码和补码的关系,需要从头开始。
使用补码,计算1-1=1+(-1)=?
000 0 000 1
+ 1 1 1 1 1 1 1 1
---------
0 0 0 0 000 0
使用补码,计算-1-127=-1+(-127)=?
1 1 1 1 1 1 1 1
+1 00 0 000 1
---------
1 1 0 0 0 000 0
负数表示成原码参与计算时,无法统一加法运算法则;引入反码,计算式1-1的结果为10000000得出-0,0带负号没有任何意义;引入补码,1-1的结果是00000000,解决了-0的问题,同时计算式-1-127的结果为10000000,这样既解决了原先的-0问题,又扩展了负数的范围,即多了一个-128。计算机内部用什么方式表示负数,其实是无所谓的,只要能够保证一一对应的关系。目前使用的补码,应该是最方便简洁的。
4.符号位存不存在?
如前所述,正数的原码、反码和补码都是它本身,负数用补码表示。这是表象,真相并非如此。可以这么说,计算机中根本不存在原码,所有数都以补码形式保存,所有的加减运算都是补码间的加法运算。
数 补码 模-补码 原码
-128 10000000→128 - -
-127 10000001→129 127 11111111
… … … …
-2 11111110→254 2 10000010
-1 11111111→255 1 10000001
0 00000000→0 - 00000000
1 00000001→1 - 00000001
2 00000010→2 - 00000010
3 00000011→3 - 00000011
… … … …
126 01111110→126 - 01111110
127 01111111→127 - 01111111
在-128~127之间的整数与补码之间存在一一对应关系,正数的原码=补码,负数的原码=模-补码。补码的最高位的1当作负号了吗?根本没有,其实是0~255这些整数及其与-128~127的映射关系罢了。在补码加原码等于模的计算过程中,根本就没有把最高位的1当作符号位,而是当作实实在在的1来计算的。符号位是一个善意的谎言,是为了方便人类理解而假想出来的。-128的原码写成1000 0000也无可厚非,却为何不写呢?因为追求严谨。
5 知道了原码的真相后该如何面对原码?
真相是:
机器数根本不存在原码,所有数据都是以补码形式保存。
机器数根本不存在符号位。
逻辑顺序是补码→原码。
正数的原码=补码。
负数的原码=模-补码。
看清了真相,却依然喜爱这样的描述方式:
机器数最高位是符号位,0表示正数,1表示负数。
逻辑顺序是原码→反码→补码。
正数的补码=反码=原码。
负数用补码表示,补码=反码+1。
特别感谢:
https://www.douban.com/note/279593693/
http://blog.****.net/scythe666/article/details/46872391
http://www.cnblogs.com/zhangziqiu/archive/2011/03/30/ComputerCode.html
https://www.zhihu.com/question/20159860/answer/21113783