补码、反码,傻傻分不清?
补码
补码的意义在于表达负数,进而将相对麻烦的减法运算转换为计算机擅长的加法。
我们先来看看,如果单纯用「符号位+原码」的表示方法,会出现什么幺蛾子,比如符号位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:
很OK。
两个负数呢?
除了符号位由于进位出了差错,绝对值上是没毛病的,只要在运算前单独识别一下正负,不让符号位参与运算就行了。
然而,当正数与负数相加,比如1+(-1),也就是两个正数相减1-1,结果会是我们所期望的0吗?
哦不,它不是,它是莫名其妙的-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的补码为:
这个公式已经清晰明了,如果用十进制来进一步理解,就变得更加简单。比如你买了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):
原来补码就是这么一个简单的概念,那为什么印象里总觉得十分混乱呢?罪魁祸首就是该死的反码。
反码
反码就是对原码的每一位进行取反,即n位二进制负数a的反码为:
n位十进制负数a的反码为:
这个概念其实很没必要,它存在的唯一意义就是方便心算补码。还记得那句宛如绕口令般的「口诀」吗?
补码等于原码的反码加1
这个「加1」是怎么来的,且看:
这就好比你买了3块钱的东西,却非要付99元……
尽管反码能帮助初学者快速心算补码,却也容易给我们的长期记忆造成混乱。我打赌,长久不碰之后,你甚至一时半会想不起来反码和补码的区别。
英文中的补码
其实在英文中,反码也叫补码。
但两者的名称反而更容易让人理解。比如对于二进制,补码叫做「two's complement」,反码叫做「ones' complement」。注意那一撇的位置,前者可以理解为「二进制的补码」,后者可以理解为「一堆1的补码」,或简称「补1码」。
对于十进制,补码就是「ten's complement」,反码就是「nines' complement」(补9码)。
「complement」就是「补充、补足」的意思,它强调的是「补」,翻译为「补码」再合适不过,「反码」看似讨巧,却徒增了我们的理解负担。
所以忘掉反码吧,就把补码留下。
补码算法溯源
下面以对人友好的十进制为例,介绍两种使用补码将减法转换为加法的方法。比如要算:
第一种方法是利用被减数的补9码:
- 计算被减数的补9码:999 - 512 = 487
- 将被减数的补9码与减数相加:487 + 128 = 615
- 计算上一步结果的补9码,即最终结果:999 - 615 = 384
该方法的本质是:
第二种方法是利用减数的补9码:
- 计算减数的补9码:999 - 128 = 871
- 将被减数与减数的补9码相加:512 + 871 = 1383
- 丢弃上一步结果最高位上的1:1383 - 1000 = 383
- 将上一步结果加1,即最终结果:383 + 1 = 384
该方法的本质是:
也等价于直接利用减数的补码:
作为计算机补码运算的开山鼻祖,帕斯卡的算术机中用的是第一种方法,而如今的计算机用的是第二种方法。
参考资料
2019年11月8日 无锡