zoukankan      html  css  js  c++  java
  • 原码反码补码详解

    一. 机器数和真值

             在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念.

     

    1.机器数

             一个数在计算机中的二进制表示形式,  叫做这个数的机器数。

     

    2.真值

             机器数的实际值称为真值。

            

    3.符号数和无符号数

             符号数和无符号数是针对符号出现的两种机器数表示方法。同一个二进制数,对符号数和无符号数具有不同的含义。

             符号数如:    char, short ,int, long等类型的变量

             无符号数如:unsigned char, unsigned short , unsigned int, unsigned long, 指针等类型的变量

     

    4.定点数与浮点数

             定点数和浮点数是针对小数点出现的两种机器数表示方法。

            

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

            

             只有符号数才有原码, 反码, 补码

             在探求为何机器要使用补码之前, 让我们先了解原码, 反码和补码的概念.对于一个数, 计算机要使用一定的编码方式进行存储. 原码, 反码, 补码是机器存储一个具体数字的一种编码方式.

     

    1. 原码

     原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:

     [+1]原 = 0000 0001

     [-1]原 = 1000 0001

     第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:

     [1111 1111 , 0111 1111]

     即

     [-127 , 127]

     

    2. 反码

     反码的表示方法是:正数的反码是其本身,负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.

     [+1] = [00000001]原 = [00000001]反

     [-1] = [10000001]原 = [11111110]反

     可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算.

            

    3. 补码

     补码的表示方法是:正数的补码就是其本身

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

     [+1] = [00000001]原 = [00000001]反 = [00000001]补

     [-1] = [10000001]原 = [11111110]反 = [11111111]补

     对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.        

     

    三. 为何要使用原码, 反码和补码

     

    1.使用补码,可以将符号位和其它位统一处理;同时,减法也可按加法来处理。

    2.使用补码, 不仅仅修复了0的符号以及存在两个编码的问题, 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].

     

             (-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补

     

             -1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的)

     

    四.补码表示的溢出问题

    以下是本人的补充的理解,不知道是否正确:

    由于计算机中的数字用补码表示,例如8bit的byte类型的表示范围为:

    [-128, 127]

    0 = [0000 0000](补)

    -128 = [1000 0000](补)

    127 = [0111 1111](补)

    当byte类型的变量超上限127时,如:

    +128 = -(-128)= 127 + 1 
    = [1111 1111](补)+ [0000 0001](补) 
    = [1000 0000](补) 
    = -128

    +129 = 127 + 2 
    = [1111 1111](补)+ [0000 0001](补) 
    = [1000 0001](补) 
    = [1111 1111](原) 
    = -127

    当byte类型的变量超过下限-128时:

    -129 = -128 - 1 
    = [1000 0000](补) - [0000 0001](补) 
    = [0111 1111](补) 
    = 127

    -130 = -128 - 2 
    = [1000 0000](补) - [0000 0010](补) 
    = [0111 1110](补) 
    = 126

    byte a = -128, b = (byte) 128, c = (byte) 129, d = (byte) 130;

    byte e = (byte) -129, f = (byte) -130;

    System.out.println(a == ((byte)-a));    // true

    System.out.println(b);  // -128

    System.out.println(c);  // -127

    System.out.println(d);  // -126

    System.out.println(e);  // 127

    System.out.println(f);  // 126

     

            

    五.原码, 反码, 补码 再深入(不一定要搞懂)

    计算机巧妙地把符号位参与运算, 并且将减法变成了加法, 背后蕴含了怎样的数学原理呢?

     将钟表想象成是一个1位的12进制数. 如果当前时间是6点, 我希望将时间设置成4点, 需要怎么做呢?我们可以:

     1. 往回拨2个小时: 6 - 2 = 4

     2. 往前拨10个小时: (6 + 10) mod 12 = 4

     3. 往前拨10+12=22个小时: (6+22) mod 12 =4

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

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

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

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

     

     同余的概念

     

    两个整数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 同余.

     

      负数取模

     正数进行mod运算是很简单的. 但是负数呢?

     下面是关于mod运算的数学定义:

     

     上面是截图, "取下界"符号找不到如何输入(word中粘贴过来后乱码). 下面是使用"L"和"J"替换上图的"取下界"符号:

     

    x mod y = x - y L x / y J

     

    上面公式的意思是:

     x mod y等于 x 减去 y 乘上 x与y的商的下界.

     以 -3 mod 2 举例:

     -3 mod 2

    = -3 - 2xL -3/2 J

    = -3 - 2xL-1.5J

     = -3 - 2x(-2)

     = -3 + 4 = 1

     所以:

     (-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].

  • 相关阅读:
    Linux crontab 命令
    tcpdump抓包工具
    tcpdump过滤某个端口
    ARM处理器基础Cortex-M4
    rtems floating poing switch
    ARM处理器的堆栈和函数调用,以及与Sparc的比较
    关于调用堆栈,任务堆栈
    如何测试嵌入式处理器的CPU使用率
    关于嵌入式实时操作系统的实时性
    RTEMS API
  • 原文地址:https://www.cnblogs.com/maoypeng/p/9123826.html
Copyright © 2011-2022 走看看