一篇文章精通计算机中的原码、反码和补码

大家好,我是贠学文,点击右上方“关注”,每天为您分享java程序员需要掌握的知识点干货。

凌晨四点的北京

在学习java基础的时候,学到int类型的时候,我们都知道int类型是由32位二进制表示的,那么它的最小值,应该为-2^31-1,但是实际上,它的最小值却为-2^31,大家有没有想过,为什么会这样呢?这就与我们今天提到的计算机中二进制的原码、反码和补码有关。

我们都知道,在计算机内部,一切的数据都是以二进制的形式来存储的,而我们在有关二进制的术语中,经常会提到原码、反码、补码这三个概念,那这三种编码分别指的是什么呢,它存在的意义又是什么呢,今天就来详细讲解一下。

原码:

这个理解起来很简单,就是二进制最原始的编码,由0和1组成,最高位为符号位,其它位为数值位,其中符号位0代码正数,1代表负数,比如用一个4位的二进制码表示 6:0110,-2:1010

补码:

在二进制的运算中,计算加法是非常简单的,但是如果计算减法,计算机的硬件电路设计会非常复杂(至于为什么会复杂,这是硬件的东西,我们不考虑),所以我们就想,是否可以让加法来取代减法运算,从而简化计算机的硬件电路设计呢?

我们都知道,在运算规则中,减去一个数,等于加上这个数的相反数,那么利用这个思路,我们就可以实现用加法来取代减法运算,从而简化计算机的硬件电路设计。但是在实际的操作过程中,发现用这种实现方式计算出来的结果是不正确的,至于为什么不正确,后面会说到。所以为了使结算结果正确,就为每一个二进制数,都设计出了对应的补码,然后在运算时和存储时,都是基于补码来运算和存储,这样计算出来的结果就是正确的。

反码:

在计算二进制原码对应的补码时,衍生出的一种处于中间状态的编码。它的存在,只是用来计算补码的。

对于正数来讲,反码和补码都等同于原码本身。

对于负数来讲,

反码:符号位不变,数值位的每一位做按位取反

补码:对反码的值+1

举例:

一个4位的二进制数表示-6,那么它的原码:1110,反码:1001,补码:1010

-1:原码:1001,反码:1110,补码:1111

假如对于两个4位的二进制数,我们要计算5-2,那么我们可以转换为5+ (-2),那我们来计算一下他的结果

0101

1010

--------

1111

我们看到,计算出来的结果竟然是-7,这明显是错误的。至于为什么会出现这样的结果,原因也很简单,我们在把2的二进制转换为-2的二进制的时候,它的数值位没有变,只是把符号位由0变成了1,那我们在数值位做运算的时候,实际上就是做的5+2,然后符号位在做运算的时候,计算的结果负数,那么计算后的符号位与计算后的数值位结合起来,结果自然就是-7。这也就解答了前面说的为什么用加上一个数的相反数这种运算来取代减法时,计算结果是不正确的原因。

那么我们怎样才能计算出正确结果呢?

按照正常的情况下:A - B = C ,其中A和B均为正数且A>B,那么C一定是大于0并且小于A的。但是按照上面的原理来讲,A - B =A + (-B)= -(A + B),这样计算出来的结果,符号位一定是负数,并且数值位的结果一定大于A,这显示是不对的,我们预期的结果是,符号位为正数,并且数值位结果小于A,那怎样才能达到这个效果呢?这时我们就想到了补码的算法。

我们可以参考下时钟的原理,时钟最多只能表示12个数,所以时钟每次过12点以后,就相当于发生了溢出,溢出后,就会再次从1点开始计算。补码的原理:假如对于一个4位的二进制数来讲,它可以表示0到7这8个数,那我们把这8个数想象成一个时钟,我们做5-2,其实就相当于把时钟回拨了两个小时,但是我们如果不回拨两个小时,而是向后拨6个小时,可以达到同样的效果,只不过这时会发生溢出,溢出后,重新从0开始计算。所以这时6就相当于-2的补码。我们可以把5+(-2)转换为5+6.

但是同时我们也要考虑下正数的计算,因为正数的计算是不存在问题的,所以正数可以不用计算补码,直接做运算。我们也可以理解为正数的反码和补码都和原码保持一致。那么这个时候问题就来了,我们上面说了-2的补码为6,那么我们如果现在知道了一个数的补码为6,怎么知道它的原码是多少呢?它的原码有可能是-2,也有可能是6。这时候就造成了原码和补码不是一对一的对应关系,所以我们无法通过补码来计算原码。所以为了解决这个问题,规定负数的反码和补码,一定还是负数。即-2的补码为-6,这样就很完美的解决了这个问题。

那么我们再来算下5+(-6)的结果

0101

1110

--------

10011

这时候我们发现发生了高位溢出,那我们再把最高位舍弃,即0011,结果为3,这时候的结果就是正确的了。

那么现在的问题来了,我们如何计算任意一个负数对于的补码呢?

根据我们上面说的,负数的反码和补码,一定还是负数,所以我们保持符号位不变,那么数值位部分的计算,我们还是参考时钟的原理,实际上就是往拨了一圈(一圈即二进制能表示的正数的个数),然后在减掉我们要计算的值(即原码的数值位),那么对于二进制来讲,就是用二进制能表示的正数的个数-原码的数值位。对于一个n为的二进制数,它的正数的取值范围为 0至2^(n-1)-1,所以它能表示的正数的个数为(2^(n-1)-1)+1个。

那么补码数值位=(2^(n-1)-1)+1 - 原码数值位 = (2^(n-1)-1) - 原码数值位 +1 = 原码数值位的反码 + 1

那上面的公式中,为什么(2^(n-1)-1) - 原码数值位 = 原码数值位的反码 呢?

因为2^(n-1)-1,是表示的二进制的最大值,即除符号位以外,数值位的所有位,都为1,所以2^(n-1)-1=2^0+2^1+2^2+......+2^n-2,那么(2^(n-1)-1) - 原码数值位=2^0+2^1+2^2+......+2^n-2 - 原码。那这个操作的最终结果,就相当去把原码的数值位按位取反了,即原码数值位的反码了。

那综合上面的结果,对于一个负数的补码的运算规则,就是保持符号位不变,数值位做按位取反,得到反码,然后反码的基础上在+1。

我们还是拿一个4位的二进制来举例:4位二进制能表示的负数的范围为:1111至1000,即-7到-0,而位二进制能表示的负数的范围为:0000至0111,就0至7,这时我们发现,出现了两个0,一个是1000,一个是0000,这显然是不行的。

通过上面我们知道,负数的反码是数值位不变,然后数值位按位取反,那么负数的反码可以表示的范围也是1000至1111,而补码是在反码的基础上+1,所以补码可以表示的范围为:1001至1111+1,就-1到-8。所以,通过补码,就完美的解决了这个问题。而int类型为什么可以表示-2^31这个问题,也同时就解释清楚了。

往期精彩:

java中的enum第一期:enum存在的意义

java中的enum第二期:enum底层实现原理

String为什么不可变

工厂模式如何消除大量的if else 第一期

Elasticsearch的核心真的是倒排索引吗?

作者介绍:

贠学文,具有多有经验的java开发工程师,业余时间利用头条分享技术知识点与自己对技术的感悟,帮助对自己未来感到迷茫的程序员,在技术上得到提升。结识一些志同道合的朋友,相互促进,共同进步。

展开阅读全文

页面更新:2024-06-09

标签:反码   补码   正数   负数   减法   数值   时钟   符号   个数   原理

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2020-2024 All Rights Reserved. Powered By 71396.com 闽ICP备11008920号-4
闽公网安备35020302034903号

Top