内容来自:https://blog.csdn.net/zhuozuozhi/article/details/80896838
二进制负数的在计算机中采用补码的方式表示。很多人很好奇为什么使用补码,直接使用原码表示多好,看上去更加直观和易于计算。然而事实告诉我们,这种直观只是我们人类的一厢情愿罢了,在计算机看来,补码才是它们最想要的。那么,为什么计算机使用补码更好,又是如何通过补码来计算数值的呢?
我看过网络上很多解释补码的文章,几乎一致的回答就是符号位不变,其他各位逐位求反再加一。在此我想说,这些都不是根本原理。谁都知道这么求,数电第一章就明确写了怎么求,关键是为什么这么算,其中的原理是什么?
本文主要的内容就是深入讲解补码的原理,其中内容有相互引用成分及计算机基础要求,不适合初学者阅读。当然,随便看看无所谓啦。
1.什么是补码
这个没有找到官方定义,只进行个人定义。
个人定义:补码是计算机中用来表示负数,使得负数能够使用加法器参与加法运算的一种码。
加减是计算机中最常用的运算,加法一般使用加法器来实现,减法则使用减法器实现。那有什么办法可以将减法变为加法,这样就可以让系统只实现加法即可,答案就是补码。
理解补码最简单的例子就是时钟。
例1:
假如一个时钟现在显示的是10点钟,如何将它调到6点钟?
解:有两种方法,一是向后拨8个小时,二是向前拨4个小时
在这个例子中,8 和 4 互为补数,也就是说4的补码是8,8的补码是4,而这个时钟的模就是12
注意:可能有人会想,在往后调8个小时虽然也调到了6点,但是他实际上比原来日期多了12小时。是的,的确如此,但是你的时钟有地方存储了这多余的12个小时吗?答案是没有,所以在你调完后,你没有记录这12个小时,换句话说,你把这溢出的12个小时自动舍弃了。当第二个人来查看闹钟时间的时候,他看到的时间就是准的。
例2:
一个数的数值是11,他的模是16,那么他的补码是多少?
解:16-11 = 5,即补码就是5。
2.模
关于模没有找到固定的定义,简单来说模就是一个循环的周期,在例1中,时钟的一个周期就是12,所以模是12。一周的模是7天,一天的模是24小时。模是补码的一个重要概念。
3.使用补码运算
例3
16-13,模为32,使用补码运算该算式。
解 (16 + (32-13))% 32 = 35 % 32 = 3
4.使用补码进行二进制运算
有看过数电基础的都应该记得,第一章就有说明如何求二进制补码是如何运算的。正数的补码即为自己,负数的补码为符号位不变,其余逐位求反再加1。
使用该定义,先通过例子求出数值,再对例子进行详细讲解,为什么可以使用负数的补码来运算。
例4 通过二进制求15-11的值
要想让减法变加法,必须转换算式为 15 + (-11)
15为正数,符号位为0,二进制表示为 01111
-11为负数,符号位为1,负数源码为 11011 ,补码符号位不变,其余逐位求反再 +1,即 10101
所以 15 + (-11) = 01111 + 10101 = 100100 舍弃最高位(与高地址位概念不同)溢出位,即00100,即+4
这是一个最简单的补码算法运算的例子,却有很多不解之处。
1.为什么负数 (-11 )逐位求反再+1就可以代表原来的数?
2.为什么高位舍弃
3.为什么符号位能够参与运算
先看问题1,-11 先不考虑符号位,观察11的二进制表示,11使用二进制表示是 1011,将1011 逐位求反 得到 0100,因为是逐位求反,1011 +0100 = 1111 ,而1111 + 1 = 10000,发现了什么?10000是四位2二进制数的模。为什么10000是4位二进制数的模呢?原理也很简单,4位寄存器最高能表示什么数?即0b1111 = 15 , 15 +1 =16 即溢出低四位归 0,所有模为16。
所以不管几位二进制数,取反后得到的值加原码会刚好的到所有的位都是1的二进制数,再加一就刚好进位得到模。所以取反加一是无论如何都能取到补码的。比如 :(原)101 + (补)((反)010 +1) = 8 ,(原)10 + (补)((反)01 +1)= 4。
所以,负数符号位不变,其余逐位求反 +1 只是算出补码最简单的方法,而不是理论基础。
前面提到过,使用补码代替原码,计算后模掉溢出得到数值就可以得到计算的值了。
再看问题2,为什么高位舍弃,这个问题其实在例1中已经做了说明,因为你没有存储这个高位的空间,用最简单的解释来解释就是,一个4位的寄存器,只能存储数据的低四位,最高第五位没地方放,就像例1中多出来的12小时,没有地方存储,那么就丢弃了,反正结果正确就行。可能有人会问,你说丢弃就丢弃吗,丢了你怎么保证是正确的?
重新再看一次例4,现在考虑符号位,将符号位加入运算。
15 - 11 = 15 + (-11),其中 -11 为 11011(加了一位符号位,所以模为 16 * 2^1) 他的补码为 10101 = 21
所以 15 -11 变为 15 +21 = 36 (01111 + 10101) ,想想看,原先是减一个数,变换后却变成了一个比减数更大的加数了,能不溢出吗。所以要模掉溢出位 36 % 32 = 4 。仔细观察被模数36 = 1 00100 你会发现,在数据高2位以前的所有高位都是32的倍数,所以用32进行模运算就会全部清0,这就是为什么高位可以直接舍弃的原因,因为高位永远会是模的倍数。
最后看问题3,为什么符号位能够参与运算,可能在这前面一直被符号位困扰,为什么参与运算就没有问题,看了这里的解释,我相信你讲不会再被困扰。
符号位对于我对二进制补码的理解产生了很大的阻碍,我不明白为什么符号位可以参与运算,而且算出来还是对的。花了不少时间理解了符号位的含义。我想说,符号位不变,其余逐位求反 +1 这个定义很差。
首先放弃符号位就是计算机表示正数和负数用的这个思维,改成这样理解,就会非常清晰:在有符号的数据类型中,第一位如果是0,那么这个数值是正数原码,第一位如果是1,那么这个数值是负数补码,这个数值是计算所得,而不是计算机设定的如此。
然后放弃负数补码符号位不变,其余逐位求反再+1这个方法,使用如下负数补码的求法。一个二进制数值如果是正数,那么将不变。如果是负数,那么这个二进制数最高位加一位0(如: 1010 变为 01010 ,位数变高一位,模需要乘2,即由16变32),对该数求补码(01010 求补码 10110) 。是不是发现,所谓的符号位不变,其余逐位求反再加一和 在数值前面再加一位0,再所有位求反再+1的效果是一样的,只要求反,最高位0肯定会变成1。一旦知道第一位是0或者1,也就确定了他是正数还是负数了,采用这种理解法,运算符可以加入运算也就理所当然了。(被减数 + 负数补数)% 负数补数的模 = 带符号计算值 这样的计算方式能保证所有的符号位都能参与运算。而符号位不变,其余取反+1的解释显的符号位非常难以理解。
最后再看下重新思考这个运算思路,在运算中,如果遇到加法运算符,则直接运算符,如果遇到减法运算符,查看减数是正数还是负数,如果是负数,从补码反求源码,加入计算,如果遇到正数,则最高位加一位0,取反 +1 求补码,加入运算。
到这里补码的原理就差不多讲清楚了,无非是使用模进行转化,但是我们站在十进制的角度,去看二进制的东西,的确会显得相当费解。
内容有相互嵌套成分,阅读后回顾前面的内容可能会更加深入理解。
还有很多特殊情况没有考虑,只讲解了最核心的内容。
本文纯属个人理解,如有纰漏,请勿拍砖。
————————————————
版权声明:本文为CSDN博主「zhuozuozhi」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zhuozuozhi/article/details/80896838
原文地址:https://www.cnblogs.com/zqk9412/p/11684706.html