本节书摘来自华章出版社《计算机组成原理》一书中的第2章,第2.1节, 作 者 Computer Organization and Architecture: Themes and Variations[英]艾伦·克莱门茨(Alan Clements) 著,沈 立 王苏峰 肖晓强 译, 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
“我常说:当你能衡量你正在谈论的东西并能用数字加以表达时,你才真的对它有了几分了解;而当你还不能测量,也不能用数字表达时,你的了解就是肤浅的和不能令人满意的。尽管了解也许是认知的开始,但在思想上则很难说你已经进入了科学的阶段。”
——Lord Kelvin
“没有测量就没有控制。”
——Tom DeMarco,1982
“细节决定成败。”
——英语谚语
“可以有数据无操作,但不能有操作无数据。”
——佚名
“四舍五入的数字总是错的。”
——Samuel Johnson
前面已经介绍了计算机,并解释了它能够处理存放在存储器中的数据。本章将更详细地介绍计算机所处理的数据。在本书的第二部分,读者还将看到处理器是怎样执行指令的。
我们首先介绍信息的表示,因为它们对后面的大部分内容有重要意义。人们用来表示信息的方式——无论是数字数据、文本数据或多媒体数据(比如声音和视频),对计算机体系结构都有重要影响。数据表示决定了计算机所执行操作的类型,数据从一个位置传到另一个位置的方法,以及对存储元件的特性要求。
第2章将从数字表示和计算机运算开始。计算机简单地将数字和其他所有信息都表示为二进制形式,因为经济地设计和制造复杂的大规模二进制电路是比较容易的。读者将看到二进制整数和负数、小数值以及像π那样的无理数的表示等内容,还会看到加、减、乘、除等基本运算操作。
本章专门用一节介绍浮点运算,使读者能处理科学计算中1.3453×1023那样很大的数和很小的数。浮点运算是非常重要的,因为它的实现决定了计算机执行复杂图形变换和图像处理的速度,而且浮点运算对计算的准确度也有很重要的影响。在介绍了浮点数之后,我们还会简要介绍一些影响形如(p-q)(x+y)的链式计算以及超越函数和三角函数精度的因素。
人们所能描述的任何事物都可以被转换为二进制形式并由计算机处理。计算机中的数据可以表示各种信息,从本书中的纯文本到算术运算中的数字,到CD-ROM中的音乐或者到电影。计算机有着不同的用途,可以帮好莱坞艺术家制作出虚拟的人群场景,也可以帮另一些人计算按揭付款。然而在这些应用里,计算机都是对相同类型的数据(比特)进行同样的基本操作。
本章将介绍如何用1和0组成的串来表示信息,特别是数的表示以及数的处理方式。
我们将使用术语“计算机”,而不必担心会产生任何歧义。40年前或更早以前,人们必须在计算机前加上模拟或数字以区分那时的两种完全不同的计算机——模拟计算机和数字计算机。我们从以下几个问题开始介绍:为什么计算机是数字的,为什么计算机设计者最终选择了二进制系统,以及信息是如何在计算机内表示的。
计算机最初被设计为计算器,使人们能更容易地完成冗长乏味的算术运算。人们习惯用十进制表示数字,因此我们会解释为什么数据要从日常的十进制表示转换为能被计算机存储和处理的形式,我们还会说明计算机如何表示负数和正数。本章还会介绍计算机如何实现加减法,以及更复杂的乘除法操作。
必须强调的是,二进制数和二进制运算并不神奇。数字计算机和二进制运算的出现仅仅是因为相应的技术实现起来非常划算。计算机的算术运算与日常生活中的一样——如果你有7个苹果并且吃了1个,那么还剩6个。无论是用十进制运算、掰手指头数、拨弄算盘珠,还是用计算机,结果都一样。
计算机中,1992347119845、0.00000000000000000000342、1.234×109或-1.3428×10-12那样很大或很小的数都是通过浮点运算的机制来处理的。我们介绍了数字计算机是如何存储和处理这些数据的。不过,因为浮点数可能只是其真实值的近似,我们将讨论浮点运算的误差来源以及在进行混合运算时误差是如何传播的。我们还会讨论计算机实现平方根、sin和cos等数学函数的方法。
数据是各种各样的信息,如数字、文本、计算机程序、音乐、图像、符号、运动图像、DNA密码,等等。实际上,信息可以是能够被计算机存储和处理的任何事物。本节将介绍位(bit,或比特)的概念并说明如何用它表示信息。
计算机内存储和处理信息的最小单位是位(bit,或比特),它是BInary digiT(二进制数)这个词的缩写。一个比特的值可以是0或1,它是不可分的,因为不能再将它分为更小的信息单位。
数字计算机将信息以一组或一串比特(称作字)的形式保存在存储器中。例如,串01011110表示一个8位的字。按照习惯,我们以最低位在最右端的方式书写二进制串。
如果计算机像人一样以十进制的方式进行运算,解释计算机是如何工作的可能要容易一些,简单地以日常生活中的运算举例就可以了。但要制造这样一台计算机需要电路能够存储和处理10个十进制数0~9。目前人们还不能制造出价格便宜的、能够可靠地区分出十个不同电压等级的电路,只能制造出便宜的、能够区分我们称之为0和1的两个电压等级的电路。
计算机通常不会每次只对一个二进制位进行操作,它们会对一组二进制位进行操作。8个二进制位为一个字节(byte)。现在的微处理器都是面向字节的,其字长是8位的整数倍(即它们的数据和地址是8、16、32、64或128位)。一个字可以是2个、4个或8个字节长,因为它的所有位可以被分别组织为2个、4个或8个8位的组。
一般来讲,计算机能够同时处理的位数越多,它的速度就会越快。随着计算机的速度越来越快,价格越来越低,一台计算机一次能处理的位的组数也越来越多。20世纪70年代第一个微处理器一次只能处理4位数据,而到了20世纪90年代初,64位微机已开始进入个人电脑市场,一些显卡还能处理128位或256位宽的数据。
一些计算机制造商用术语“字”(word)表示16位的值(与字节对应,字节是8位的值),长字表示32位的值。还有一些制造商则用字表示32位的值,用半字表示16位的值。本教材一般用字表示一台计算机能处理的信息的基本单位。
前面已经提到过,一串二进制位可以表示任何数据。读者自然会问表示某个数据需要多少位。如果要将一天中的小时表示为24个不同值中的一个(即0~23),共需要多少位?如何指定这些数字对应的位模式?
图2-1描述了如何用1位、2位、3位和4位得到一个二进制的值序列。我们从图2-1最左边只有一位的情况开始,这时可以沿着两条路径中的一条前进——向上表示该位为0而向下表示该位为1。增加第2位将得到4条从起点开始到状态00,01,10和11的路径。增加第3位将得到抵达状态000,001,010,011,100,101,110和111的8条路径。最后,增加第4位将得到从状态0000到1111的16条路径。
每当数字增加1位时,路径的总数将翻一倍。4位得到16条路径,5位得到32条路径,依此类推。一个n位的字将得到2n条不同的路径或位模式。一个8位的字节将得到28=256个可能的值,一个16位的字将得到216=65 536个不同的值。为了用二进制数表示任何一个拥有最多n个值的量,应找到一个使不等式n≤2m成立的最小位数m。例如,要表示整数百分数(即n=0,…,100),m应为7,因为100≤27。但这里并没有指出如何最优地安排这m位对应的2m个位模式。从图2-1中可以看出这些位被放在了相同的位置上(2.2节将对其进行详细讨论)。我们很快将会看到数字值有几种不同的表示方式。
信息表示
一个n位的字可以表示2n个不同的位模式,图2-1描述了n=1,2,3和4的情况。那么一个n位的二进制字又可以表示什么呢?最简单的答案是什么也表示不了,因为一个由二进制1和0组成的串没有任何内在含义。怎样解释一个特定的二进制数只取决于程序员赋予它何种含义。在介绍二进制运算之前,我们简要介绍一下一般的二进制码而不考虑数字。以下是一些能够用字表示的对象。
指令 字长为32位或更长的计算机用一个字来表示CPU能够完成的操作(8位或16位计算机用多个字表示一条指令)。指令的二进制编码与其功能之间的关系由计算机设计者决定。例如,一台计算机上表示“A加B”的二进制序列可能与另一台计算机上的完全不同。
数量 一个字或多个字都可以用来表示数量。数可被表示为多种格式(如BCD整数、无符号二进制整数、有符号二进制整数、二进制浮点数、整数复数、浮点复数、双精度整数,等等)。字节10001001可能在一个系统中表示数值–119,在另一个系统中表示137,而在第三个系统中表示89。程序员必须按照数的类型对其进行操作,用数字8去乘字符串“John”的二进制表示也是完全可以的。
字符 字符是一个叫作“字母表”的集合中的元素。拉丁或罗马字母表中的字母、数字字符(A-Z,a-z,0-9)和*、-、+、!、?等符号都被分配了二进制值,因此可以在计算机内存储和处理。ISO 7位字符码或ASCII码(美国信息交换标准代码)是在计算机工业中应用得非常广泛的一种编码,它用7位表示一个字符,一共可以表示27=128个不同的字符。其中有96个字符是可打印字符。其余32个是不可打印的,用于完成回车、退格、换行等特殊功能。表2-1列出了每个ASCII码的值及其所代表的字符。因为计算机都是面向字节的,它通常可以通过在最高位前补0的方法将7位ASCII码转换为8位——我们也会使用这个方法。
为了将一个ASCII字符转换为对应的7位二进制码,应将该字符在ASCII码表的行号作为ASCII码的高3位,列号作为低4位。表2-1用二进制和十六进制(稍后将介绍十六进制数)两种形式对各行和列编号。例如,字母“Z”的ASCII表示为5A16或10110102。
十进制数字字符0,1,2,3,4,5,6,7,8和9对应的ASCII码分别是3016,3116,3216,3316,3416,3516,3616,3716,3816和3916。例如,数字字符4用ASCII码001101002表示,而数值4用000001002表示。当按下键盘上的“4”后,计算机得到的输入是00110100而不是00000100。当读入一个来自键盘的输入或将一个输出送往显示器时,都必须在数字字符的ASCII码和数字的值之间进行转换。在高级语言里,这个转换是自动完成的。
表2-1左边第三、四两列表示0000000~0011111之间的ASCII码对应的字符,其中没有字母、数字或符号。这两列字符都是不可打印的,要么用于控制打印机或显示设备,要么用于控制数据传输链路。ACK(应答)和SYN(同步空闲)等数据链路控制字符与通信系统有关,通信系统将要传送的字符和管理信息流所需的特殊字符混合在一起。这样的系统已不像之前那样受欢迎,数据链路通过其他机制进行控制。
7位的ASCII码一共可以编码128个字符,为了支持?、?和é等重音字符,它已被扩展为8位的ISO 8859-1拉丁编码。但因为这种编码不适用于世界上的许多语言,如汉语和日语,人们又设计了Unicode 16位编码,表示这些语言中的文字。Unicode的前256个字符被映射到ASCII字符集上,使得ASCII码与Unicode的转换非常容易。Java语言将Unicode作为其字符表示的标准方法。
有时需要对某种字符编码进行扩展或增强(例如,使之含有另一种语言中的文字符号)。增加每个字符的编码位数或者使用转义序列都可以扩展现有的字符编码。将7位的ISO/ASCII字符集扩展为8位,可以得到两个128个字符的字符集。如字符最高位为0,则其余7位代表128个标准ISO/ASCII字符中的一个。反之,若字符最高位为1,其余7位将表示128个新字符中的任意一个(比如非拉丁语字符或者甚至是图形符号)。
使用转义序列是另一种扩展字符集的方法,转义序列用一个特殊字符说明其后的字符(或字符串)的含义将按照与标准或缺省字符集中不同的方式进行解释。例如,ISO/ASCII的换码字符ESC就用来说明紧跟在其后的字符有新的含义。
图像、声音和视觉 数字计算机处理大量表示声音、静态图像和视频的数据。也许有人会说一幅照片的大小相当于1000个词,但在数字世界中并非如此。如果我们假设平均每个词中含有5个ASCII字符和1个空格,那么1000个词共有6000个字符或大约6KB。一台全画幅数码单反相机所拍摄的raw(未编码)格式照片,每张的大小约为30MB。此时,一张照片的大小是1000个词的5000倍。
组成照片的基本单位是像素(picture element),每个像素的大小可以是8位(单色)或24位(三基)。一张高分辨率照片中可能有超过4K×3K个像素。运动图像的情况更甚,因为视频将作为一串静态图像依次传输,每秒发送60次(60次/s)。实际情况并不像数据所反映的那样糟糕,因为无论是静态图像还是动态图像,都可以进行压缩以减少其数据量——压缩比可以达到10倍甚至更高。静态图像用JPEG算法压缩,动态图像则用MPEG算法压缩。在介绍处理多媒体应用的专用指令集时我们还会回到这一话题。如果要进行实时图像处理,需要很大的存储容量,很高的传输带宽,以及很强的处理能力。我们有理由认为高性能计算机技术在过去10年的发展受到了多媒体应用需求的驱动。下面举一个与多媒体有关的问题规模的例子,请考虑一架载客量为800人的空客A380的飞行娱乐系统的设计,理论上所有800名乘客都可以使用该系统在50余部影片内选择一部来观看——所有这些都是实时进行的。
声音的存储和处理曾是计算机设计者面临的巨大挑战,但与图像相比这已不再是一个严峻的问题,因为音频处理所需的计算、存储和传输带宽已经完全处于现代计算、存储和传输设备的能力范围内。奈奎斯特抽样定律(Nyquist ttheorem)指出,如果以至少两倍于音频流最高频率的速率对初始波形进行采样,就可以重新构造出声音信号。16位采样对除了最高质量音频外的其他所有音频都足够了。因此,以32K次/s的速率进行声音采样共需215×2字节/s=216字节/s,这在今天是毫无问题的。使用高级心理声学模型编码方法(见第5章)和MP3等算法对声音进行压缩,可以将其所需带宽降到最初的十分之一。
多媒体信号与大多数其他形式的数据的编码之间有一个重要的区别。通常数据必须进行精确编码,它的存储或处理都不会丢失信息。没人希望文本文件或信用卡账单中出现随机错误。这种编码被称为无损的,它表明无论进行多少次编码或解码,总可以得到同样的结果(通过将频繁出现的词或一组字母替换为短语来压缩文本文件的zip编码算法是一个很好的无损压缩的例子)。图像和声音编码(MPEG、JPEG和MP3)则采用有损编码,意味着编码会造成不可逆的质量损失。这种有损编码充分利用了“如果不能轻易地觉察到,人们就不会过于关注细节的损失”这一原理。