补码详解(转)

前言:几乎计算机入门书本的第一章都会涉及补码。但是从来不会深入讲解。如此重要的概念和应用不应该只知其然不知其所以然。
 
    本篇将澄清和解决如下几个问题:
    1.提出补码的缘由。
    2.补码的本质。
    3.补码的意义。
    4.我们能否自己设计一种计数体系(类补码)。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 
    第一、提出一个问题:
    给出8张扑克牌,我们约定扑克牌只能依次顺序排在桌面上,并且忽略牌面的大小,牌和牌之间的唯一区别就是正放还是反放(扣着的)。
    例如: 正 正 正 正 反 正 正 反。这算是一种排放方法。问一共有多少种排放方法?
    由于每张牌是正还是反与其他牌不冲突,也就是独立(独立事件)的。所以一共有2的8次方个排放方式,也就是256种。
 
    第二、延伸这个问题:
    我们要用这8张牌,向第三方传达一个信号,并且这个信号是一个数字。首先要做的就是约定。和第三方约定:正放代表1,反放代表0,
    权重从右到作分别是2的0次方、2的一次方... ...2的7次方。这样就能分别表示出 0——255一共256个数字。
    例如:正 正 正 正 正 反 反 反 代表11111000也就是128+64+32+16+0+0+0=240.
    可以和第三方有任何的约定:比如约定不管我排出什么 都表示1.或者比如我们约定只有后四张牌有意义。又或者。我们约定排序没有权重
    只进行加操作得出数字。(这些就是约定的任意性) 
 
    第三、回到计算机寄存器:
    假设计算机寄存器一共有8个位,那么这8个位可以表示出256个状态。类似,如果有16个位可以表示出65536个状态。
    这样表示的一个明显缺点就是不能表示出负数,例如 -5如何表示。
    关键点:我们不能说用第一个位表示符号。因为这样就颠倒了因果。我们只能说(16位的前提下),因为一个16位寄存器可以一共有65536
    种状态,这些状态可以无歧义映射65536个数(一个状态可以映射两个数但是会出现歧义)。例如:0——65535,我们把其中一部分状态的
    集合映射为负数,把这个集合的补集映射为正 数和0,这样是可以的,见图:
    

图片

 

    可以提出无数个方案来建立这个映射。为了方便我们把开头的第一个数字表示为符号位,

    并且约定:如果这个符号位是1那么表示负数,如果这个符号位是0那么表示正数。

    到这里一切似乎很合理。其实。如果我们要表示0,该如何表示呢?按照约定。0:00000000或者0:10000000.。一个数字零竟然有两种表示

    方法。我们关心的是这个问题是不是可以容忍。这里会有分歧,但答案是:可以容忍。其实这正是原码的表示法。这个可以容忍的问题也是

    原码的一个不足。我们用一个环来表示原码的形象化描述:

    

 

图片

     可以看到1——127有127个数、(-1)——-(-127)有127个数、再加上两个0.一共有256个数。之所以可以用环表示是因为不断的给寄存

    器加一可以使寄存器的状态陷入到死循环中。

 

    第四、对问题的深入:

    用原码表示的优点:约定简单明了、静态的表示数字也很容易实现。

    但是缺点也是明显的:第一就是0有两个状态与之映射,这样就浪费了一个状态的使用,暂时我们是不区分正零和负零的。再就是

    动态的数字运算实现复杂。并且符号位要单独处理。(这里有个关键的判断方法:一直加1判断是不是符号位要单独处理

    例如从127加1,应该得到128却得到-0(这里也可以理解为溢出),但是再加一,应该得到1,却得到-1.所以必须把符号位单独拿出来处理

    并且数值位也要进行不只一次的转换。并且有三个溢出点:从-127到0、从127到-0、从-0到-1(也就是在连续加一操作中必须出现三次中

    断)。

 

    第五、对问题的分析:

    仔细看上图,可以发现由于-0处于一个不合适的位置导致必须进行三次中断处理。那么已知我们有无数种映射方案,在这些方案中会不会有

    一个最合适的呢?我们把环的左边部分进行倒序映射,也就是用10000001映射-127,用11111111映射-1。-0暂时不去管。这样再来分析

    从原来的-127加1变成了-1加1,正好-1加1等于0,这个中断点成功的消除了!

    到现在上图中只剩下了两个中断点。并且到现在为止仍然符合第一位作为符号位的约定。这种映射方案的计算规则就是:如果是正数就用

    原码表示。如果是负数,数值位按位取反再加1(取反加1的本质就是进行倒序映射操作)。0单独处理并且有两种表示方法。

    如图:

    

 

图片

 注意:这不是反码(或者我们可以说是我们自己定义的反码)。可以看出方案的本质就是把负数重新倒序映射到原来的寄存器状态集合中。因为这样消除了一个中断点,这个被消除的

    中断点由于采用了这个方案现在可以进行连续加1操作(符号位可以不去单独处理)并且符合数学规则了。

 

    第六、对方案的进一步优化:

    现在为止。那个恨人的-0仍然在那里占着位置,既无用又无意义。其实到这一步很容易也非常难。

    澄清概念:现实存在、映射、

    试着回答下边问题:

    1.对于寄存器的某一个特定状态,可以有多少个带有具体数学意义的数字进行映射?

    2.我们能否把寄存器的某一个特定状态例如:01010100映射为数学中的0?

 

    如果这两个问题你能成功回答并且正确。那么现在一切都没有问题了。

    我们看第一个问题。答案是无数个。因为在数学中整数的个数是无数的。而任何一个数字都可以被这个特定的寄存器状态映射。映射成功的

    关键仅仅在于计算法则例如  :   { f(寄存器特定状态)----->数学中的一个整数 }。其中f就是这个计算法则。我们的按位取反加1是众多法则中

    的一个特例,倒序映射。

    如果第一个问题你理解了那么第2个问题是肯定的。

    回到上图中再看一眼那个数字-0.别犹豫了。把这个状态映射成-128吧。也就是f(10000000)---->(-128)

    最后一步:我们确定f(10000000)---->(-128) 中的f是什么。也就是确定它的计算法则。巧合的是计算法则正好是数值位按位取反再加1。

    那么现在从-1到-128的所有数字都可以被一个统一的计算法则进行映射了。其实这不是巧合(而是必然)。

    

 

图片

 

 

 

 

    第七、第一步总结:

    上边的方案便是补码方案。我们分析补码方案。

    1.可以看到中断点由反码的两个变成了一个。只有一个中断 点了!!(也可以说是溢出点)

    2.我们消除了-0.也就是现在的方案是一个一一映射。

    3.动态的数字运算中符号位全部参与了运算,不需要另外处理。

    4.对补码的解释有多种方法。本质还是映射的建立。这是最本质的解释。还有一种解释是从消除借位出发。比如9减去任何以为十进制数都不需要借位。同理1减去任何一位二进制数也不需要借位。这就是求补的过程,而不是本质。

 

    第八、最终总结:

    1.提出补码的缘由便是在进行寄存器状态到数字之间映射的过程中出现了各种问题,第一个是数字重叠、第二个是单独处理符号位、第三

        是物理层上的实现难度。

    2.补码的本质有两个方面:1.静态的数字映射为一一映射。2.动态的数字运算可以不去单独处理符号位,实际上到补码符号位已经可以全部

        参与运算了。可以断定:补码的本质就是一种映射方案。这个方案解决了静态表示和动态运算两个问题。

    3.补码的意义:1.降低了物理层的CPU设计难度。2.符合数学上的运算规则(仅仅加入了一个简单的映射法则f)3.在寄存器状态的加1死循环

        中只有一个中断点(溢出点),这个溢出是无法避免的,因为寄存器的位数是有限的。

    4.如果全部理解了上文。答案是肯定的。我们可以设计一个计算法则,例如按位取反加n(n属于所有整数)。但是补码是最佳选择。

 

 

   

    

posted on 2012-11-29 18:17  zhangdinet  阅读(504)  评论(0编辑  收藏  举报