如有侵权,请作者尽快联系我,我会第一时间删除本文。
一直没搞懂为什么需要反码和补码,在网上查资料的时候看到了下面这篇。
关于反码和补码的优势讲得很好懂,不过文章的后半段关于余和模的描述,看完之后总觉得没看明白。评论中提到了下面的文章
signed-binary-numbers
(简单翻译放在下面,原版请参考上面的链接。)
两篇文章对照看完后,把自己的想法写在下面。
1.补码的取得方式
第一篇文章中是,补码的取得方式是通过(反码+ 1)。
而在第二篇文章中是 (模 -原码),结果虽然一样,但是明显前一种更符合人脑的理解习惯,但是后一种却是在真实电路中的处理方式。
补码 = 反码 + 1 ,这也很好的解释了为什么在二进制中对于一段取值范围负数会比正数多一。比如在java中,Integer类型的取值范围是 -2147483648 到 2147483647 也就是 -231 到 231-1。因为0是在正数的范围内表示(0的二进制代码的最高有效位MSB是0)。
2.反码的符号位
第一篇文章中,负数的反码是原码保留最高有效位msb不变,其他位按位取反。第二篇文章中是将这个负数对应的正数的原码全部取反,包括msb。
其实是一个意思。负数的原码中的符号位 和 负数对应正数取反之后的符号位都是 1。
3.正0和负0
不论哪种计算方式,都是通过溢出一位,然后忽略溢出位的方式,得到相同的一个二进制代码。
在数学中,正数(包括零)表现为没有符号的数字。我们不在他们前面放上一个正号(+)来显示他们是正数。
但是当处理负数时,我们在数字前面加上一个负号(-)来表示这个数字是负数,并且和无符号的正数相区别。带符号的二进制数也是同样的。
然而,在数字电路中,并没有规定一个加号或者减号来标识一个数字。因为在数字系统中用二进制数字(0 和 1)来进行操作,在微电子学中被使用时,这些0和1 被叫做一个比特(bit,来自BInary digiT的缩写)。分成特定范围大小的数字被叫做一些通用的名字,比如字节(byte) 或者字(word)。
我们之前看到过,一个8bit的数字的取值范围是从 0(00000000) 到 255(11111111),每一个8bit字节都有28,256个不同的组合。
所以,比如有一个无符号位的二进制数01001101 等于十进制的77。但是数字系统和计算机必须和处理正数一样很好的处理负数才行。
在数学上,数字通常由符号和值组成。符号表明数字是正或者是负,而值表示了数字的大小。比如23,+156 或者-274。现代数字是这种被叫做”符号大小表示法”(sign-magnitude)的形式,最左边的位数指示了符号,剩下的位数代表着值。
sign-magnitude的概念是表示正数和负数最简单方法,也是最常用的方法的方法之一。因此负数可以通过简单的改变相对应的正数的符号来得到,也就是每一个正数或者无符号的数都有一个被标记的相反数,比如+2 和-2 , +10 和-10等等。
但是如果我们有的只是一串0和1,我们应该怎么表示有符号的二进制数呢。我们知道二进制只有两个数,0 或者1,为了我们的方便一个符号应该有两个值,+ 或者 -。
那么我们可以使用一个单独的bit来定义一个有符号的二进制数的符号为正或者负,然后添加这个符号,来表示一个正的二进制数 或者 负的二进制数。
对带符号的二进制来说,最高有效位(the most significant bit ,MSB)被用作符号位,如果为0,意味着这个数字是正数。如果为1,意味着这个数字为负数。数字中剩下的bit用来表示这个二进制数的大小。
我们可以看到 sign-magnitude 通过把 一共有n位的bit分成两个部分来存储正数和负数 : 1位给符号,n-1位给单纯的二进制数值。比如十进制数 53 可以被如下表示成8 bit的带符号的二进制数
缺点是,与之前我们有一个全范围的n bit的无符号二进制数不同,现在我们只有一个n-1bit的带符号的二进制数,位数的范围减少了,
所以,比如我们有4个bit来表示一个带符号的二进制数,(一个bit给符号位,3个bit给数值位),那么我们在sign-magnitude概念中能表示的实际数字的范围是:
相比之前,4bit的无符号二进制数的范围是从0到15,或者在16进制中是从0到F,我们现在有一个减少的范围是从-7到+7.所以无符号二进制数没有一个单独的符号位,因此可以有一个更大的取值范围,相比于有符号二级制数,最高有效位(MSB)只是一个额外的bit位。
符号大小表示法的另一个缺点是,我们有+0 和 -0。两个都是有效的,但是哪一个才是正确的呢。
有符号二进制数(原码) 例子1
把下面的十进制数转换为带符号的二进制数,
注意,对于4bit,6bit,8bit,12bit,或者32bit有符号二进制数来说,所有的位数必须有值,于是 0 被用来填充在最左边的位数和第一个或者说是最高位的 1 之间的空间。
一个二进制数的符号大小表示形式是易于理解和使用的,就像我们一直在数学中使用的十进制。如果这个二进制数是负数我们就在前面加一个 1,如果是正数就加一个 0 。
但是,使用符号大小表示法会导致两个不同的表示形式有相同的值。比如 +0 和 -0在4bit二进制数中分别是 0000 和 1000。所以我们看到使用这种方法对于0 会存在两种表示,正0 和负0,而这会导致计算机和数字化系统更复杂。
二进制的反码(One’s Complement)
One’s Complement 也被叫做 1’s Complement,是我们在符号二进制系统中可以用来表示负的二进制数的另一种方法。在反码中,正数和之前的符号大小表示法一样保持不变。
而负数,通过取1的补(倒置,取反)来表示。正数总是用0 开始,反码总是用1开始来指示一个负数。
负数的反码是他的正数副本的补,所以为了得到反码,我们需要按顺序改变每一个bit。因为1的反码是0,反之亦然,那么100101002的反码就是011010112,把所有的1变成0,把所有的0变成1。
构建数字化算法或者 逻辑译码器电路时,找到一个带符号二进制数的最简单的方法是使用 逆变器( Inverters)。逆变器是一个天然的补集生成器,可以用来并行找到任意二进制数的反码。
使用逆变器的反码
我们可以看到找到二进制数N的反码是非常简单的,我们只需要简单的把1变成0,把0变成1,就可以得到一个 -N。
就像之前的原码,反码也有Nbit个位数来表示范围在 -2(n-1) 到+2(n-1)的数字。比如,在反码中4bit可以被用来表示在-7 到+7 范围内的十进制数,并且和之前一样有两个0。
反码的加法和减法
反码的一个主要优点是两个二进制数的加法和减法。在数学上,减法有多种不同的方式,比如 A - B,也可以写成 A + (-B) , -B + A等等。因此,两个二进制数的减法可以简单的通过使用加法来实现。
二进制加法遵循和普通加法一样的规则,除了在二进制中只有两个数,并且最大的是1(最大的十进制数是9),因此对于二进制来说可能的组合如下所示
当两个数字都是正数的时候,A +B的和,他们可以通过直接和(direct sum,包括数字和符号)被加在一起,因为当单个bit被加在一起时,“0 + 0”, “0 + 1”, 或者 “1 + 0” 会返回“0” 或者 “1”。让我们看一个例子。
两个二进制数的减法
一个8位数字化系统需要用反码来相减下面两个数字 115 和 27。那么在十进制中,就是 115 - 27 = 88。
首先我们需要把这两个十进制数转换为二进制,并且通过在前面增加0来他们有相同的位数,来构造一个8bit的数。因此:
现在我们要找到第二个二进制数的反码,而第一个数不变。所以通过把所有的1变为0,把0变为1,00011011的反码是11100100。
把第一个数字和第二个数字的反码相加得到
由于这个数字化系统的基于8bit工作的,所以只有前8位被用来提供结果,并且我们简单的忽略最后一位(第9bit),这个bit被称为溢出位(overflow bit)。当最高符号位(最左位)这一列发现推进时,溢出就会发生。溢出位,或者进位位可以被完全忽略,或者在计算中被传递到下一个数字段。溢出表明答案为正。如果没有溢出表明答案为负。
上面计算的8bit结果是 01010111 (溢出的 1 忽略),并且从反码转换回真正的值,我们现在需要给反码加上1,因此
所以115 ( 011100112 ) 减去 27( 000110112 ) ,使用反码得到的结果是 010110002 或者10进制的 (64 + 16 + 8) = 8810 。
我们可以看到,无符号或者有符号的二进制数都可以通过反码和加法来实现减法。诸如TTL 74LS83 或者74LS283的加法器可以被用来加减两个4bit二进制数或者串联在一起来构造一个包括进位的8bit加法器。
带符号二进制数的补码
补码(Two’s Complement 或者 2’s Complement ),就像它的名字,就像前面的原码,反码一样是另一种方法,我们可以可以用来在一个数字化系统中表示一个负的二进制数。在补码中,正数和无符号二进制数完全一样,而一个负数,被表示为一个二进制数,这个数加上相对应的正数为0。
在补码中,负数是在两个数的差中它对应正数的补码。 A – B = A + ( B的补码 ),使用和以前一样的相同流程,那么补码就是反码加减 1 。
补码相对前面的反码主要的优点在于,没有两个0的问题,还有生成补码更容易。因此,当数字用补码表示时,数学运算更容易执行。
让我们来看上面的两个数字8bit数 115 和 27 的补码减法,并且我们记得在上面他们对应的二进制数是
我们的数字是8bit长,那么有28个有效值来表达我们的数,并且在二进制中这等于1000000002 or 25610。那么2710的补码是
第二个负数的补,意味着减法变成了更容易的两个数的加法,所以和是 115 + (27的补集),也就是
和之前一样,第9为溢出位被无视,我们只对前8位感兴趣,所以结果是 010110002 或者 (64 + 16 + 8) = 8810 ,就和之前一样。
带符号的二进制数摘要
我们已经看到负数可以通过把最高有效位(the most signficant bit MSB)作为符号位来表示。如果一个n位的二进制数是带符号的,那么它最左边的一位用来表示符号,剩下的n-1位用来表示数字。
比如,在4bit数中,只剩下3位来保存实际的值。而如果是无符号的二进制数,那么所有的位数都可以用来表示数字。
带符号的二进制数的表达方式通常被称作 符号大小表示法(sign-magnitude),如果符号位是0,这个数字就是正数,如果是1,那么这个数字就是负数。当处理二进制算术运算时,使用负数的补是更方便的。
补 是一个表示负数的可选方案。这个备选的编码系统通过简单的加法来考虑负数的减法。
由于正的sm 数总是以0开始,他的补因此会以1开始来表达一个负数,就像下面的表格
4bit带符号的二进制数比较
10进制 | 原码 | 反码 | 补码 |
---|---|---|---|
+7 | 0111 | 0111 | 0111 |
+6 | 0110 | 0110 | 0110 |
+5 | 0101 | 0101 | 0101 |
+4 | 0100 | 0100 | 0100 |
+3 | 0011 | 0011 | 0011 |
+2 | 0010 | 0010 | 0010 |
+1 | 0001 | 0001 | 0001 |
+0 | 0000 | 0000 | 0000 |
-0 | 1000 | 1111 | - |
-1 | 1001 | 1110 | 1111 |
-2 | 1010 | 1101 | 1110 |
- 3 | 1011 | 1100 | 1101 |
-4 | 1100 | 1011 | 1100 |
-5 | 1101 | 1010 | 1011 |
-6 | 1110 | 1001 | 1010 |
-7 | 1111 | 1000 | 1001 |
二进制数带符号的补,可以使用反码或者补码,反码和补码很重要是因为他们允许负数的表达。
补码通常被用在计算机中来处理负数,唯一的缺点是如果我们想用带符号的二进制数的形式来表达负数,我们必须放弃一些以前的取值范围。