补码、反码,傻傻分不清?

2019-11-09  本文已影响0人  逸之

补码

补码的意义在于表达负数,进而将相对麻烦的减法运算转换为计算机擅长的加法。

我们先来看看,如果单纯用「符号位+原码」的表示方法,会出现什么幺蛾子,比如符号位0表示正、1表示负:

正数 二进制原码 负数 二进制原码
0 0 0000 -0 1 0000
1 0 0001 -1 1 0001
2 0 0010 -2 1 0010
3 0 0011 -3 1 0011
4 0 0100 -4 1 0100

当进行两个正数的相加,比如1+2=3:
\begin{matrix} & 0\ 0001 \\ + & 0\ 0010 \\ \hline & 0\ 0011 \end{matrix}

很OK。

两个负数呢?
\begin{matrix} & 1\ 0001 \\ + & 1\ 0010 \\ \hline & 0\ 0011 \end{matrix}

除了符号位由于进位出了差错,绝对值上是没毛病的,只要在运算前单独识别一下正负,不让符号位参与运算就行了。

然而,当正数与负数相加,比如1+(-1),也就是两个正数相减1-1,结果会是我们所期望的0吗?
\begin{matrix} & 0\ 0001 \\ + & 1\ 0001 \\ \hline & 1\ 0010 \end{matrix}

哦不,它不是,它是莫名其妙的-2。

于是就轮到了补码登场。

时钟就是生活中一个用到补码思想的活生生的例子,在我们定时(比如定闹钟)的时候,如果要将原本的6点调整到9点,可以选择朝前拨3个小时,也可以选择朝后拨9个小时。因为在不考虑进位的情况下,6+3=6-9。这意味着,3和-9有着同等的效力,因为在时钟的十二进制中,3就是-9的补码。同理,-8的补码是4,-7的补码是5,等等。规律是,两者的绝对值之和为12。

推广到二进制上,-1的补码是1(两者绝对值之和为2),-01的补码是11(两者绝对值之和为4),-001的补码是111(两者绝对值之和为8)……
总结而言:n位二进制负数a的补码为:
2^n-|a|

这个公式已经清晰明了,如果用十进制来进一步理解,就变得更加简单。比如你买了3块钱的东西,给老板一张10元纸币,老板就需要找你7元。如果你给老板一张100元的纸币,老板就要找你97元。这个找头,就是补码。

于是,对于4位二进制数,有:(正数还是写作原码)

正数 二进制补码 负数 二进制补码
0 0000 -0 0000
1 0001 -1 1111
2 0010 -2 1110
3 0011 -3 1101
4 0100 -4 1100

我们不再需要符号位,并且正负数的相加运算就变得完美无瑕,比如1+(-2):
\begin{matrix} & 0001 \\ + & 1110 \\ \hline & 1111 \end{matrix}

原来补码就是这么一个简单的概念,那为什么印象里总觉得十分混乱呢?罪魁祸首就是该死的反码。

反码

反码就是对原码的每一位进行取反,即n位二进制负数a的反码为:
\begin{matrix} \underbrace{ 11\cdots1 } & -\ |a| \\ n个1 \end{matrix}

n位十进制负数a的反码为:
\begin{matrix} \underbrace{ 99\cdots9 } & -\ |a| \\ n个9 \end{matrix}

这个概念其实很没必要,它存在的唯一意义就是方便心算补码。还记得那句宛如绕口令般的「口诀」吗?

补码等于原码的反码加1

这个「加1」是怎么来的,且看:
\begin{cases} a的补码 = 2^n - |a| \\ a的反码 = (2^n-1) - |a| \end{cases}

这就好比你买了3块钱的东西,却非要付99元……

尽管反码能帮助初学者快速心算补码,却也容易给我们的长期记忆造成混乱。我打赌,长久不碰之后,你甚至一时半会想不起来反码和补码的区别。

英文中的补码

其实在英文中,反码也叫补码。

但两者的名称反而更容易让人理解。比如对于二进制,补码叫做「two's complement」,反码叫做「ones' complement」。注意那一撇的位置,前者可以理解为「二进制的补码」,后者可以理解为「一堆1的补码」,或简称「补1码」。

对于十进制,补码就是「ten's complement」,反码就是「nines' complement」(补9码)。

「complement」就是「补充、补足」的意思,它强调的是「补」,翻译为「补码」再合适不过,「反码」看似讨巧,却徒增了我们的理解负担。

所以忘掉反码吧,就把补码留下。

补码算法溯源

下面以对人友好的十进制为例,介绍两种使用补码将减法转换为加法的方法。比如要算:
\begin{matrix} & 512 \\ - & 128 \\ \hline & 384 \end{matrix}

第一种方法是利用被减数的补9码:

  1. 计算被减数的补9码:999 - 512 = 487
  2. 将被减数的补9码与减数相加:487 + 128 = 615
  3. 计算上一步结果的补9码,即最终结果:999 - 615 = 384

该方法的本质是:
a - b = 999 - [(999 - a) + b]

第二种方法是利用减数的补9码:

  1. 计算减数的补9码:999 - 128 = 871
  2. 将被减数与减数的补9码相加:512 + 871 = 1383
  3. 丢弃上一步结果最高位上的1:1383 - 1000 = 383
  4. 将上一步结果加1,即最终结果:383 + 1 = 384

该方法的本质是:
a - b = a + (999-b) - 1000 + 1

也等价于直接利用减数的补码:
a - b = a + (1000-b) - 1000

作为计算机补码运算的开山鼻祖,帕斯卡的算术机中用的是第一种方法,而如今的计算机用的是第二种方法。

参考资料


2019年11月8日 无锡

上一篇 下一篇

猜你喜欢

热点阅读