• 原码,反码,补码、移码


    参考文章

    参考文章1

    https://blog.csdn.net/zl10086111/article/details/80907428

    作者:张子秋
    出处:http://www.cnblogs.com/zhangziqiu/ 
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

    参考文章2

    https://blog.csdn.net/afsvsv/article/details/94553228

    参考文章3

    https://www.cnblogs.com/Jamesjiang/p/8947252.html

    一、预备知识

    在学习原码, 反码和补码之前, 需要先了解一些概念.

    1、机器数

    一个数在计算机中的二进制表示形式,  叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1.

    比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就是 10000011 。

    那么,这里的 00000011 和 10000011 就是机器数。

    2、真值

    因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位1代表负,其真正数值是 -3,而不是形式值131(10000011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。

    例:

    0000 0001的真值 = +000 0001 = +1

    1000 0001的真值 = –000 0001 = –1

    3、原理

    我们学习之前还得认识二进制,十六进制。会二进制与十进制的相互转化运算。

    进制转换可以参考我的博客:https://www.cnblogs.com/Zzbj/p/10905744.html

    由计算机的硬件决定,任何存储于计算机中的数据,其本质都是以二进制码存储。

    根据冯~诺依曼提出的经典计算机体系结构框架。一台计算机由运算器,控制器,存储器,输入和输出设备组成。其中运算器,只有加法运算器,没有减法运算器。

    所以,计算机中的没法直接做减法的,它的减法是通过加法来实现的。你也许会说,现实世界中所有的减法也可以当成加法的,减去一个数,可以看作加上这个数的相反数。当然没错,但是前提是要先有负数的概念。这就为什么不得不引入一个该死的符号位。

    1. 而且从硬件的角度上看,只有正数加负数才算减法。

    2. 正数与正数相加,负数与负数相加,其实都可以通过加法器直接相加。

    原码,反码,补码的产生过程,就是为了解决,计算机做减法和引入符号位(正号和负号)的问题。

    二、原码, 反码, 补码的基础概念和计算方法

    对于一个数, 计算机要使用一定的编码方式进行存储. 原码, 反码, 补码是机器存储一个具体数字的编码方式.

    1、原码

    原码:是最简单的机器数表示法。用最高位表示符号位,‘1’表示负号,‘0’表示正号。其他位存放该数的二进制的绝对值。

    例如:

    带符号的8位二进制:

    [+1] = 0000 0001 = +1

    [-1] = 1000 0001 = -1

    若以带符号位的四位二进值数为例 

    1. 1010 : 最高位为‘1’,表示这是一个负数,其他三位为‘010’,

    2. 即(0*2^2)+(1*2^1)+(0*2^0)=2(‘^’表示幂运算符)

    3. 所以1010表示十进制数(-2)。

    下图给出部份正负数数的二进制原码表示法

    首先大概了解一下二级制加法运算

    二进制运算规则:逢2进1
    0+0=0,0+1=1,1+0=1,1+1=10 
    也就是当两个数相加的二进制位仅一位为1时,相加的结果为1;
    如果两个二进制位全是0,相加的结果仍为0;
    而如果两个相加的二进制位均为1,则结果为10(相当于十进制中的2)
    
    
    例如:
    # 这里暂时不考虑符号
    1010 + 0110 = 10 + 6 = 16
    
       1010
    +  0110
    ----------
      10000
    
    逢2进1(从右到左):
    0+0=0
    1+1=10  # 进位1,剩余0
    0+1+1(这个1是进位) = 10  # 进位1,剩余0
    1+0+1(这个1是进位) = 10  # 进位1,剩余0
    1  # 最后进位得到的1
    
    
    再比如
    111 + 111 = 7 + 7 = 14
    
       111
    +  111
    ----------
      1110
    
    逢2进1(从右到左):
    1+1=10  # 进位1,剩余0
    1+1+1(这个1是进位)=10+1=11  # 进位1,剩余1,这种情况的运算规则是:先算原本的1+1=10,再算10+1(进位)=11
    1+1+1(这个1是进位)=10+1=11  # 进位1,剩余1
    1  # 最后进位得到的1

    OK,原码表示法很简单有没有,虽然出现了+0和-0,但是直观易懂。
    于是,我们高兴的开始运算。
    
    0001+0010=0011 (1+2=3)OK
    
    0000+1000=1000 (+0+(-0)=-0) 额,问题不大
    
    0001+1001=1010 (1+(-1)=-2)
    
    噢,1+(-1)=-2,这仿佛是在逗我呢。
    
    于是我们可以看到其实正数之间的加法通常是不会出错的,因为它就是一个很简单的二进制加法。
    
    而正数与负数相加,或负数与负数相加,就要引起莫名其妙的结果,这都是该死的符号位引起的。0分为+0和-0也是因他而起。
    
    所以原码,虽然直观易懂,易于正值转换。但用来实现加减法的话,运算规则总归是太复杂。于是反码来了。

    2、反码

    我们知道,原码最大的问题就在于一个数加上他的相反数不等于零。

    例如:

    0000 0001 + 1000 0001 = 1000 0010 (1+(-1)=-2)

    0000 0010 + 1000 0010 = 1000 0100 (2+(-2)=-4)

    于是反码的设计思想就是冲着解决这一点,既然一个负数是一个正数的相反数,那我们干脆用一个正数按位取反来表示负数试试。

    反码:

    正数的反码还是等于原码
    
    负数的反码就是他的原码除符号位外,按位取反。

    例如:

    带符号的8位二进制:

    [+1] = [00000001] = [00000001]

    [-1] = [10000001] = [11111110]

    那么我们再试着用反码的方式解决一下原码的问题

    把原码都转成反码进行计算,得到反码,把结果得到的反码再换算回原码即可

    反码转换回原码:
    正数:反码就是原码
    负数:符号位不变,其他位按位取反

    # 正数和负数相加
    1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0
    互为相反数相加等于0,解决,虽然是得到的结果是-0
    
    1 - 2 = 1 + (-2) = [0000 0001]原 + [1000 0010]原 = [0000 0001]反 + [1111 1101]反 = [1111 1110]反 = [1000 0001]原 = -1
    结果也是正确的
    
    # 再试着做一下两个负数相加
    (-1) + (-2) = [1000 0001]原 + [1000 0010]原 = [1111 1110]反 + [1111 1101]反 = [1 1111 1011]反 = [1 0000 0100]原 = -4
    
    噢,好像又出现了新问题
    (-1)+(-2)=(-4)?

    看来相反数问题是解决了,但是却让两个负数相加的出错了。

    但是实际两个正数相加和两个负数相加,其实都是一个加法问题,只是有无符号位罢了。而正数+负数才是真正的减法问题.
    也就是说只要正数+负数不会出错,那么就没问题了。负数加负数出错没关系的,负数的本质就是正数加上一个符号位而已。

    在原码表示法中两个负数相加,其实在不溢出的情况下结果就只有符号位出错而已(1001+1010=0011)

    实际反码表示法其实已经解决了减法的问题,他不仅不会像原码那样出现两个相反数相加不为零的情况,而且对于任意的一个正数加负数的计算结果是正确的。
    所以反码与原码比较,最大的优点,就在于解决了减法的问题。

    但我们还有一个负数相加的问题,此时就需要用补码了。

    3、补码

    补码:

    1. 正数的补码等于他的原码
    2. 负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)

    [+1] = [00000001] = [00000001] = [00000001]

    [-1] = [10000001] = [11111110] = [11111111]

    补码运算规则是:

    • X+Y = [X]补 + [Y]补 = [X+Y]补 = 再转换为原码即可
    • X-Y = [X]补 + [-Y]补 = [X-Y]补 = 再转换为原码即可
    • -X-Y = [-X]补 + [-Y]补 = [-X-Y]补 = 再转换为原码即可

    例如:

    正数和负数相加:
    1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [1 0000 0000]补 = [0 0000 0000]原 = 0
    
    两个负数相加:
    (-1) + (-2) = [1000 0001]原 + [1000 0010]原 = [1111 1111]补 + [1111 1110]补 = [1 1111 1101]补 = [1 1111 1100]反 = [1 0000 0011]原 = -3
    
    # 由此可见,补码即解决了符号问题,也解决了负数加负数的情况

    4、移码

    移码(又叫增码或偏置码)通常用于表示浮点数的阶码,其表示形式与补码相似,只是其符号位用“1”表示正数,用“0”表示负数,数值部分与补码相同。

    即:不管正负数,只要将其补码的符号位取反即可。
    例如:
    X=-101011 , [X]原= 1010_1011 ,[X]反=1101_0100,[X]补=1101_0101,[X]移=0101_0101
     

    三、溢出

    1、溢出的常用处理方法

    用变形补码进行双符号位运算(正数符为00,负数符号以11)

    若运算结果的符号位为:

    00 正数无溢出

    01 正溢出(但是也是正数)

    10 负溢出(但是也是负数)

    11 负数无溢出

    2、溢出解析

    1.说到溢出,还是要先提一下自然丢弃。

    请看下面这个例子:

      -2 - 6

      -2  -->   1110[补]

      -6  -->   1010[补]

      相加  --------

      结果 (1)1000 

      结果的位数比原先的多出了一位,此处最左边的1,是会被自然丢弃的(就是不要了)。再看结果,对1000[补]=0000[原](也就是0)。这和我们想要的-8有天壤之别。为什么会出现这个情况呢?

      原因就是这里出现了溢出!

    首先来看溢出的定义:

      对一个N位二进制补码,其可以表达的范围是 - 2N-1+1 ~ 2N+1 - 1之间。如果超出这个范围就称为溢出了。

    拿上面的-2-6来说,我们刚刚在计算时,转换为二进制补码是4位的。它的取值范围是-7~+7之间。而我们想要的结果是-8,比范围的最小值还要小,这个叫做负溢出。同理如果想要的结果比最大值还要大,那么就叫做正溢出,如取值范围是-7~+7之间,想要的结果是+8,那么就是正溢出。

    说完了溢出的定义,我们来说说溢出的判定,就是怎么在计算开始时知道自己算的结果是不是溢出了?

    采用双符号形式,我们再计算一次 -2-6

      11 110[补]

      11 010[补]

       (1)11 000[补]

    得到结果是 11 000[补](最高位超出位宽,自然舍弃),采用双符号运算得到的最后结果应该是单符号的,

    就是:-2 = 11 110 前面双符号11代表负数, -6 = 11 010 前面双符号11代表负数,但是得到的结果11 000 只要前面1是符号位,代表负数,真值是1 000,

    因此最后结果:11 000[补] = 11 000[原] = -8

    再比如:

    -5-3(按8位算),11 111 1011[补] + 11 111 1101[补] =  (1)11 111 1000[补]

    双符号位的话,计算结果以符号位高位为结果符号,即 11,发生了负溢出。

    11 111 1000[补] = 1 0000 1000[原] = -8

     

    四、反码与补码的原理

    计算机中的数值是以补码形式存储的(只不过正数的补码跟原码一样。

    强调原码,反码,补码的引入是为了解决做减法的问题。在原码,反码表示法中,我们把减法化为加法的思维是减去一个数,等于加上一个数的相反数,结果发现引入了符号位,却因为符号位造成了各种意向不到的问题。

    反码+1 ,它只是补码的另外一种求法,不是补码的定义。

    1、补码的原理

    为了便于理解可以用时钟计算(12小时制的)
    9要拨到5,可以减4,也可以加8 ,所以此时 -4和+8是等价的 。
    那么 ,我们可以认为 8是4的补码
    那么这两个数有什么联系呢? 没错他们和在一起就绕了时钟一圈,用数学表达的话就是 两数的相加的绝对值 为 12
    类比到补码:首先的明白一圈是多少 ?假设机器一圈为128(2的7次方)
    于是用128减真值 也就是所谓的 数值位取反,末位加一 的操作了。
    再回到时钟, 9-4=9+[4]补=(9+8)%12 因为他会多走一圈,所以我们再在这里还要 %12,而计算机补码是有周期的,不用这一步。

    也就是,当前9点,我们希望拨到5点,做法如下

    1. 往回拨4个小时: 9 - 4 = 5

    2. 往前拨8个小时: (9 + 8) mod 12 = 5

    3. 往前拨8+12=20个小时: (9+20) mod 12 =5

    2,3方法中的mod是指取模操作, 17 mod 12 =5 即用17除以12后的余数是5.

    所以钟表往回拨(减法)的结果可以用往前拨(加法)替代!

    现在的焦点就落在了如何用一个正数, 来替代一个负数. 上面的例子我们能感觉出来一些端倪, 发现一些规律. 但是数学是严谨的. 不能靠感觉.

    首先介绍一个数学中相关的概念: 同余

    两个整数a,b,若它们除以整数m所得的余数相等,则称a,b对于模m同余

    记作 a ≡ b (mod m)

    读作 a 与 b 关于模 m 同余。

    举例说明:

    4 mod 12 = 4

    16 mod 12 = 4

    28 mod 12 = 4

    所以4, 16, 28关于模 12 同余.

    负数取模

    -1 mod 4 = (-1 + 4*n) mod 4,n取正整数,一直到括号里的数不为负数。

    例如:

    (-2) mod 12 = 12-2=10

    (-4) mod 12 = 12-4 = 8

    (-5) mod 12 = 12 - 5 = 7

    再回到时钟的问题上:

    回拨2小时 = 前拨10小时

    回拨4小时 = 前拨8小时

    回拨5小时= 前拨7小时

    注意, 这里发现的规律!

    结合上面学到的同余的概念.实际上:

    (-2) mod 12 = 10

    10 mod 12 = 10

    -2与10是同余的.

    (-4) mod 12 = 8

    8 mod 12 = 8

    -4与8是同余的.

    距离成功越来越近了. 要实现用正数替代负数, 只需要运用同余数的两个定理:

    反身性:

    a ≡ a (mod m)

    这个定理是很显而易见的.

    线性运算定理:

    如果a ≡ b (mod m),c ≡ d (mod m) 那么:

    (1)a ± c ≡ b ± d (mod m)

    (2)a * c ≡ b * d (mod m)

    如果想看这个定理的证明, 请看:http://baike.baidu.com/view/79282.htm

    所以:

    7 ≡ 7 (mod 12)

    (-2) ≡ 10 (mod 12)

    7 -2 ≡ 7 + 10 (mod 12)

    现在我们为一个负数, 找到了它的正数同余数. 但是并不是7-2 = 7+10, 而是 7 -2 ≡ 7 + 10 (mod 12) , 即计算结果的余数相等.

    接下来回到二进制的问题上, 看一下: 2-1=1的问题.

    2-1=2+(-1) = [0000 0010] + [1000 0001]= [0000 0010] + [1111 1110]

    先到这一步, -1的反码表示是1111 1110. 如果这里将[1111 1110]认为是原码, 则[1111 1110]原 = -126, 这里将符号位除去, 即认为是126.

    发现有如下规律:

    (-1) mod 127 = 126

    126 mod 127 = 126

    即:

    (-1) ≡ 126 (mod 127)

    2-1 ≡ 2+126 (mod 127)

    2-1 与 2+126的余数结果是相同的! 而这个余数, 正式我们的期望的计算结果: 2-1=1

    所以说一个数的反码, 实际上是这个数对于一个膜的同余数. 而这个膜并不是我们的二进制, 而是所能表示的最大值! 这就和钟表一样, 转了一圈后总能找到在可表示范围内的一个正确的数值!

    而2+126很显然相当于钟表转过了一轮, 而因为符号位是参与计算的, 正好和溢出的最高位形成正确的运算结果.

    既然反码可以将减法变成加法, 那么现在计算机使用的补码呢? 为什么在反码的基础上加1, 还能得到正确的结果?

    2-1=2+(-1) = [0000 0010] + [1000 0001] = [0000 0010] + [1111 1111]

    如果把[1111 1111]当成原码, 去除符号位, 则:

    [0111 1111] = 127

    其实, 在反码的基础上+1, 只是相当于增加了膜的值:

    (-1) mod 128 = 127

    127 mod 128 = 127

    2-1 ≡ 2+127 (mod 128)

    此时, 表盘相当于每128个刻度转一轮. 所以用补码表示的运算结果最小值和最大值应该是[-128, 128].

    但是由于0的特殊情况, 没有办法表示128, 所以补码的取值范围是[-128, 127]

  • 相关阅读:
    docker安装 与 基本配置
    linux 挂载windows ntfs 分区 -- centos 安装ntfs-3g
    Linux find 用法示例
    linux之sed用法
    js -ajax 学习
    搭建SSH框架整合Struts2和Spring时,使用@Autowired注解无法自动注入
    Hibernate学习之属性级别注解
    Hibernate学习之类级别注解
    org.hibernate.service.spi.ServiceException: Unable to create requested service [org.hibernate.engine.jdbc.env.spi.JdbcEnvironment]
    Hibernate学习之二级缓存
  • 原文地址:https://www.cnblogs.com/Zzbj/p/13621019.html
Copyright © 2020-2023  润新知