直观的理解计算机中的数值编码

 直观的理解计算机中的数值编码

      

       什么是编码

大家都知道计算机其实是一个二进制的世界,但是它又是如何为我们呈现出另一面缤纷多彩的世界出来的呢?编码技术就是其中最重要的基础组成部分,事实上在人类世界中编码技术是无处不在的。

这里所说的编码指并非指CodingCoding指的是书写代码的过程,即编程(Programming)

首先让我们用“数学”的语言描述一下到底什么是编码:

  编码就相当于一个函数,它将一个集合中的元素映射到另一个集合中去。

 

       通俗的讲,编码就是一个协议(规定),以实现用一个信息来代替另外的一个信息的目的。计算机世界是一个二进制世界,那么她是如何认识‘A’这个字母的呢?答案就是利用编码技术硬性地将二进制数‘1000001’映射为‘A’,只要是遵循这项编码技术的计算机,当它遇到一个字符值等于‘1000001’时,她就会将它解释为字符‘A’。

       一般而言,编码中的两个集合之间的元素都是一一对映的,当然也有例外。

       什么是数值编码

       由于计算机是二进制的,无法表达负数,整数值,小数等数值,因此计算机需要将这些数值进行编码,来扶持这些数值的使用。下面只讲正负整数值的映射。

       原码

    原码是计算机数值编码中最简单的一种,当然也有许多的局限性:

原码     十进制数

00000 映射为 0

00001 映射为 1

00010 映射为 2

00011 映射为 3

00100 映射为 4

11111 映射为 31

可以看出,现在上面的原码编码与二进制数是一样的。聪明的童鞋已经看出来了,使用上面的原码不能表示负数,因此我们还是利用编码技术,将第一个二进制数位规定为符号位,当这个位为0时,表示正数;为1时表示负数。如下:

原码      十进制数

0 0000 映射为 0

0 0001 映射为 1

0 0010 映射为 2

1 1101 映射为 -13

1 1110 映射为 -14

1 1111 映射为 -15

    对于原码编码来说,加法是非常简单的,只要在能够表示的范围内,直接使用二进制加法即可,但是使用减法可就麻烦了(计算机中为了简化设计,使用编码技术来用加法实现减法),例如:

    2 – 13 = -11

    根据交换律,我们可以写成这种形式:

    -13 + 2 = -11

    那么使用原码的加法则为:

    1 1101 + 0 0010

那么结果就为:

1 1101 + 0 0010 = 1 1111

可以看出1 1111在原码中映射为-15,而正确的结果应该是-11。因此当遇到减法时,使用原码是不合适的。为什么不适合呢?我们再观察一下表格,我们可以看出,其中对于负数的编码中,二进制值越大,映射出来的负数却越小:

1 1101 < 1 1110 < 1 1111

-13    >-14    > -15

因此,当我们对-13加上2时,反而得出了-15,而不是-11

为了解决这个问题,就有了下面的反码编码技术以及补码编码技术。

反码

虽然使用原码能够表示负数,但是在运算减法时得出的却是错误的结果。为了满足使用加法实现减法的要求,前辈们创造出了反码这项编码技术(致敬)。反码的映射表是这样子的:

反码      十进制数

0 0000 映射为 0

0 0001 映射为 1

0 0010 映射为 2

1 1101 映射为 -2

1 1110 映射为 -1

1 1111 映射为 -0

我们对照表中的每一对相反数可以看出,他们的二进制码都是取反的。例如

1 1110 -1

| ||||

0 0001  1

将一个二进制数中的位全部置反的操作(0则置为11则置为0),我们称之为取反。在反码的计算机中,对一个数取反,即可得到他的相反数。

接下来,让我们发挥一点想象力来探究下反码对于减法是如何实现的,我们根据0 00001 1111的顺序列出反码的映射表:

0    被映射为 0 0000 _____________________正限线 <--------------|

1    被映射为 0 0001                                                 |

2    被映射为 0 0010                                                 |

3    被映射为 0 0011                                                 |

4    被映射为 0 0100                                                 |

5    被映射为 0 0101                                                 |

6    被映射为 0 0110                                                 |

7    被映射为 0 0111                                                  |

8    被映射为 0 1000                                                 |

9    被映射为 0 1001                                                 |

10   被映射为 0 1010                                                 |

11   被映射为 0 1011                                                  |

12   被映射为 0 1100                                                 |

13   被映射为 0 1101                                                 |

14   被映射为 0 1110                                                 |

15   被映射为 0 1111                                                  |

_________________________________________假想的对称轴            |

-15   被映射为 1 0000                                                |

-14   被映射为 1 0001                                                |

-13   被映射为 1 0010                                                |

-12   被映射为 1 0011                                                |

-11   被映射为 1 0100                                                |

-10   被映射为 1 0101                                                |

-9    被映射为 1 0110                                                |

-8    被映射为 1 0111                                                |

-7    被映射为 1 1000                                                |

-6    被映射为 1 1001                                                 |

-5    被映射为 1 1010                                                |

-4    被映射为 1 1011                                                |

-3    被映射为 1 1100                                                |

-2    被映射为 1 1101                                                 |

-1    被映射为 1 1110                                                |

-0    被映射为 1 1111_____________________负限线 <--------------|

我们可以看出,正数跟负数被一条假想的对称轴分开来,相互对称。我们可以看到,反码的编码方式避免了原码所出现的问题:

-2 <     -1    < -0

1 1101 < 11110 < 1 1111

接下来我们看看反码是如何做减法:

2 – 13 = 2 +(-13) = 0 0010 + 1 0010 = 1 0011

通过查表可以知道1 0011正是-11

目前为此,反码的减法似乎已经能够正常工作了,但是接着看下面的式子,情况却有些不同:

-2 – 13 = -2 + -13 = 1 1101 + 1 0010 = 0 1111

注意虽然1 1101 + 1 0010应该等于10 1111,但在我们这里的内存模型中,一个数值最多由5个位来储存,因此第6位会被自动丢弃了。

通过查表可以知道0 1111+15,而不是我们要的值-15。我们接下来比较一下这两种情况,探究一下为什么会出现这种问题?

第一种情况:2 – 13 = 2 + (-13) = 0 0010 + 10010 = 1 0011

对于第一种情况,我们可以看到计算机实际上是做了0 0010 + 1 0010这个加法。参照映射表来看,在这个加法中,我们可以把0 0010看成基址,而1 0010是一个偏移,意思就是把0 0010做为原点,向下走1 0010步,也就是说从+20 0010)这个位置,向下走181 0010)步,我们可以看到+2向下的第18个数正是-11,因此答案正确。

第二种情况:-2 – 13 = -2 + -13 = 1 1101 +10010 = 0 1111

第二种情况与第一种情况稍有不同。我们可以看到这个操作相当于以-2为原点,向下走18步。但是我们可以看到,当我们走到第三步是就已经越出了负限线,回到正限线从头开始计数,更糟糕的是反码的编码中有两个0,一个+0,一个-0,占了负限线,正限线两个位置,因此我们走的两步实际上都踏在了0这个值上,所以我们距离正确的值就会少走一步。因此,为了得到正确的结果,那么只要我们发现运算时跨越了负限线,那么其值就一定要再加上1(即再向下踏一步)。

纠正后的操作如下:

-2 – 13 = -2 + -13 = 1 1101 + 10010 = 0 1111

0 1111 + 1 = 10000-15

至此,反码减法完美实现。

实际上“跨越了负限线”对应的计算机术语是“溢出”,即当前的5位的内存位不足以保存当前的值时(0 0010 + 1 0010的值应该为6位的10 1111),我们称这种情况为“溢出”,当发生溢出时,计算机舍弃第6位(相当于从正限线从新开始向下走步数一样,而不是跑出负限线),并设置溢出位标志为1,随后计算机将溢出位标志的值加到结果值上。

实际上不管是第一种还是第二种情况,计算机都会将溢价标志的值加到结果值上,结果总是正确的,而不是多加电路来判断是否加上溢出位标志:

2 - 13 + 溢出位 = 0 0010 + 10010 + 溢出位 = 1 0011 + 0(溢出位) = 10011

-2 - 13 + 溢出位 = 1 1101 + 10010 + 溢出位 = 0 1111 + 1(溢出位) = 1 0000

       注意,此处一旦计算机做完加法,溢出位就会被置位并与其结果相加。

       补码

       我们可以看到,反码的实现有一个问题,因为它有两个0,而不是一个0,并且这两个0+0-0)是头尾连接在一起的。因此,我们需要进行改进,我们将-0-15都向下位移一个位置,-0则越过负限线与正限线的+0重叠在一起,映身表就变成这样:

0    被映射为 0 0000 _____________________正限线 <--------------|

1    被映射为 0 0001                                                  |

2    被映射为 0 0010                                                 |

3    被映射为 0 0011                                                 |

4    被映射为 0 0100                                                 |

5    被映射为 0 0101                                                  |

6    被映射为 0 0110                                                 |

7    被映射为 0 0111                                                 |

8    被映射为 0 1000                                                 |

9    被映射为 0 1001                                                  |

10   被映射为 0 1010                                                 |

11   被映射为 0 1011                                                 |

12   被映射为 0 1100                                                 |

13   被映射为 0 1101                                                 |

14   被映射为 0 1110                                                 |

15   被映射为 0 1111                                                 |

_________________________________________假想的对称轴            |

-16   被映射为 1 0000                                                |

-15   被映射为 1 0001                                                |

-14   被映射为 1 0010                                                |

-13   被映射为 1 0011                                                 |

-12   被映射为 1 0100                                                |

-11   被映射为 1 0101                                                |

-10   被映射为 1 0110                                                |

-9    被映射为 1 0111                                                 |

-8    被映射为 1 1000                                                |

-7    被映射为 1 1001                                                |

-6    被映射为 1 1010                                                |

-5    被映射为 1 1011                                                |

-4    被映射为 1 1100                                                |

-3    被映射为 1 1101                                                |

-2    被映射为 1 1110                                                |

-1    被映射为 1 1111_____________________负限线 <--------------|

我们可以看到,在原来的位置(即原来是-15的位置)现在由-16来代替,这样负数就比正数多了一个数,所以补码的正负数是不对称的。

对于反码来说将一个负数转化为正数的方法是使用取反操作,对于补码来说,也有一个操作将负数转化为正数,我们称之为取补。

假设有一个数A,其取补操作为:1.A的反码 2.反码值加1

我们知道反码是从补码改进来的,主要的改动是负数向下位移了一位,因此我们只要求出A的反码,然后加1即可。反码是可以使用取反来将负数转化为正数的,但是补码则不行,因为补码正负数分布是不对称了(补码中0的补码还是0)。当然我们使用逆运算也是可以求出来的,但已经不能叫做补码操作了:

求补:-A = 求反(+A + 1

求补逆运算:求反(+A = -A - 1 = +A = 求反((-A-1

即将负数减1,然后求反即得正数值。

我们可以使用上面反码中所举的两条式子来验算一下补码的减法:

第一种情况:2 – 13 = 2 + (-13) = 0 0010 + 1 0011 = 1 0101

       1 0101正是-11的补码,正确。

第二种情况:-2 – 13 = -2 + -13 = 1 1110 + 10011 = 1 0001

       1 0001正是-15的补码,正确。
      

到这里我们已经学习了原码、反码以及补码。最后在后面留一道思考题。

 

思考题:

       在补码中,为什么+3+4等数取补后在负数部分都有对应的位置(即-3-4两个位置),而0取补跟-16逆取补其对应的位置都是它们自身?为什么是-160这两个数而不是其它数?给出一种直观的解释。

 

对于计算机浮点数编码部分将在另一篇文章中讲叙。

                                                                                                                                                                

Thanks for reading, by XLCYun.

                                                                                                                                                   袖里藏云写于2013/10/20凌晨150

 

希望对计算机学习者们有所帮助,有问题请留言。转载请注明出处。

 

原文地址:https://www.cnblogs.com/XLCYun/p/3378724.html