一文搞懂补码 前言
在学习计算机组成原理时经常接触到补码,我们知道计算机使用补码来表示负数,并且负数的补码按位取反再加一是他对应的正数。但是我们往往知道的也就仅限于此,其原理和原因似乎不被重视。像这样对一些概念浮于表面的后果就是,当涉及到这个概念更深层次的问题时,我们原有的理解就会变得促膝见肘,一些现象无法从已有的知识解释。而当我们再次深层次的去挖掘原理时,就会发现,其背后往往是数学在施展魔法。
如何用加法代替减法 首先,为什么计算机不直接计算减法?如果计算机在底层可以直接进行两个数的减法计算,那么就不需要补码什么事了。原因也很简单,纯粹的二进制减法实现起来相当复杂。而使用补码可以将减法通通转化成加法计算,CPU 对于执行加法运算非常快速且简单,而且如此一来计算机就可以透明的计算加减法。
表盘模型 那么,怎么实现加法代替减法呢?我们可以参考时钟的表盘。
假如我们有 0~9 共 10 个数,他们被均匀刻在一个表盘上:
我们可以在上面取到 0 ~ 9 共 10 个刻度(每个刻度表示某个范围内的每个整数,比如0~255,这里简便起见只考虑 0~9),表盘上的指针可以从一个刻度拨动到下一个刻度。从任意一个刻度朝固定方向连续移动 D = 10 次,指针将再次指向出发刻度。现在我们讨论如何在上面表示负数。假设在圆盘上从 0 刻度顺时针移动一次,有 a = 1 (a 指向刻度 1)如下图:
现在我们想知道,如何表示 \(-a\) (即 -1)?因为这里的指针移动是有方向的(顺时针、逆时针),所以你肯定会说,从 0 反方向移动一个刻度,即为 \(-a\) 。没错,不过我们暂时不这样考虑。我们先来想这样一件事,若有一个值属于 0 ~9 能代表 \(-a\) ,那么它与 a 的加和是为 0 的(注:加和为 0 在这里的体现是 a 再次移动某次后,指向 0 刻度),现在我们观察这个表盘,a 已经移动了一个刻度,如果想让它移动到 0 刻度,有两种方法,一是之前说过的反向移动一个刻度。另一种就是继续向前移动 9 个刻度,回到 0 刻度。两种方法都可以回到 0 ,不过我们不考虑第一种,因为它不方便表示。
示意图:
现在考虑如何表达第二种移动方法,很显然,我们可以将它表述为:\(a + (表盘总共可移动次数 - a) = 0\)(刻度) ;即 \(a + (D-a) = 0(刻度)\) ;可以看到,我特意强调了这里的 0 是刻度,如果你将 \(D = 10\) 带入上式,你会得到一个结果 10,但这却和上式矛盾。原因是上式计算的是最后指针指向的刻度,而不是移动的次数。如果将上式做一下化简,就会得到式:\(D = 0(刻度)\) 。可能看起来有些奇怪,再对其变型:\(0 + D = 0(刻度)\) ;该式含义为从 0 刻度移动 D 次(10次),将再次回到 0 刻度。虽然这种说法很合理,但是计算机可没有给我们提供关于刻度的计算,\(0 + D\) 从数值上就是等于 D,即为 10。所以为了让数值计算上满足我们的刻度计算的结果,我们可以使用 取模 运算来模拟这种在表盘上的刻度移动运算:\[[a + (D - a)] \% D = (D) \% D = 0\] ;
注意到这里的 \(D-a\) 数值上等于 10 -1 = 9,带入上式后:\(a + (-a) = [a + (D-a)]\%D = (a + 9)\%D = (10)\%10 = 0\) ; 即规定了这种运算后,我们就可以用 9 来代表\(-1\),用 \((D-a)\%D\) 来代表 \(-a\)。然后我们也可以惊喜的发现,由于我们使用一个正数表示某个数的负数,在我们规定的运算规则下,两个数的减法运算似乎被我们变成了两个数的加法运算OVO!。
注意,一旦我们使用 9 来代表 \(-1\) ,那么 9 便只能作为 \(-1\) 参与运算,我们无法再取得 9 ,因为他已经有其他用途了。所以,原取值范围将缩一半,0~9 的范围可能就变成了 0~4 的取值范围,剩下的数 5~9 用来表示 0~4 对应的负数 。 接下来计算在 0~9 范围内的每个数的负数表示:
对于这个表盘来说,当 a 取 0 时,0 无正负(补码 0 无符号),所以 0 仍代表 0;
当 a 取 1 时,\(-a = (D-a)\%D = (10-1)\%10 = 9;即 -1 = 9\) ;
当 a 取 2 时,\(-a = (D-a)\%D = (10-2)\%10 = 8;即 -2 = 8\) ;
当 a 取 3 时,\(-a = (D-a)\%D = (10-3)\%10 = 7;即 -3 = 7\) ;
当 a 取 4 时,\(-a = (D-a)\%D = (10-4)\%10 = 6;即 -4 = 6\) ;
当 a 取 5 时 ,\(-a = (D-a)\%D = (10-5)\%10 = 5;即 -5 = 5\) ; (5 使用 5 本身来表示自己的负数?先跳过)