一文搞懂补码 (2)

a 无法再取更大的值,因为 6 已经被作为 \(-4\) 看待,我们的正数范围只能取到 5 。分配完正负角色后的表盘如下图所示(黑色 0 非正非负,红色为正数,蓝色为负数,绿色可正可负):

一文搞懂补码

初始时我们可以在表盘上取到的值是 0~9,当使用一部分的值作为负数使用后,原来的值的绝对值范围将缩小,这里在分配完正负后,其范围为 \([0,5] \cup [-1,-4]\)\([0,4] \cup [-1,-5]\)。为表述方便,我们之后称这些用来表示另一个正数的负数的数 为对应正数的 补数

现在我们来简单验证一下,在规定模运算下,减法运算到加法运算转换的正确性:

\(1-1=(1+9)\%10 = 0\);

\(2-2 = (2+8)\%10 = 0\)

\(3-3=(3+7)\%10 = 0\);

\(...\)

\(3-2=(3+8)\%10=1\);

\(4-1=(4+9)\%10=3\)

\(...\)

工作的很好:)。

二进制的情形

现在来考虑计算机中二进制数的情形,并考虑在此情形下如何将减法等效为加法。

现设有 8 位二进制数,它的取值个数有 \(2^8\) 个,所以取值范围为:即 \(0 ~ 255\) 。同我们之前讨论的一样,如果我们将这 256 个数均匀分布到一个表盘上,那么同样可以得到一个满是刻度的表盘:

一文搞懂补码

这里 D 取 \(2^8=256\),根据之前的讨论,我们可以很容易得出在这个范围上,可以使用 128 ~ 255 这 128 个数表示 0 ~ 127 对应的 128 个负数,并通过取模运算从而实现减法变为加法运算。你可能注意到,之前在我们的 0~9 表盘上,5 的位置是绿色的,我也提过,5 是可正可负的,因为它的正负并没有严格要求,因为我们 透明 的处理加法和减法,无论是加 5,还是减 5,作为加法取模运算后,结果都是正确的。\(5-5\)\(5+5\) 最后都会变成 \((5+5)\%D\) 来计算。而现在我们的例子,128 被认为是代表一个负数(即一个正数的补数),至于为什么,稍后会解释。

接下来让我们考虑一下当这些数为二进制的形式时的情况。

通过之前的结论,我们可以如此举例: 45 的负数为 \(D-45 = 256 - 45 = 211\) ,将他们分别用二进制形式表示:

十进制 二进制
45   0010 1101  
211   1101 0011  

因为 \(45 - 45 = 0 = (45+211)\%D\) ;使用二进制形式表示(下面过程没有使用模运算):

0010 1101 - 0010 1101 = 0000 0000 = 0010 1101 + 1101 0011 = 0010 1101 + 1101 0011 ------------- 10000 0000

注意这个结果,它有 9 位,显然已经超出 8 为二进制数,所以得到溢出后的结果:0000 0000。没错,无需任何附加运算,只需要简单的将一个数与它的补数相加,CPU 将得到两个数的差值。这就是计算机中,实现负数的方式。而一旦有了负数的正数表示形式,那么减法自然可以转化为加法进行计算。

二进制补数计算

那么紧接着的另一个问题也就随之而来,怎么知道每个数的补数呢?我们之前是通过这种方式计算的:

\(-a = (D-a)\%D\) ;对于给定范围内的每个数都带入这个公式计算一次吗?从算法上来讲,是的。但是从实现上来讲,计算机有更高效的方法来实现这个操作。找到一个二进制数的补数,等价于找到一个数与原数加和后,最高位发生溢出,其余位为 0。那我们自然想到,一个二进制数,如果全为 1 的话,再将其加 1,那么最高位就自然溢出,并清零了所有的位。那么怎么获得全 1 的二进制数呢?考虑二进制数:

0010 1101;想要该数与一个数加和为全 1 ,那么该数必然将原数所有的 0 都变为 1,而原来的1 保持不变,所以我们得到一个二进制数:1101 0010;仔细一看,没错,原数的反码。此时将两数加和得到的全 1 二进制数加 1,即得到 8 位全 0 的结果。上述过程就是计算机求解一个二进制数__补码__ 的过程,而计算机使用补码来表示一个二进制正数的负数形式。

表示范围

接下来我们讨论 0~255 这个范围中,哪些作为正数,哪些作为补数存在。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpfjps.html