03.编码与原码、补码、反码
03.编码与原码、补码、反码
什么是信息
在讲什么是编码之前,先讲一下什么是信息。不同的学科对信息这个单词的解读都不一样。我们这里可以简单的理解为信息就是知识的传递,它描述了一种状态或者一个事实。
那么我们如何传递信息呢?这就需要用到编码。
比如我有这样一个信息,今天是2020年1月1号。我可以用文字的形式将其表示出来,也可以用语音的形式向其表示出来,还可以用手语或者用盲文将其表示出来。在这里文字是一种编码的形式,语音也是一种编码方式。手语和盲文也是一种编码的形式。
我们这里下一个定义。码制就是表示事物的规则。这里的码制就是指文字,语音,手语和盲文。
将阿拉伯数字表示成其他进制中的数,也是一种编码方式。但我们要注意,即使是在其他进制中,数字也是可以做加减乘除四则运算的,也是可以进位的。
原码
我们如果要表示一个数的二进制数方式,我们可以直接写出它的二进制数,但如何表示一个负的二进制数呢?在我们日常使用十进制数进行运算时,我们是用一个负号”-“,放到数字前面表示这是一个负数。当然我们手工计算二进制时,这样做也是可以的。
而在计算机中,我们并没有表示负号的机制。计算机只能处理0或者1。
那我们可不可以在二进制数之前加一位数,例如0表示正数,1表示负数?当然是可以的。我们称这样的编码方式为原码。
例如+5可以这样表示 0 0101
, -5可以这样表示 1 0101
但如果我们直接用原码去做二进制计算,是不可以的。例如上述的两个数相加,结果应该是0,但如果我们这样表示二进制数,和我们想要的结果不符合:
0 0101
1 0101
1 1010
1 1010
表示的是-9!为什么呢?因为我们这里用0表示负数,1表示正数,这其实是一种编码方式,不是一种数学中进位的方式,****我们这里将码制跟数制混合起来了,这是一种混合的编码方式。最高位的一他代表的是是事物,表示这个数是正还是负,而不是表示数字。也就是这样混合编码的话,四则运算就失效了。
所以如果我们要用原码计算,必须先比较出两数的绝对值,然后相减,得到差值。这个方法很麻烦,用电路实现的话还需要用到比较电路和减法电路。
我们知道,二进制的加法是很简单的,无非就是:
- 0 + 0 = 0
- 0 + 1 = 1
- 1 + 1 = 10
即使是多位二进制数,进位也只需要一位一位往前进就行。
而如果一旦涉及到减法,就要考虑借位了。而用电路实现借位,是非常麻烦的。我们来做一道减法题:
2 5 3
- 1 7 6
= 7 7
要做这道题,从最右边一列开始。首先,6比3大,所以需要从5借1,这样就变成了13减6,结果是7。
由于从5借了1,5就变成了4,4比7小,所以继续从2借1,14减7等于7。
2被借1后成为1,1减1为0,所以最后结果是77
幸运的是,早于十六世纪,法国数学家布莱士·帕斯卡(Blaise Pascal 1623-1662)早已想到办法,通过某种方式,可以将减法变为加法,这样,只需要加法电路,我们就可以完成加减法了!该方法就是补码,计算机中沿用至今。
补码
模、补数:在日常生活当中,可以看到很多这样的事情:
- 把某物体左转 90 度,和右转 270 度,在不考虑圈数的条件下,最终的效果是相同的;
- 把分针倒拨 20 分钟,和正拨 40 分钟,在不考虑时针的条件下,效果也是相同的;
- 把数字 87,减去 25,和加上 75,在不考虑百位数的条件下,效果也是相同的;
- ……
上述几组数字,有这样的关系:
- 90 + 270 = 360
- 20 + 40 = 60
- 25 + 75 = 100
式中的 360、60 和 100,就是“模”。式中的 90 和 270、20 和 40,以及 25 和 75,就是一对对“互补”的数字。
知道了“模”,求某个数字的“补数”,就是轻而易举的了:如果模为 365,数字 120 的补数为:365 - 120 = 245。
用补数代替原数,可把减法转变为加法。出现的进位就是模,此时的进位,就应该忽略不计。
有了补码,我们怎么计算呢?举例,有个小孩,只能认识 100 个数。超过 100,就从 0 开始数。他很小,只会加法,还不会做减法。可以这样教他:减一,就用加上 99 代替。因为:
- 28 -1 = 27, 减去1,等于加上1 的补数, [1]补 = 模(100) - 1 = 99
- 28 + 99 = (1)27
同样,减二,可以用加上 98 代替;
- -3,可以用+97 代替;
- -4,可以用+96 代替;
- …… 以此类推
在 100 个数字的范围内,
1、99,是一对互补的数字。
2、98,也是。
用互补的数,代替原数,即可把减法,转换成加法。注意,要忽略进位。
有补码的好处:用了补码,就不用借位了。这句话是重点,重点,重点。例如:
28 -1 = 28 - (100 - 99) = 28 + 99 - 100,这样,28 和99相加,是不用借位的;
但我们求 -1的补码,100 - 99,还是要借位,怎么办呢?我们可以转换成这样:
28 - 1 = 28 -(99 -99 +1) = 28 +99 -99 -1 = 28 + 99 - (99 + 1)= 28 + 99 -100。
我们再举一个例子,求 - 66的补码:
[66]补 = 模 - 66 = 100 - 66 = 99 + 1 -66 = 99 -66 +1
其中,99 - 66 也是不用借位的。
以上是求补码的原理,也是二进制求补码的原理。
现在,我们回到二进制。在八位二进制数字的范围内,共有 256 个数字。0000 0000~1111 1111,即 0~255。
- 1、255,是一对互补的数字。
- 2、254,也是。
- ……以此类推;
那么,-1,就可以用 +255 代替。
-2,也就可以用 +254 代替。
即:
[-1]补=255。
[-2]补=254。
计算时,加上正数,是不需要求取补数的;只有进行减法(或者加上负数),才需要对减数求补数。
由此,我们引申出补码的定义:
- 正数不变;
- 负数即用模减去绝对值。
那么,已知一个数 N ,其 8 位字长的补码定义为:
- [N]补 = X ……………0 <= N <= +127,正数和 0 的补码,就是该数字本身
- [N]补 = 28-|N|…… -28 <= X < 0,负数的补码,就是用模,减去该数字的绝对值
计算机的书上,一般,都有这个定义式。
我们引申下补码的通用定义:对于有效数字(不包含符号位)为 n 位的二进制数 N,它的补码 (N)comp 表示如下:
- (N)comp = N (当N为正数)
- (N)comp = 2n-N…… (当N为负数)
但是,求补码的过程中,还是有涉及到借位,怎么办呢?我们可以这样: 例如有效位数为8,求 (-26)10 的二进制补码,我们可以先求出其 二进制源码为: 0001 1010
然后,就是用28 - 0001 1010
1 0000 0000
- 0001 1010
为了不借位,我们可以将28 变成 28 -1 + 1。 因为28 = 28 -1 + 1
其中,28 -1 = (1111 1111)2
那么,28 - 0001 1010 可以变成这样子:
1111 1111
+ 1
- 0001 1010
我们调换下顺序:
1111 1111
- 0001 1010
+ 1
因为在一个二进制数中,只有0和1,1是最大的, 1减去1, 1减去0,都不用借位。而且,我们观察到,1-1=0, 1-0 = 1
从结果来看,我们把被减数的每一个结果取反(1取反得0,0取反得1),就能得到结果,我们还要必要做减法吗?
(1111 1111)2 - (0001 1010)2 的结果,和我们直接取反(0001 1010)2 的结果,是一样的。然后我们再加上1,不就得到补码了吗?
反码
由此我们引申出的定义:二进制数 N 的反码 (N)inv 的定义:
- (N)inv = N 当N为正数
- (N)inv = 2n - 1 - N 当N为负数
2n - 1 是n位全为1的二进制数,所以求 负数的补码的时候,只要将负数的每一位1改为0, 0改为1,也就是取反,就可以得到反码了。后续我们会看到,在电路上,实现取反一个二进制数是很容易的(至少比实现借位方便)。
小结
我们求补码的话,一般是采用取反加一的办法,希望你能理解它的原理,而不是死记硬背。
原码、反码、补码对照表
这里以4位二进制数为例 (8位的太多了)
十进制数 | 二进制原码(带符号数) | 二进制反码 | 二进制补码 |
---|---|---|---|
+7 | 0111 | 0111 | 0111 |
+6 | 0110 | 0110 | 0110 |
+5 | 0101 | 0101 | 0101 |
+4 | 0100 | 0100 | 0100 |
+3 | 0011 | 0011 | 0011 |
+2 | 0010 | 0010 | 0010 |
+1 | 0001 | 0001 | 0001 |
+0 | 0000 | 0000 | 0000 |
-1 | 1001 | 1110 | 1111 |
-2 | 1010 | 1101 | 1110 |
-3 | 1011 | 1100 | 1101 |
-4 | 1100 | 1011 | 1100 |
-5 | 1101 | 1010 | 1011 |
-6 | 1110 | 1001 | 1010 |
-7 | 1111 | 1000 | 1001 |
-8 | 1000 | 1111 | 1000 |
补码与十进制转换
如何通过负数的二进制补码求十进制数? 可以看成 第一位是-127的权重,在清华大学的课程这样说道:
其实,如何通过负数的二进制补码求十进制数? 可以看成 第一位是-127的权重,在清华大学的课程这样说道:
我说是二进制的补码的话,那么这个时候这一串二进制数它所给的权发生了一点变化。大家看最高位在刚才的编码的时候,我们是不是把它当成了一个符号位?所谓符号位是不是认为它不带大小,对吗?但是如果当你进行补码编码的时候,它是一个带符号的,同时也是带大小的这么一个权值,它是一个负的权值。那么如果这样一下来之后,是不是说我后边的都是正数了?没问题,后边这都是正数。
我说是二进制的补码的话,那么这个时候这一串二进制数它所给的权发生了一点变化。大家看最高位在刚才的编码的时候,我们是不是把它当成了一个符号位?所谓符号位是不是认为它不带大小,对吗?
但是如果当你进行补码编码的时候,它是一个带符号的,同时也是带大小的这么一个权值,它是一个负的权值。那么如果这样一下来之后,是不是说我后边的位都是正数了?没问题,后边这都是正数。
例如我们求 (1001)2 的补码,表示的十进制数是多少,两种方法:
- 减一后取反。 1001 - 1 = 1000 ,取反后就是0111 ,也就是说是-7的补码
- 我们可以理解为,第一位二进制数是带符号的, 后续的每一位二进制数都是不带符号的,那么 (1001)2 就可以理解为是 1×(-23) + 0 × 22 + 0×21 + 1×20 = - 8 + 1 = -7
数字 0 ~+127,直接就是补码。
数字 -128 ~ -1,是用 128~255 当作补码。
一些术语
补码:Complement,英语里又叫two's complement(对2求补)
反码:Inverse code
有些教材会称 反码为1的补码, 1's Complement
参考
《数字电路基础》第五版 作者:阎石 第1.4.2节
《编码》:第13章,如何实现减法 "第13章 如何实现减法"
《计算机组成原理-白中英》:2.1节