之前写过一篇文章,总结计算机中全加器的原理,使用全加器可以方便的对两数进行相加运算。
但是如何处理减法呢?
这里就要使用补码了。那何为补码呢,之前学习的时候一直没有仔细思考,这个补字的含义。其实这里的补就是另一种状态的对立。想象一下如果一面墙破了一个洞,我们要把它补起来。那么这里的补,其实就是破了洞的墙和缺块的地方共同组成了一面完成的墙。
在日常生活中,我们使用十进制的减法时,常常遇到要借位进行相减的时侯,如253减去176。此时个位和百位上的数字不够减,就需要借位了。有时为了快速的计算,我们常常会这样处理,即253 + 1000 - 176- 1000, 然后再将1000 拆解成999 + 1 ,于是原式子就变成了 253+ 999 - 176+ 1 - 1000,这样使用999 - 176就不会发生借位了,各位上的减法都是10以内的。同时,最后的加 1 和减去1000都比较容易计算。这样一个减法通过变成两个加法和两个减法来简化计算的难度了。
其实在 999 - 176= 823这个等式中,我们称823是176对999的补数。
在计算机中处理二进制的时候也借鉴了这样的思想。 因为计算机二进制只有两个数,如果求对1的补数,1就是0,0就是1,相当于取反了。 这样计算起来就比较容易,在逻辑电路上实现也很方便,直接用非门就可以了。
下面用补码的形式计算一下253减去176,过程如下:
过程 | 溢出位 | 第8位 | 第7位 | 第6位 | 第5位 | 第4位 | 第3位 | 第2位 | 第1位 |
---|---|---|---|---|---|---|---|---|---|
253二进制 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | |
176二进制 | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | |
176补码 | 0 | 1 | 0 | 0 | 1 | 1 | 1 | 1 | |
253与176 补码相加 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
253与176 补码相加在加1 | 1 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 0 |
上一行结果减 100000000 | 1 | 0 | 0 | 1 | 1 | 0 | 1 |
最终的结果为 1001101 也即十进制的77。 从上表格看,如果使用补码的话可以方便的将计算过程转化为加法。
上述的分析过程只是使用了补码来简化了减法的运算过程,那么如何用补码来表示负数呢?
以8位的二进制为例,每个二进制位上有两种状态,总共可以表示2的8次方个数,即从0000-0000到1111-1111,也即十进制中的0到255。为了表示负数,将第一个二进制位用来表示符号,剩余的二进制位表示数值大小。由于少了一位表示数值,此时数值的表示范围就减小了一半,变成了 000-0000 到 111-1111了,也即十进制中的0到127 (剩余的一半其实就是以补码形式表示的负数了)。
用补码表示时,规定首位为0时表示正数,首位为1时表示负数。
对于正数,其补码等于本身,无需改变,但是负数需要将对应的正数按位取反,然后加1得到补码。 比如-125,如下表格列出计算过程。
十进制 | -125 |
---|---|
125的二进制 | 0111 1101 |
125的反码表示 | 1000 0010 |
加 1,得到-125的补码 | 1000 0011 |
得到了补码后,如何将补码转化成对应的十进制数呢?
此时,可以先按照正常的二进制转化成十进制的方式来,只是首位的计算要根据符号位取对应的正负。 如-125的补码为 1000 0011,可以进行如下的计算转化成十进制
-1 * 2^7 + 1 * 2^1 + 1 = -128 + 2 + 1 = -125
下表是部分8位二进制补码表示的编码,平时可以用来参考。
二进制 | 十进制 | 转化过程 |
---|---|---|
1000 0000 | -128 | -1 * 2^7 |
1000 0001 | -127 | -1 * 2^7 + 1 |
1000 0010 | -126 | -1 * 2^7 + 1 * 2^1 |
1000 0011 | -125 | -1 * 2^7 + 1 * 2^1 + 1 |
1000 0100 | -124 | -1 * 2^7 + 1 * 2^2 |
… | … | … |
1111 1101 | -3 | 如上,展开为 -127 + 64 + 32 + 16 + 8 + 4 + 1 |
1111 1110 | -2 | 如上,展开为 -127 + 64 + 32 + 16 + 8 + 4 + 2 |
1111 1111 | -1 | -127 + 64 + 32 + 16 + 8 + 4 + 2 + 1 |
0000 0000 | 0 | |
0000 0001 | 1 | 1 * 2^0 |
0000 0010 | 2 | 1 * 2^1 |
0000 0011 | 3 | 1 * 2^1 + 1 |
… | … | … |
0111 1100 | 124 | 1 * 2^6 + 1 * 2^5 + 1 * 2^4 + 1 * 2^3 + 1 * 2^2 |
0111 1101 | 125 | 1 * 2^6 + 1 * 2^5 + 1 * 2^4 + 1 * 2^3 + 1 * 2^2 + 1 * 2^0 |
0111 1110 | 126 | 1 * 2^6 + 1 * 2^5 + 1 * 2^4 + 1 * 2^3 + 1 * 2^2 + 1 * 2^1 |
0111 1111 | 127 | 1 * 2^6 + 1 * 2^5 + 1 * 2^4 + 1 * 2^3 + 1 * 2^2 + 1 * 2^1 + 1 * 2^0 |
1开头全部表示的是负数,0开头表示的是负数。同时,将上述表格中的负数补码先取反在加1可以得到对应的正数。
虽然在平时的编码过程中,好像没有直接使用到补码,但是理解了补码之后或许能更好的理解各种数据长度的表示范围及其他计算机基础。一点点的清理学习路上的障碍,后面的学习之路才会更通畅。