第二章比特,数据类型和运算2.1比特和数据类型2.1.1信息的最小单位——比特我们在第一章中提到,计算机是一个由多个层次组织起来的系统。在计算机中通过电子的流动,一个用自然语言描述的问题可以轻而易举的得到解决。在计算机的内部,数以亿计非常微小、快速的元件控制着电子的流动。这些元件对电路中电压的有无做出反应。事实上,它们可以对电压具体的数值做出反应,而不仅仅是对电压的有无做出反应。但是这样会使控制电路和探测电路变得非常复杂而没有多少实际用途,因此在实际的应用中往往是探测两点之间电压的有无而不是测量电压的具体值。要明白这些,先想一想家中的插座孔,你可以测量一下两孔间电压具体的值,比如,是120伏特呢,还是115伏特,或者是118.6伏特。但是电路系统只会探测电压是否存在,因为这样更简单。如果你想测量电压值,那么你还需要一组仪器,而如果想探测电压是否存在,把你的手指伸进去就足够了。我们把存在电压用“1”表示,而把不存在电压用“0”表示,我们称这一个个的“0”和“1”为比特(bit),或位,是“二进制位”(binarydigit)的缩写。想想你从小就使用的0,1,2,3,4,5,6,7,8,9,它们是十进制数,用十个符号表示,而二进制只有两个表示数的符号:0和1。更精确的说,计算机并不是区分电压的绝对不存在(即0)和绝对存在(即1)。实际上,计算机的电路区分的是接近0的电压和远离0的电压。例如,如果计算机把2.9伏的电压表示为1,把0伏的电压表示为0,那么2.6伏的电压也会被视作1,而0.2伏的电压会被当作0。计算机要解决一个真正的问题,必须能唯一的识别出许多不同的数值,而不仅仅是0和1。一根线路上的电压只能唯一的表示两个数值中的一个,一个表示为0,另一个表示为1。这样,为了唯一的识别出多个数值,必须对多个位进行组合。例如,如果我们用8位(对应8根线路上的电压),我们就能用01001110表示某一个特定值,用11100111表示另一个值。事实上,如果我们使用8位,最多能区分出256(即28)个不同的值。一般说来,如果有k位,我们最多能区分出2k个不同的值。这些k位的每一种组合都是一个编码,对应着某个特定的值。2.1.2数据类型对于表示同一个数值,存在许多种表示方法。例如,数字5可以被写为5。这是你习惯的标准的十进制计数法。它也可以被一个人用伸出的手指数来表示,这种方法写下来就是11111,这种计数法有个名字——一元计数法。罗马字中还有另一种表示5的符号——字符v。我们即将会看到5的第四种符号表示是二进制00000101。只是简单的表示这些数值还不够,我们还必须能对这些数值进行运算。如果在计算机上能对以某种表示法编码的信息进行运算,我们就把这种特殊的表示法称为数据类型。每种指令集结构都有它自己的数据类型集,和对那些数据类型进行运算的指令集。在本书中,我们主要使用两种数据类型:用来表示我们要进行算术运算的正负整数的二进制补码整数,和用来表示我们想输入计算机或显示在计算机显示器上的键盘上的字符的ASCII码。在后面就会解释这两种数据类型。事实上,在大多数计算机上还存在着多种数值表示方法。回忆在中学学习过的“科学计数法”,它要求你将十进制数621表示为6.21*102。在计算机中,也存在以这种形式表示的数字,并且也提供了对这种表示法的数值的运算。这种数据类型通常被称为浮点数。我们将在2.6节展示这种表示法。2.2整数数据类型2.2.1无符号整数我们即将看到的第一种信息的表示方法,或数据类型是无符号整数。无符号整数在计算机中有很多用途。如果我们想将某个任务执行有限次,可以使用无符号整数,表示该任务已经执行的次数。计算机中的存储单元的地址,就像房屋的地址一样,可以通过“某大街129号”和“某大街131号”来区分,也可以使用无符号整数来表示。首先回忆一下我们日常使用的十进制系统。在一个十进制数329中,尽管单独的3的绝对值只是9的1/3,但这里的3表示比9大得多的值。原因就在于这个3在329中的位置决定了它表示300(3*102),而9则表示9*100。这就是位置计数法,或称为定位数制。在十进制系统里,10被称为数制中的基数或基。在计算机中,可以使用类似的采用位置计数法的一串二进制数来表示无符号型整数,不同的是基数为2,二进制数为0和1。例如:如果我们使用5位有效数字来表示我们需要的数值,则数字6可以表示为00110,即:0*24+0*23+1*22+1*21+0*20使用k位数,就可以表示从0到2k-1共2k个整数。使用5位数,可以表示十进制整数0到31。2.2.2有符号整数然而在实际的计算中,还经常会使用负数,因此只有无符号整数是不够的。为了表示有符号整数,我们可以将k位的2k个不同的数字分为两半,一半表示正数,另一半表示负数。这样,使用5位码字就可以表示从+1到+15的正数,以及从-1到-15的负数。这样,就有30个整数被表示出来。因为25是32,那么,还有两个5位的码字没有被分配。其中一个是00000,如果将其分配给数值0,现在,就得到一个从-15到+15的完整的整数值系列。还留下最后一个数没有分配,对于如何对其进行分配的问题,首先需要考虑的是从+1到+15,从-1到-15,到底是哪些码字与其一一匹配的?首先,正数是按照位置计数法直接表示的。因为有k位,而且我们想要用2k个码字的一半来表示从0到2k-1-1的正数,所有的正数在它们的表示法中都会在最高位有一个0。在k=5的情况下,最大的正数15使用01111来表示。注意在图2.1的三种数据类型中,0和所有正数都是以0开头。那么,负数是如何表示的呢(在k=5的情况下,从-1到-15)?通常,第一个想法就是:如果最高位为0表示是正数,那么把该数字的最高位设为1就表示其对应的负数。这种表示法就是原码数据类型,见图2.1。第二个想法是(这种方法实际上曾经在某些早期的计算机上使用过,如数据控制公司6600):对正数“按位取反”,就表示与其绝对值大小相同、符号相反的负数,例如,+5是用00101来表示的,那么-5就被记作11010。这种表示法在计算机工程中被称为反码,见图2.1。计算机设计者可以用任意的位组合来表示任意整数。但是究竟采用哪种表示方法呢?实际上,原码和反码在设计加法逻辑电路时,都会使问题复杂化。原码和反码在进行加法运算时都会造成不必要的硬件需求,于是就出现了补码表示法,见图2.1。补码被运用于现在的每一台机器之中。2.3二进制补码整数我们在图2.1中看到了从-16到+15的二进制补码表示法。为什么要采用这种表示法呢?首先,正整数使用直接的位置计数法来表示。对于5位而言,我们使用了25的一半的码字来表示整数0到+15。对负数表示法的选择是基于尽可能使逻辑电路最简单的想法,几乎所有的计算机都使用相同的基本结构来进行加法运算。它被称之为算术逻辑单元,也就是我们所知道的取首字母缩写的ALU。我们将在3、4章介绍ALU的真正结构。现在只需要了解:一个ALU有两个输入和一个输出。在它进行运算时,它将要相加的两个二进制位组合作为输入,求和产生的一个二进制位组合作为输出。例如,如果ALU能进行5位的输入组合的运算,两个输入值为00110和00101,结果(ALU的输出)是01011。加法过程如下:00110+00101=01011两个二进制位组合的加法和两个十进制位组合的加法相同,从右向左,一列一列的运算。如果一列的加法有进位,进位立即加至它的左列。值的一提是二进制ALU并不知道(也不关心)所加的两个位组合表示什么。它只是简单的将二进制数相加。ALU只做加法而不考虑其他,这一点将对我们为整数分配码字十分有利。首先考虑,如何分配码字[表示一个数(或字符)的若干位二进制代码。],使得两个绝对值相同符号相反的整数在ALU做加法的结果为0。也就是说,如果对ALU的输入是A与-A的表示,那么,ALU的输出应是00000。为了实现这个目的,补码数据类型定义了每一个负整数的表示,即:当ALU把它加上绝对值大小相同的正数,结果会是0的表示法。比如说,既然00101是+5的表示法,11011就被选作-5的表示法。此外,还必须考虑的重要一点是,分配码字应使ALU在对每个数值加00001后,应得到正确的结果。这样,所有的二进制数值就像十进制的-15到+15一样。我们可以使用数学语言来表示:表示(VALUE+1)=表示(VALUE)+表示(1)以上两点,就足以保证ALU正确地做加法运算(只要得到的结果不大于+15或小于-16)。特别的,注意-1和0的表示,分别为11111和00000。当我们对-1的表示加上00001,我们就得到00000,虽然有一个进位,但是该进位不影响结果。也就是说,把00001和-1的表示相加的正确结果是0,而不是100000。因此,进位可以被忽略。实际上,在做补码算术运算时,这个进位总是被忽略。一个计算-A的表示的简便方法:已知A的表示,对A按位取反,然后把A和A的反码相加;这个结果是11111。如果再加上00001,就得到结果为0。因此,-A的表示可以简单地通过把A的反码加1得到。例题2.1-13的二进制补码表示是什么?1.如果A是+13,它表示为011012.A的反码是100103.加1到10010得到10011,-13的二进制补码表示是10011我们可以通过对A和-A的表示作加法,来验证我们的结果:01101+1001100000你可能已经注意到01101和10011的加法,除了得到00000,还得到一个进位。也就是说,01101和10011的二进制加法实际上得到100000。然而,正如我们前面看到的,在使用二进制补码的情况下,这个进位可以被忽略。至此,使用5位,我们能够识别出15个正数,15个负数,和一个0。如果k=5,我们能够识别出32个不同的数,但是我们只说明了31(15+15+1)个。剩下的一个表示是10000,我们应该分配一个什么值给它?我们注意到-1是11111,-2是11110,-3是11101,等等。继续下去,-15是10001。正如正数的表示法一样,当我们从-1到-15按顺序后退时,ALU从每一个表示上减去00001。这样,将-16分配给10000是很便利的,那就是从10001减去00001得到的数值。在第5章,我们将说明一个被我们亲切的称为LC-3(小计算机3)的计算机。LC-3对16位的数值进行运算。因此,LC-3中的二进制补码类型整数是从-32768到32767的整数。2.4二进制-十进制转换我们经常需要在二进制补码数据类型和日常生活中使用的十进制表示之间进行转换。2.4.1二进制到十进制的转换我们按如下方法从二进制补码向十进制转换:为了举例说明,我们假设使用8位的二进制补码表示,相应的十进制数值从-128到127。一个8位的二进制补码数采取如下格式:a7a6a5a4a3a2a1a0其中的每个ai要么是0要么是1。1.检查最前面的a7。如果是0,该整数是正数,我们就可以直接计算其数值。如果是1,该整数是负数,我们必须首先取反加一。(当然,先取反再加一也一样)2.通过简单的计算:a6*26+a5*25+a4*24+a3*23+a2*22+a1*21+a0*20得到该数值。我们只需将那些系数为1的2的幂次简单相加,就可以得到该数值。3.最后,如果原数值是负数,我们在前面加一个负号前缀即可。例2.2将二进制补码整数11000111转换为十进制数值。1.既然最前面的一位是1,则该数值是负数。我们首先必须得到与其绝对值相同的正数的二进制表示,为00111001。2.该数值可以通过计算:0*26+1*25+1*24+1*23+0*22+0*21+1*20或32+16+8+1得到,为57。3.11000111对应的十进制数值是-57。(还有更简便的方法:11000111=-power(2,7)+75)2.4.2十进制到二进制的转换把一个数从十进制转换成二进制复杂一些。这种方法最重要的依据是:如果一个正的二进制数的最右端的数字为1,则这个数为奇数;否则为偶数。再来看一个通用