在计算机中,为什么 -1 要用补码表示为 1111 ?
首先要了解 CPU 的基本单元 ALU 模块。在 ALU 里,加法是最基本的运算。通过程序,乘法可以转换为加分,除法可以转换为减法。补码表示,则可以让减法直接转换为加法。这样,ALU 核心只需要加法器就好,加法器可以通过集成电路中的晶体管来实现。
要将减法转换为加法,在数学公式里,只要加一个括号即可:
X - Y = X + (-Y)
在 CPU 里,如果想让加法器具备减法功能,核心是负数如何表示。
原码
大自然赐予了我们硅,硅是很稳定的半导体,通过 PN 结等精细工艺,可以做成各种晶体管。在晶体管的世界里,基本状态只有 on 和 off 两种,也就是只有
1 和 0。于是,在计算机里,我们采用二进制来表示整数。
这样,最自然的想法是,对于正整数来说,直接从十进制转换成二进制就好。比如 1 用 0001 来表示,2 用 0010 来表示。
负整数怎么来表示呢?增加一个符号位来表示?比如二进制的最高位为 0 时表示正数,为 1 时则表示负数。
这样,-1 用 1001 表示,-2 用 1010 表示。这就是原码的概念。
在原码的表示里:
1 - 1 = 1 + (-1) = 0001 + 1001 = 1010 = -2
显然,原码表示法,不能把减法变成加法,悲催。
补码
据说,所有科学问题,最后都是数学问题,而所有数学问题,最后都离不开哲学。
哲学太深奥了,我们来看看从数学上,如何把 X + (-Y) 转换成二进制相加。
我们希望有某种表示方法,使得 1 + (-1) = 0000 现在 1 用 0001 来表示,-1 应该怎么表示?
-1 = 0000 - 0001
= 10000 - 0001 (高位借一)
= (1111 + 0001) - 0001
= (1111 - 0001) + 0001(第一步:取反)
= 1110 + 0001(第二步:加一)
= 1111
也就是说,如果 -1 用 1111 来表示,则 1 + (-1) 的二进制表示通过加法就能进行减法操作,达成了我们最初的目的。
进一步,针对所有负整数:
-Y = 0000 - Y
= (1111 + 1 ) - Y
= (1111 - Y) + 1 将正整数 Y 的二进制先取反、再加一
整数转换成二进制后,先取反,再加一,这就是负整数的补码表示。正整数的补码跟原码一样。
到此为止,补码的前世今生就说完了,但还有些有意思的事情。
简单之美
可以说,补码的出现,是为了能让 ALU 在设计时更简单:只需要加法电路就好,不需要减法电路。追求简单,极致的简单,这能给性能、工业化等带来很大价值。
再从数学上来看,整数遵循一些很容易被我们忽略的重要规律:
- 数轴上,左边的数永远小于右边的数。比如 -8 < -2 < 0 < 3 < 5
- 数轴上,只有一个零。
- 相邻整数之间相差一。
来看补码表示:
-8 -7 -6 -5 -4 -3 -2 -1 0 1 2 3 4 5 6 7
1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111
- 只看负数的补码表示,明显 1000 < 1001,补码表示下,左边的负数很自然小于右边的负数。
- 很明显只有一个零,就是 0000,不存在 -0 跟 0 的表示不一样。
- 再看相邻整数之间,左边的整数加一,就是右边的整数,包括 1111 + 1 = 0000 。
初看补码,有些怪异、不直观,但在二进制的世界里,补码是如此自然。第一个想出用补码来表示整数的人,真是个天才啊。
(完)