前言
一般来说,进行10进制的运算 14 − 14 = 0 14-14=0 14−14=0,这是小学二年级就能口算出来的,那么计算机内部是如何处理的呢?计算机进行加法运算的时候,将原码进行相加即可得到正确的结果,那么 14 − 14 14-14 14−14直接进行原码的操作会发生什么呢?
14 D = 00001110 B 14D = 00001110B 14D=00001110B − 14 D = 10001110 B -14D = 10001110B −14D=10001110B
我们知道 14 − 14 = 14 + ( − 14 ) 14-14 = 14+(-14) 14−14=14+(−14),计算一下得知
0 0 0 0 1 1 1 0 + 1 0 0 0 1 1 1 0 1 0 0 1 1 1 0 0 \begin{matrix} &0&0&0&0&1&1&1&0\\ +&1&0&0&0&1&1&1&0\\ \hline &1&0&0&1&1&1&0&0\\ \end{matrix} +011000000001111111110000
那么此时的有符号二进制数 10011100 B = − 28 D 10011100B=-28D 10011100B=−28D,计算结果是-28,这显然不符合我们的预期。
老师们通常的教法
老师们通常引导我们用的是生活中时钟的例子
假如现在的时间是10点,并且假如我们的钟表快了2小时,那么我们最容易想到的办法就是往回倒走2小时,我们通常会想到的算式是 10 − 2 = 8 10 - 2 = 8 10−2=8,另外一种笨的办法就是让他走快一些,快多少呢,我们需要拨快的时间是 12 − 2 = 10 12-2=10 12−2=10小时
我们需要将时钟拨快10个小时 10 + 10 = 20 10+10=20 10+10=20,拨快10小时后时间变为了20点,因为时钟一圈的总数是12,所以超过20是没有意义的,那么就要减去12,那么此处为什么要取模运算呢,因为可能超过12的数量可以是很多很多圈,例如超过了2个12小时 ,那么此时就需要减去24小时。
由此我们得出的结论是:
在时钟这个问题上,要想将10点变为8点,用 10 + ( − 2 ) 10+(-2) 10+(−2)和用 10 + ( 12 − 2 ) = 10 + 10 10+(12-2)=10+10 10+(12−2)=10+10的效果是一样的,由此,我们延伸一下,我们感觉在一个有周期性的事物当中,有时候加上一个负数等于加上这个周期减去这个负数的绝对值。
我们来看下下面的推算过程,我们最开始是希望这样的运算
0 0 0 0 1 1 1 0 + 1 0 0 0 1 1 1 0 1 0 0 1 1 1 0 0 \begin{matrix} &0&0&0&0&1&1&1&0\\ +&1&0&0&0&1&1&1&0\\ \hline &1&0&0&1&1&1&0&0\\ \end{matrix} +011000000001111111110000
因为有符号位,我们计算的结果是错误的,所以我们希望转化为两个无符号的数字,通过上面的分析,我们希望转化为下面
0 0 0 0 1 1 1 0 − 0 0 0 0 1 1 1 0 0 0 0 0 0 0 0 0 \begin{matrix} &0&0&0&0&1&1&1&0\\ -&0&0&0&0&1&1&1&0\\ \hline &0&0&0&0&0&0&0&0\\ \end{matrix} −000000000000110110110000
此时去掉了符号位,如果直接相减,得到的结果0就是我们想要的结果,但是此时需要计算机实现符号位的自动处理,这在实现上会很复杂,但是通过上面的分析,我们知道任何数字加上它的模之后都还等于它自身的值,于是我们可以利用加法的交换率进行推导,当然前提是字长为8的计算机数 − 14 D = − 14 D + 256 D = − 00001110 B + 10000000 B = + 10000000 B − 00001110 B -14D=-14D+256D=-00001110B+10000000B=+10000000B-00001110B −14D=−14D+256D=−00001110B+10000000B=+10000000B−00001110B
于是我们可以得到下面的式子:
0 0 0 0 1 1 1 0 + 1 0 0 0 0 0 0 0 0 − 0 0 0 0 1 1 1 0 \begin{matrix} &&&&&&&&&&&0&0&0&0&1&1&1&0\\ +&1&0&0&0&0&0&0&0&0&-&0&0&0&0&1&1&1&0\\ \hline \end{matrix} +100000000−0000000011111100
因为 256 = 1 + 255 256=1+255 256=1+255,所以可以继续演化为:
0 0 0 0 1 1 1 0 + ( 0 0 0 0 0 0 0 1 + 0 1 1 1 1 1 1 1 ) − 1 0 0 0 1 1 1 0 \begin{matrix} &&&&&&&&&&&&&&&&&&&&&0&0&0&0&1&1&1&0\\ +&(&0&0&0&0&0&0&0&1&+&0&1&1&1&1&1&1&1&)&-&1&0&0&0&1&1&1&0\\ \hline \end{matrix} +(00000001+01111111)−0100000011111100
此时利用加法的运算优先级相同的原则,继续演化为
0 0 0 0 1 1 1 0 + 0 0 0 0 0 0 0 1 + ( 0 1 1 1 1 1 1 1 − 1 0 0 0 1 1 1 0 ) \begin{matrix} &&&&&&&&&&&&&&&&&&&&&0&0&0&0&1&1&1&0\\ +&0&0&0&0&0&0&0&1&+&(&0&1&1&1&1&1&1&1&&-&1&0&0&0&1&1&1&0&)\\ \hline \end{matrix} +00000001+(01111111−0100000011111100)
我们一系列的推导,其实就是下面的式子
100000000 − 00001110 = ( 00000001 + 01111111 ) − 10001110 = 00000001 + ( 01111111 − 10001110 ) = 11110010 100000000-00001110 = (00000001+01111111)-10001110 = 00000001+(01111111-10001110) = 11110010 100000000−00001110=(00000001+01111111)−10001110=00000001+(01111111−10001110)=11110010
如果我们先计算括号中的式子就会发现奇迹,得到的数字就是-14的按位取反,即每一个比特上面的数字除了符号位的全部数值位取反,那么最后再加上最前面的1,我们可以得到如下的式子
0 0 0 0 1 1 1 0 + 1 1 1 1 0 0 1 0 1 0 0 0 0 0 0 0 \begin{matrix} &0&0&0&0&1&1&1&0\\ +&1&1&1&1&0&0&1&0\\ \hline &1&0&0&0&0&0&0&0\\ \end{matrix} +011010010010100100110000
此时我们进行取模运算 100000000 / 256 = 0 100000000/256=0 100000000/256=0,其实不进行取模运算的话,对于字长为8的有符号二进制数字来说,一个字节的长度只有8位,最高位的1已经被舍弃了,式子的结果还是等于0,于是我们实现了 14 + ( − 14 ) 14+(-14) 14+(−14)的机器运算的正确结果,此时回忆一下这个式子我们做了什么
- 把一个负数a等价换算为 a + 模 a+模 a+模,(也有说用模减去这个数的相反数的,即: a = m o d − ∣ a ∣ a=mod-|a| a=mod−∣a∣),即 a + 100000000 a+100000000 a+100000000,例如
− 14 = − 14 + 256 = 256 − 14 -14=-14+256=256-14 −14=−14+256=256−14
- 把模换算成1+2n-1,例如
256 = 1 + 255 256=1+255 256=1+255,即100000000 等=00000001+01111111
- 优先计算 01111111 − a 01111111-a 01111111−a,此时得到的结果是a的按位取反的值,即反码
- 加上最前面的数字00000001,即再加上1
于是我们把这一系列的过程得到的机器码叫做补码,并且知道了,补码的机器码等于按位取反之后,再加上数字1,这一系列的操作可以用下面的这幅图来表示
这其中,我们经历了数字的拆分,运算的优先级变化,就像小学时候的快速运算法( 9 + 6 + 4 = ? 9+6+4=? 9+6+4=?,我们就会先运算 6 + 4 6+4 6+4),至此我们明白了为什么负数的反码等于按位取反之后再加上1了。
我自己更能理解的方法
其实我们完全可以忘掉上面的时钟,我们想象一下每天有256个小时,那么我们的钟表是这样的,我认为这样更好理解一些。假如现在是192点,但是实际上钟表快了64个小时,为了将时钟调整准确,接下来看下面的 这个图
192 − 64 192-64 192−64
192 + 256 − 64 192+256-64 192+256−64
我们知道这两个式子的计算结果都是可以把时钟调整到我们想要的结果,也就是结果是相等的,于是就按照第二个式子进行做吧
192 + 256 − 64 = 192 + ( 256 − 64 ) = 192 + ( 1 + 255 − 64 ) = 192 + ( 1 + ( 255 − 64 ) ) 192+256-64 = 192+(256-64) =192+(1+255-64) = 192+(1+(255-64)) 192+256−64=192+(256−64)=192+(1+255−64)=192+(1+(255−64))
我们换成二进制的计其数的写法,应该是这样的
1 1 0 0 0 0 0 0 + 1 0 0 0 0 0 0 0 0 − 0 1 0 0 0 0 0 0 \begin{matrix} &&&&&&&&&&&1&1&0&0&0&0&0&0\\ +&1&0&0&0&0&0&0&0&0&-&0&1&0&0&0&0&0&0\\ \hline \end{matrix} +100000000−1011000000000000
⬇️1 1 0 0 0 0 0 0 + 0 0 0 0 0 0 0 1 + 0 1 1 1 1 1 1 1 − 0 1 0 0 0 0 0 0 \begin{matrix} &&&&&&&&&&&&&&&&&&&1&1&0&0&0&0&0&0\\ +&0&0&0&0&0&0&0&1&+&0&1&1&1&1&1&1&1&-&0&1&0&0&0&0&0&0\\ \hline \end{matrix} +00000001+01111111−1011000000000000
⬇️1 1 0 0 0 0 0 0 + 0 0 0 0 0 0 0 1 + ( 0 1 1 1 1 1 1 1 − 0 1 0 0 0 0 0 0 ) \begin{matrix} &&&&&&&&&&&&&&&&&&&&1&1&0&0&0&0&0&0\\ +&0&0&0&0&0&0&0&1&+&(&0&1&1&1&1&1&1&1&-&0&1&0&0&0&0&0&0&)\\ \hline \end{matrix} +00000001+(01111111−1011000000000000)
⬇️1 1 0 0 0 0 0 0 + 0 0 0 0 0 0 0 1 + 0 0 1 1 1 1 1 1 \begin{matrix} &&&&&&&&&&1&1&0&0&0&0&0&0\\ +&0&0&0&0&0&0&0&1&+&0&0&1&1&1&1&1&1\\ \hline \end{matrix} +00000001+1010010101010101
⬇️ 1 1 0 0 0 0 0 0 + 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 \begin{matrix} &1&1&0&0&0&0&0&0\\ +&0&1&0&0&0&0&0&0\\ \hline &1&0&0&0&0&0&0&0\\ \end{matrix} +101110000000000000000000
10000000B=128,此时,时钟又回到了128点,我们的结果正确了。
结论
- 对于正数来说
原码=反码=补码
这是我们一眼就能看懂的有符号机器数
- 对于负数来说
反 码 = 原 码 的 符 号 位 不 变 , 数 值 位 按 位 取 反 反码 = 原码的符号位不变,数值位按位取反 反码=原码的符号位不变,数值位按位取反
补 码 = 原 码 的 符 号 位 不 变 , 数 值 位 按 位 取 反 , 然 后 + 1 补码 = 原码的符号位不变,数值位按位取反,然后+1 补码=原码的符号位不变,数值位按位取反,然后+1
补 码 = 负 数 + 模 = 模 − 负 数 的 绝 对 值 , 补码 = 负数+模=模-负数的绝对值, 补码=负数+模=模−负数的绝对值,这一点很重要,因为原码的符号位不变,数值位按位取反,然后+1这个结论就是通过补码=模-负数的绝对值演算过来的
为什么补码的补码是原码
前面我们得到了结论
补码 = 原码的符号位不变,数值位按位取反,然后+1
那么我们现在把补码再求一下补码,就是求补码的补码,我们以-14为例
- -14的原码[-14D]原=10001110B
- 先求出-14的反码 [-14]反 = 11110001
- 再求出补码 [-14]补 = 11110010
- 此时我们求出补码的反码, [[-14]补 ]反 =10001101
- 再求出补码的补码[[-14]补 ]补 =10001110
可以看出10001110和[-14]相等,即[[-14]补 ]补=[-14D]原,也就是原码的补码的补码=原码,这是一种巧合吗,如何证明,其实很简单
我们不用去管什么取反加1这些概念,而是从补码的由来说起
假设一个负数是a,那么它的[a]补=a+模=模-(-a),那么[[a]补]补=模-(模-(-a))=模-模+a=a,于是就等于它自身了