补码

作者: 良辰夜 | 来源:发表于2019-11-05 17:10 被阅读0次

    转载需首行注明原地址

    本章参考 车向泉老师的公开课《计算机系统中的数据表示》

    目标:真正理解为什么要有补码,明白补码的各种性质

    目录

    1. 原码
    2. 模运算
    3. 补码
    4. 补码的性质
    1. 计算机不会存小数点, 多一个小数点,意味着数据需要多占一位,而且计算起来,需要拆分小数点后进行运算,所以计算机存的都是定点数(提前就拆分好,避免运算时拆分)。
      而浮点数可以用两个定点数表示,所以以下我们研究的各种存储编“码”都是针对定点数而言。

    1. 原码

    大家都知道计算机里面存的是2进制,而实际的数据是有正负之分的,为了标识正负,很容易想到给数加上一个进制位。
    于是就有了原码,规定加0表示正数,加1表示负数。

    • 定义:


    • 整体来讲:
      原码就是首位用0表示正数,1表示负数,所以非常简单直观。
      这种简单和直观会带来很多个头疼的问题:
      1 +0和-0,0不唯一了
      2 加减运算及其复杂
      例如:要计算 A - B,首先要判断A和B的数的正负和大小,以此来判断最终的正负及运算规则,比如A>B,A+B- (+表示正,A+表示A是正数,同理,B-表示B是负数),那么实际上算A+|B|,如果A<B,A+B+,那么实际上应该算 B-A,结果上放上负号,如果....
      无疑,这样的设计放到运算器上,可能今天就没有PC了,那么这种运算最致命的地方在哪里? 为什么会如此复杂?
      其根源在于: +-号无法带入运算,这时,数可能是正负,运算可能是加减,同时A,B本身还有大小,则可能出现222共计16种情况。

    • 那么如何把+-号带入计算机呢?
      首先必须了解计算最基本的运算,然后带入正负,通过正数求负数。

    2. 模运算

    计算机有个非常讨人厌的且无可避免的情况,“溢出“,因为计算机的字长是有限的,而计算的数可以生成无限的结果。那么当计算出来的数超过字长,应该怎么处理呢?

    • 一般来说有两个处理方法,一个返回错误,一个将无法表示的位丢弃(溢出)。
    • 返回错误这当然没什么好说,尽管"溢出"同样让人"不爽"。但是还是应该研究溢出,建立“溢出”模型。

    于是便有了模运算(这并不是历史,我是瞎推测的)

    模运算定义:
    在一个运算系统内,若A、B、M满足以下关系:A=B+K*M (K为整数),则记作A≡B(mod M)。

    一个很形象的例子:时钟,我把时钟上任意一个时间,拨动一圈,它又回到拨动前的时间。而“溢出”的就是一个天的时间,这和计算机内部非常像。

    3. 补码

    知道了计算机最基本的运算规则:有模运算。那么应该带入正号来求出负数。
    首先,还是规定首位为0就是正数。例如正数A。
    那么负数可以看成:-A(0<=A<M,M为模)
    已知:A=B+K*M ,可得 :-A = -A + M ,
    同时:已知0<=A<M , 可得 :(-A+M) > 0。
    这相当于用一个正数 (-A+M) 表示出一个负数 -A 。
    同理可得,A = A+M。

    由此,可以得出补码的定义:
    对于任意一个数 X ,都有X = X + M (X mod M)。

    推广到计算机(假设字长为n),可以得到定义:


    (注意:负数部分=号,是强制规定,例如8位字长机器码对应10000000,我们强制认定为-128)

    4.补码的性质

    4.1 补码的符号位

    由此可知,首位0一定是正数,首位1一定是负数。

    4.2 补码中的0唯一

    4.3 补码的范围

    假设机器字长为n。

    • 定点小数:
      -1 <= x < 1- 2^(-(n-1)) ==> 1.0 ~ 0.111....1 (n-1个1)

    • 定点整数:
      -2^(n-1) <= x <= 2^(n-1)-1 ==>1000...0 ~ 0999..9

    4.4 补码、真值、原码间的相互转换

    4.4.1 真值 => 补码

    x为真值

    • 当 x >= 0 ,补码==真值==原码
    • 当 x <= 0

    假设字长为n, |x|代表数值位
    小数:

          x补 = 2 + x  // -1 <= x <=  2^(-(n-1)
              = 1+(1-2^(-(n-1)))-|x| + 2^(-(n-1)) // 1+ 0.111...1 - |x| + 0.000...1          
              = 符号位为一 + |x|全部位按位取反 + 末位加一 
    
              //x为-1时,|x|=1,溢出了,结果为0。按位取反+1后继续溢出为0,符号位设置1,结果刚好对-1(主要原因是-1这个是强制认定的值)
    

    整数:

          x补 = 2^n + x // -2^(n-1) <= x < 0
              =2^(n-1) + (2^(n-1)  - 1) - |x|  + 1
              =符号位为一 + |x|全部位按位取反 + 末位加一 
    

    由此,得到一个经验"按位取反,末尾加一"

    然而,这个多了一个“加一”,计算机多跑了一次,有办法优化吗?


    我们发现,可以从左往右早第一个1,1和1右边的不变,左边的按位取反。
    例如 ,-0.10100100 ,可以一步写出答案1.01011100。

    4.4.2 补码 => 真值

    假设字长为n,x为补码
    小数:

    x真 = x - 2
        = -(1-2^(-(n-1)) - (x-1) +  2^(-(n-1))  // -(0.111..1 - 数值位 + 0.000...1)
        = -(补码数值位按位取反 + 0.000...1)
    

    整数:

    x真 = x - 2^n
        = -(2^(n-1)-1 - (x-2^(n-1)) + 1)// -(111...1 - 数值位 + 1)
        =- (补码数值位按位取反 + 1)
    

    同 真值转补码,补码转真值也可以优化:
    从左往右早第一个1,1和1右边的不变,左边的按位取反,再加上-号。

    4.5 符号的扩展

    原则:扩展后,不能影响大小。

    正数:
    小数尾位补0,整数高位补0,因为补码等于真值,所以扩展后保持不变。

    负数:

    • 小数:末尾补0
    • 整数:高位补1

    简单证明下整数:(注意,x为什么可以替换,因为我们是扩展符号位,而x扩展前后必须想等)


    4.6 补码的算术右移


    所以,定点小数,正数右移高位补0,负数高位补1

    我们推导一下定点整数:
    正数:

    [1/2x补] = 1/2x = 1/2x补
    

    负数:

    [1/2x补] = 2^n + 1/2x
             =2^n + 1/2(-2^n + x补)
             =2^(n-1) + 1/2x补
    

    所以,定点整数,正数右移高位补0,负数高位补1

    由此,可以得到结论,正数右移,高位补0,负数右移高位补1。

    4.7 补码的算术左移

    这里主要的问题:算术左移会让模变大,否则溢出。

    相关文章

      网友评论

          本文标题:补码

          本文链接:https://www.haomeiwen.com/subject/eowrbctx.html