计算机数据存储格式是如何存储数据的,当数据计算有溢出时如何保证数据的正确性,要求具体

第1章 数据存储 - 异步社区
第1章 数据存储
在本章中,我们学习有关计算机中数据表示和数据存储的内容。我们要研究的数据类型包括文本、数值、图像、音频和视频。除了传统计算外,本章的很多内容还涉及数字摄影、音频/视频录制和复制,以及远程通信等领域。
1.1 位和位存储
1.2 主存储器
1.3 海量存储器
1.4 用位模式表示信息
*1.5 二进制系统
*1.6 整数的存储
*1.7 小数的存储
*1.8 数据与程序设计
*1.9 数据压缩
*1.10 通信差错
在计算机科学中,我们首先要学习的是,信息是如何编码并存储在计算机中的。我们第一步要讨论的是计算机数据存储设备的基础知识,然后研究如何对信息进行编码并将其存储到这些系统内。最后我们将探讨现今数据存储系统的各个分支,以及如何用数据压缩和错误处理这样的技术来克服它们的不足。
1.1 位和位存储
在现今的计算机中,信息是以0和1的模式编码的,这些数字称为位(bit,是binary digit的简写,意思是)。尽管你可能倾向于把它们与数值联系在一起,但它们的确只是些符号,其意义取决于正在处理的应用:它们有时表示数值,有时表示字母表里的字符和标点符号,有时表示图像,还有时表示声音。
1.1.1 布尔运算
为了理解单个的位在计算机中是如何存储和操作的,这里我们假设位0表示(false),位1表示(ture)。为了纪念数学家乔治·布尔(George Boole,),处理真/假值的运算被称为布尔运算(Boolean operation)。乔治·布尔是逻辑数学领域的先驱。3个基本的布尔运算是AND(与)、OR(或)和XOR(异或),图1-1概述了这三种运算。(我们之所以用大写字母来表示这些逻辑运算符的名字,是为了与它们对应的英语单词区分开来。)这些运算类似于算术运算的乘法和加法,因为它们都是结合一对值(运算输入),得出第三个值(运算输出)。不过,与算术运算不同的是,布尔运算结合的是真/假值,而不是数值。
布尔运算AND可用于计算,连接词AND和两个较小或较简单的语句组成的一条语句的真/假值。这种语句的一般形式如下:
其中,P代表一个语句,Q代表另外一个语句。例如:
AND运算的输入是复合语句分句的真/假值,输出则是复合语句本身的真/假值。因为P AND Q语句的值只有在其两个分句都是真时才为真,所以可以得出结论:1 AND 1的输出应该是1,而其他所有情况的输出值都应该是0,如图1-1所示。
图1-1 布尔运算AND、OR和XOR(异或)的输入值和输出值
同理,OR运算是建立在如下形式的复合语句的基础之上的:
其中,同样,P代表一个语句,Q代表另外一个语句。当其中至少有一个分句为真时,语句才为真,如图1-1所示。
英语中没有可以单独表示XOR运算含义的连词。当两个输入一个为1(真),另一个为0(假)时,XOR运算的输出为1(真)。例如,P XOR Q语句的意思是“或者是P,或者是Q,但不会是两个共存。”(简言之,当两个输入不同时,XOR运算的输出为1。)
NOT(非)运算是另一个布尔运算。它与AND、OR和XOR不同,因为它只有一个输入。它的输出值与输入值是相反的;如果NOT运算的输入值为真,那么它的输出值就为假,反之亦然。因此,如果NOT运算的输入是下面语句的真/假值:
Fozzie is a bear.
那么,其输出就是下面语句的真/假值:
Fozzie is not a bear.
1.1.2 门和触发器
门(gate)指的是一种设备,给定一种布尔运算的输入值,门可以得出该布尔运算的输出值。门可以通过很多种技术制造出来,如齿轮、继电器和光学设备。现在的计算机中,门通常是由小型电子电路实现的,其中数字0和1由电压电平表示。不过,我们不需要关注这些细节问题。对于我们来说,知道如何用门的符号形式来表示门就足够了,如图1-2所示。注意,与门、或门、异或门和非门,是由不同形状的符号表示的,输入值在一边,输出值在另一边。
图1-2 与门、或门、异或门和非门的图形表示及其输入输出值
门为构造计算机提供了构件。计算机构造的一个重要步骤如图1-3的电路所示,这个电路是触发器(flip-flop)电路的一个特例。触发器是计算机存储器的基本部件,它是一个可以产生0或1输出值的电路,它的值会一直保持不变,直到有另一个电路过来的临时脉冲(临时变成1之后再变为0)使其变换成其他值。换句话说,通过设置,可以让输出在外界刺激的控制下“记住”0或者1。例如,在图1-3中,只要电路的两个输入值一直都是0,输出值(无论是0还是1)就不会变。不过,在它的上输入端临时放置一个1,会强制其输出值为1;反之,在它的下输入端临时放置一个1,会强制其输出值为0。
图1-3 一个简单的触发器电路
我们来仔细研究一下这个问题。在我们不知道图1-3中电路的当前输出值的情况下,假设上面的输入值变为1,而下面的输入值仍为0(见图1-4a),那么不管或门的另外一个输入值是什么,它的输出值都将为1。接着,因为与门的另外一个输入值已经为1(触发器下输入端为0时非门的输出值),所以它的两个输入值都为1。于是,与门的输出值变成1,这意味着,现在或门的第二次输入值将为1(见图1-4b)。这样就可以确保即使触发器上面的输入值变回0(见图1-4c),或门的输出值也会保持为1。总之,触发器的输出值已经为1,上面的输入值变回0后,其输出值仍然保持不变。
图1-4 将一个触发器的输出值设置为1
同理,在下输入端上临时放置数值1,会强制触发器的输出值为0,而且输入值变回0后,输出值仍然保持不变。
我们介绍图1-3和图1-4中的触发器电路的目的有3个。第一,展示设备是如何通过门制造出来的,这是一个数字电路的设计过程,是计算机工程领域的一个重要课题。事实上,在计算机工程中,触发器只是诸多基础工具电路中的一种。
第二,通过触发器的概念为抽象和抽象工具的使用提供一个例子。事实上,还有很多其他的构建触发器的方法。其中一种方法如图1-5所示。如果你用这个电路做实验就会发现,尽管它有着不同的内部结构,但它的外部特性与图1-3中的是一样的。计算机工程师不必知晓触发器中实际使用的是哪种电路,只需理解触发器的外部特性并将其作为一个抽象工具来使用即可。一个触发器和其他定义良好的电路一起形成了一个构件集合,工程师可以直接利用这个构件集合构造更复杂的电路。因此,计算机电路的设计就会呈现一种层次结构,其中每一层都将较低层次的构件作为抽象工具使用。
图1-5 构建触发器的另一种方法
第三,触发器是现代计算机中存储二进制位的一种方法。更确切地说,可以将触发器的输出值设置为0或1。其他电路可以通过向触发器的输入端发送脉冲来调整这个值,还有一些电路可以通过将这个触发器的输出用作它们的输入来响应存储的值。因此,许多触发器(被构造成非常小的电子电路)可以用作计算机内部记录信息的一种方法,将信息编码成0和1的模式。实际上,众所周知的超大规模集成(very large-scale integration,VLSI)技术支持将数百万个电子元件构造在一个称为芯片(chip)的晶片上,从而创建出包含数百万个触发器及其控制电路的微型设备。因此,这些芯片被用作构建计算机系统的抽象工具。事实上,在某些情况下,还可以用超大规模集成技术在单块芯片上创建整个计算机系统。
1.1.3 十六进制记数法
当我们考虑计算机的内部活动时,必须考虑位模式(也叫位串)的处理问题,有些位串相当长,长位串常被称为流(stream)。不幸的是,人脑很难理解流。即便只是抄录位模式也会令人厌烦,而且容易出错。因此,为了简化这种位模式的表示方法,我们常使用一种称为十六进制记数法(hexadecimal notation)的简写符号,它是利用机器位模式的长度为4的倍数这样一个事实制定的。具体来说就是,十六进制记数法用一个符号表示位模式的4位。例如,一个12位的串只需要3个十六进制符号就可以表示。
图1-6展示了十六进制编码系统。左边一列是所有长度为4的位模式,右边一列是十六进制记数法使用的符号,表示左边那列位模式。使用这个系统,位模式可以表示为B5。具体方法是,把位模式拆分为长度为4的子串,然后用十六进制的符号代替每一个子串,即用B来表示1011,用5来表示0101。同理,16位模式1000可以简化成更易为人接受的形式A4C8。
图1-6 十六进制编码系统
第2章将广泛使用十六进制记数法,由此你就能体会到它的效率。
问题与练习
1.什么样的位模式输入可以使得下面的电路输出值为1?
2.对于图1-3中的触发器,我们在文中强调,下输入端放置1(同时保持上输入端为0),会迫使触发器的输出为0。描述一下这种情况触发器内部的活动序列。
3.假定图1-5中的触发器的输入都以0开始,描述一下当上输入端被临时设为1时所发生的活动序列。
4.a.如果一个与门的输出值传递给了一个非门,那么这个组合电路计算的布尔运算称为与非(NAND),当且仅当两个输入值都为1时,输出值为0。与非门的符号和与门的符号类似,只是输出有一个圆圈。下面的电路包含一个与非门。这个电路完成的是什么布尔运算?
b.如果一个或门的输出值传递给了一个非门,那么这个组合电路计算的布尔运算称为或非(NOR),当且仅当两个输入值都为0时,输出值为1。或非门的符号和或门的符号类似,只是输出有一个圆圈。下面的电路包含一个与门和两个或非门。这个电路完成的是什么布尔运算?
5.用十六进制记数法来表示下面的位模式。
  a. 0010  b.   c.
6.下面的十六进制模式表示的是什么位模式?
  a. 5FD97      b. 610A      c. ABCD      d. 0100
1.2 主存储器
为了存储数据,计算机包含大量的电路(如触发器),每一个电路能够存储一个位。这种位存储器被称为计算机的主存储器(main memory)。
1.2.1 存储器结构
计算机的主存储器是由称为存储单元(cell)的可管理单位组成的,一个典型存储单元的容量是8位。因为一个8位的串称为一个字节(byte),所以一个典型存储单元的容量是一个字节。像微波炉这样的家用电器中嵌入的小型计算机的主存储器,可能仅仅包含几百个存储单元,但是大型计算机的主存储器可能有几十亿个存储单元。
虽然计算机中没有左或右的概念,但是我们通常假设存储单元的位是排成一行的。该行的左端称为高位端(high-order end),右端称为低位端(low-order end)。高位端的最左一位称作高位或最高有效位(most significant bit)。取这个名称是因为,如果把存储单元里的内容解释为数值,那么这一位就是该数的最高有效数字。类似地,低位端的最右一位称为低位或最低有效位(least significant bit)。于是,我们可以如图1-7所示的那样描述字节型存储单元的内容。
图1-7 字节型存储单元的结构
为了区分计算机主存储器中的各存储单元,每一个存储单元都被赋予了一个唯一的“名字”,称为地址(address)。这类似于通过地址找到城市里的一座座房屋。不过,存储单元中的地址都是用数字表示的。更精确地说,我们把所有的存储单元都看作是排成一行的,并按照这个顺序从0开始编号。这样的编址系统不仅为我们提供了唯一标识每个存储单元的方法,而且也给存储单元赋予了顺序的概念(见图1-8),这样就有了诸如“下一个单元”“前一个单元”的说法。
图1-8 按地址排列的存储单元
将主存储器中的存储单元和每个存储单元中的位都进行排序,会产生一个重要的结果:计算机主存储器的所有二进制位会实际排成一长行。这个长行上的片段可以存储的位模式因此比单个存储单元要长。特别是,我们只需要两个连续的存储单元就可以存储16位的串。
为了做成一台计算机的主存储器,实际存放二进制位的电路还组合了其他的电路,这个电路使得其他电路可以在存储单元中存入和取出数据。以这种方式,其他电路可以通过电信号请求从存储器中得到指定地址的内容(称为读操作),或者通过请求把某个位模式存放到指定地址的存储单元里(称为写操作)。
因为计算机的主存储器是由独立的、可编址的存储单元组成的,所以可以根据需要独立访问这些存储单元。为了反映用任何顺序访问存储单元的能力,计算机的主存储器常被称为随机存取存储器(random access memory,RAM)。主存储器的这种随机存取特性,与1.3节中将要讨论的海量存储系统,形成了鲜明的对比,在海量存储系统中,长位串被当作合并块来操控。
尽管我们介绍说,触发器是存储二进制位的一种方法,但是在现代的大多数计算机中,RAM都是用其他类似的更复杂的技术制造的,这些技术可以让RAM高度小型化、响应时间更短。其中许多技术将位存储为可快速消散的电荷。因此,这些设备需要附加电路(称为刷新电路),在1秒内反复补充电荷很多次。因为它的这种不稳定性,通过这种技术构造的计算机存储器常被称为动态存储器(dynamic memory),于是就产生了术语DRAM(读作“DEE-ram”),用来表示动态RAM(dynamic RAM)。有时动态存储器会用术语SDRAM(读作“ES-DEE-ram”)来表示同步DRAM(synchronous DRAM),采用这种附加技术可以缩短从存储单元取出信息所需要的时间。
1.2.2 存储器容量的度量
在第2章我们会学到,如果主存储器中存储单元的总数是2的幂,那么主存储器设计起来会很方便。因此,早期计算机存储器的大小通常以1024(即210)个存储单元为单位来度量。因为1024接近于数值1000,所以计算机行业的许多人采用前缀(kilo)来表示这个单位。也就是说,术语(kilobyte,符号表示为KB)被用于表示1024字节。因此,带有4096个存储单元的机器会被说成是有一个4KB的存储器(24)。随着存储器容量的增大,这个术语逐渐扩大到了兆字节(megabyte,符号表示为MB)、吉字节(gigabyte,符号表示为GB)和太字节(terabyte,符号表示为TB)。遗憾的是,这种前缀(kilo-)(mega-)等的用法属于术语的误用,因为这些前缀已经是其他领域用于指称1000的幂。例如,在度量距离时,(kilometer)指的是1000米,在度量无线电频率时,(megahertz指的是1 000 000赫兹。在20世纪90年代后期,国际标准组织为2的幂制定了专门的术语:字节(kibi-byte)、字节(mebi-byte)、字节(gibi-byte)和字节(tebi-byte),用来表示1024的幂,而不是1000的幂。然而,尽管这种区别在世界上许多地方的当地法律里都有规定,但一直以来,普通大众和许多计算机科学家都不愿意放弃这个已经比较熟悉但会引起歧义的“兆字节”(megabyte)。因此,提醒大家:一般说来,、等术语在涉及计算机度量时表示2的幂,但在其他环境中表示1000的幂。
问题与练习
1.如果地址为5的存储单元存有值8,那么“将值5写入6号存储单元”和“将5号存储单元的内容移到6号存储单元”之间有什么差别?
2.假定你想交换存储在2号和3号存储单元中的值。那么下面的步骤错在哪里?
  1:把2号存储单元中的内容移到3号存储单元。
  2:把3号存储单元中的内容移到2号存储单元。
请设计能够正确交换这两个存储单元内容的步骤。如有必要,可以使用额外的存储单元。
3.一台带有4 KB存储器的计算机,其存储器里有多少个二进制位?
1.3 海量存储器
由于计算机主存储器的不稳定性和容量的限制,大多数计算机都有称为海量存储(mass storage,或者二级存储)系统的附加存储设备,包括磁盘、CD盘、DVD盘、磁带、闪存驱动器和固态硬盘(所有这些我们稍后会讨论)。相对于主存储器,海量存储系统的优点是更稳定、容量大、价格低,并且在许多情况下,能从机器上取下存储媒介进行存档。
磁性和光学海量磁存储系统和海量光存储系统的主要不足是,它们一般都需要机械运动。因为主存储器的所有工作都是由电子器件实现的,所以比起计算机主存储器来,海量存储系统的数据存取需要花费更长的时间。此外,与固态系统相比,带有移动部件的存储系统更容易出现机械故障。
1.3.1 磁系统
很多年以来,磁技术已经占据了海量存储领域。最常见的例子便是我们现在使用的磁盘(magnetic disk)或者硬盘驱动器(hard disk drive,HDD),它里面是可以旋转的薄盘片,表面有用以存储数据的磁介质涂层(图1-9)。读/写磁头安装在盘片的上面和/或下面,当盘片旋转时,每个磁头在盘片上面或下面相对于称为磁道(track)的圆圈转动。通过重定位读/写磁头,可以对各个同心的磁道进行存取。在很多情况下,一个磁盘存储系统包含若干个盘片,这些盘片安装在同一根轴上,层叠在一起,盘片之间有足够读/写磁头滑动的空间。在这种情况下,所有读/写磁头是一起移动的。每当读/写磁头重定位时,都可以访问一组新磁道,这组磁道称为柱面(cylinder)。
图1-9 磁盘存储系统
因为一个磁道可以包含的信息通常比我们每一次想要处理的多,所以每个磁道又被划分成若干个称为扇区(sector)的小弧区,这些扇区上记录的信息是连续的二进制位串。磁盘上所有的扇区都包含相同数目的二进制位(典型的容量是512个字节到若干KB),而且在最简单的磁盘存储系统里,每一个磁道包含的扇区数都相同。因此,靠近盘片外边缘的磁道扇区上的位的存储密度,要比靠近盘片中心的磁道上的小,因为外磁道比内磁道长。相反,在大容量磁盘存储系统里,靠近外边缘的磁道包含的扇区要远多于靠近中心的磁道,这种存储能力常通过一种称作区位记录(zoned-bit recording,ZBR)的技术得以应用。在使用区位记录技术时,一些相邻的磁道会被统称为区,一个典型的盘片大约包含10个区。一个区的所有磁道有相同数目的扇区,但是靠外的区中每一个磁道包含的扇区数,比靠内的区中每一个磁道包含的扇区数多。采用这种方式,能够有效利用整个磁盘的表面。不管细节如何,一个磁盘存储系统都包含许多独立的扇区,每一个扇区都可以作为独立的位串进行存取。
一个磁盘存储系统的容量取决于使用的盘片数目以及磁道与扇区的划分密度。容量较小的系统可能只有一个盘片。大容量磁盘系统的容量可达数GB,甚至TB,同一根轴上可能装有3到6个盘片。此外,数据有可能存储在每个盘片的上下两面。
有4个标准可以用来评估一个磁盘系统的性能:(1)寻道时间(seek time),读/写磁头从一个磁道移到另一个磁道所需要的时间;(2)旋转延迟(rotation delay)或等待时间(latency time),盘片旋转一周所需时间的一半,也就是读/写磁头到达所要求磁道后,等待盘片旋转使读/写磁头位于所要存取的数据(扇区)上所需要的平均时间;(3)存取时间(access time),即寻道时间和旋转延迟之和;(4)传输速率(transfer rate),在磁盘上读出或写入数据的速率。需要注意的是,在区位记录存储情况下,盘片旋转一次,外区磁道通过读/写磁头传递的数据量,要多于内区磁道,因此,数据传输速率会随所使用的盘片部位的不同而有所变化。
限制磁盘存取时间和传输速率的一个因素是磁盘系统旋转的速度。为了支持高速旋转,这些系统里的读/写磁头并不接触盘片,而只是“悬浮”在盘片表面。磁头与盘片之间的空间很小,以至于一粒小小的灰尘都可能卡在其中,导致磁盘和磁头损坏,这一现象便是磁头划伤(head crash)。因此,磁盘系统出厂时都密封在箱子里。凭借这样的构造,磁盘系统能够以每秒几百次的速度旋转,达到每秒数以MB的传输速率。
因为磁盘系统的操作需要物理运动,所以难以与电子电路的速度相比。电子电路的延迟时间是以纳秒(十亿分之一秒)甚至更小的时间单位计算的,而磁盘系统的寻道时间、等待时间和存取时间是以毫秒(千分之一秒)度量的。因此,与电子电路等待结果的时间相比,从磁盘系统检索信息所需要的时间非常长。
磁存储技术现在很少使用了,包括磁带(magnetic tape)和软盘驱动器(floppy disk drive)。磁带是将信息记录在一条绕在卷轴上的薄塑料带的磁涂层上,软盘驱动器则是将带有磁涂层的单个盘片封在一个为了便于从驱动器中取出而设计的便携式的盒子里。磁带驱动器的寻道时间极长,它和它的兄弟——录音带——一样,都要花费很长的倒带时间和快进时间。不过,低成本和大数据容量,使磁带非常适用于那些数据主要被线性读或写的应用,如存档数据备份。虽然软盘盘片的可移动特性是以比硬盘盘片低得多的数据密度和存取速度为代价的,但是,在更大容量、更耐用的闪存驱动器诞生之前的几十年里,软盘盘片的便携性还是极其有价值的。
1.3.2 光系统
另一类海量存储器应用的是光技术。光盘(compact disk,CD)就是其中的一种。光盘的直径为12厘米(大约5英寸),由涂着光洁保护层的反射材料制成。光盘上的信息是通过在反射层上创建偏差的方法记录的,可以通过激光检测CD快速旋转时反射层的不规则反射偏差来读取。
CD技术最初是用于音频记录的,采用的记录格式叫作数字音频光盘(compact disk-digital audio,CD-DA),而现在的CD,作为计算机的数据存储设备,实质上使用的仍是同样的格式。特别值得一提的是,CD上的信息存储在一条磁道上,它呈螺旋形缠绕在CD上,很像老式留声机唱片里的凹槽,不过与老式留声机唱片不同的是,CD上的磁道是由内至外的(见图1-10)。这条磁道被划分为称为扇区的单元,每个扇区都有自己的标识,数据存储容量为2 KB,这个容量在录制音频时,大约能录制秒的音乐。
图1-10 CD存储格式
需要注意的是,盘片外边缘螺旋形磁道的距离比内部螺旋形磁道的要长。为了使CD的存储能力达到最大,信息是按照统一的线性密度存储在整个螺旋形磁道上的,这就意味着,在螺旋形磁道上,外部边缘环道存放的信息比内部环道的多。所以,如果盘片旋转一整圈,激光在扫描外部螺旋形磁道时,读到的扇区个数要比扫描内部时读到的多。因此,为了获得统一的数据传输速率,CD-DA播放器要能够根据激光在盘片上的位置来调整盘片的旋转速度。不过,由于用于计算机数据存储的大多数CD系统,其盘片都是以比较快的恒定速度旋转的,所以其CD驱动器必须适应数据传输速率的变化。
由于采用这种设计思想,CD存储系统在处理长且连续的数据串(如音乐复制)时,表现最好。但是,当一个应用需要随机存取数据项时,磁盘存储器所用的方法(单个的同心磁道被划分成独立存取的扇区)就优于CD所用的螺旋形方法。
传统CD的存储容量是600~700 MB。但是,数字多功能光碟(digital versatile disk,DVD)可具有多达几个GB的存储容量,它由多个半透明的层面构成,精确聚焦的激光可以识别其不同的层面。这种盘片能够存储冗长的多媒体信息,包括完整的电影。最后,蓝光技术(blu-ray technology)使用蓝色(而非红色)激光,能够极为精确地聚焦激光束。因此,蓝光光碟(blu-ray disk,BD)的容量是DVD的5倍多,其巨大的存储器容量能满足高清视频的需要。
1.3.3 闪存驱动器
基于磁技术或光技术的海量存储系统的一个普遍特征是,通过物理运动来存储和读取信息,例如,旋转磁盘、移动读/写磁头和扫描激光束。这就意味着,其数据的存储和读取速度比电子电路的要慢。闪存(flash memory)技术有克服这个缺点的潜力。在闪存系统里,二进制位是由电子信号直接发送到存储介质中的,电子信号使得该介质中二氧化硅的微小晶格截获电子,从而转换微电子电路的性质。因为这些微小晶格能够在没有外力的情况下保持截获的电子很多年,所以闪存技术非常适合存储可移植的、非易失性数据。
尽管存储在闪存系统里的数据能够像在RAM应用中一样,以小字节单元存取,但是现代技术规定存储的数据应批量擦除。不过,反复的擦除会逐渐损坏二氧化硅的晶格,这就意味着,现今的闪存技术不适合一般的主存储器应用,因为主存储器的内容一秒可能要改很多次。然而,在那些改变可以被控制在一个合理的水平的应用里(如数码相机和智能手机),闪存已经成为海量存储技术的一个选择。的确,因为闪存对物理震动不敏感(与磁系统和光系统不同),所以它现在正代替便携式应用(如笔记本电脑)中的其他海量存储技术。
闪存设备称为闪存驱动器(flash drive),容量可达几百GB,可用于一般的海量存储应用。闪存设备被封装在极小的塑料格子里,其一端有一个可以取下的帽,当驱动器处于脱机状态时,可以保护这个设备的电子连接器。因为这些便携设备容量大,很容易与计算机进行连接或断开,所以是理想的便携数据存储器。不过,由于它们的微小存储晶格的缺点,当涉及真正长期应用时,它们不如光学盘片可靠。
较大的闪存驱动器称为固态硬盘(solid-state disk,SSD),是专为替代磁硬盘设计的。与硬盘相比,固态硬盘防震抗摔性能更好,能更安静地运行(因为没有移动部件),存取时间更短。不过,固态硬盘要比同等大小的硬盘贵,所以在购买计算机时,它仍然被看作是高端选择。虽然固态硬盘扇区也受所有闪存技术所共有的寿命比较有限的影响,但通过耗损均衡(wear-leveling)技术频繁地将改变的数据块重定位到驱动器中使用次数最少的位置上,可以减小这个影响。
闪存技术的另一应用是安全数字(secure digital,SD)存储卡(memory card),简称SD卡。SD卡的容量高达2 GB,它们被制成塑料封装的晶圆,有邮票大小(还有更小的小型和微型SD卡),其中,安全数字高容量(secure digital high capacity,SDHC)存储卡(简称SDHC卡)的存储容量可以高达32 GB,新一代的安全数字扩展容量(secure digital extended capacity,SDXC)存储卡(简称SDXC卡)的容量可超过1 TB。由于这些卡物理结构紧凑,可以方便地插入小型电子设备的插槽,因此它们是数码相机、智能手机、音乐播放器、汽车导航系统,以及其他许多电子产品的理想选择。
问题与练习
1.我们可以从加快磁盘或CD转速中获得什么?
2.当记录数据到多盘片存储系统时,我们是应该写满一张盘片后再写另一张盘片,还是应该写满一个柱面后再写另一个柱面?
3.为什么预订系统里的那些需要经常更新的数据,要存储在磁盘里,而不是CD或DVD里?
4.哪些因素能使同一个驱动器能读包含CD、DVD以及蓝光光碟在内的所有光碟?
5.相对于本节介绍的其他海量存储系统,闪存驱动器有什么优势?
6.让磁硬盘驱动器仍具竞争力的优势是什么?
1.4 用位模式表示信息
在研究了位存储的技术后,现在来了解如何将信息编码为位模式。我们将集中学习一些流行的文本编码方法、数字数据编码方法、图像编码方法以及声音编码方法。其中每一个编码系统都可能会影响到典型的计算机用户。我们的目标是充分了解这些技术,以便知道应用这些技术的效果。
1.4.1 文本的表示
文本形式的信息通常由一种代码表示,其中文本中的每一个不同的符号(如英文字母和标点符号)均被赋予唯一的位模式。这样,文本就表示为一个长的位串,位串中的连续位模式表示的是原文本中的连续符号。
在20世纪的40年代和50年代,人们设计了许多这样的代码,并结合不同的设备使用,随之增加了不少通信问题。为了缓解这种情况,美国国家标准化学会(American National Standards Institute,ANSI,读作“AN–see”)采用了美国信息交换标准码(American Standard Code for Information Interchange,ASCII)。这种代码使用长度为7的位模式来表示大小写英文字母、标点符号、数字0~9以及某些控制信息(如换行、回车和制表符)。后来,ASCII码通过在每个7位位模式的最高端添加一个0,扩展为8位位模式。这个技术不仅使所产生的代码的位模式与字节型存储单元相匹配,而且还提供了128个附加位模式(通过给附加的位赋予数值1),可以表示除英语字母和关联的标点符号之外的符号。
美国国家标准化学会(ANSI)成立于1918年,是由一些工程师协会和政府机构联合创办的非赢利性联盟,致力于协调私人部门自发标准的发展。现在,ANSI成员中有1300多个企业、专业组织、行业协会和政府机构。ANSI的总部设在纽约,它是美国在ISO的代表。它的网站是http://www.ansi.org。
其他国家的类似组织包括澳大利亚标准组织(Standards Australia)、加拿大标准委员会(Standards Council of Canada)、中国国家质量技术监督局(China State Bureau of Quality and Technical Supervision)、德国标准化学会(Deutsches Institut für Normung)、日本工业标准调查会(Japanese Industrial Standards Committee)、墨西哥标准指导委员会(Dirección General de Normas)、俄罗斯联邦国家标准和度量委员会(State Committee of the Russian Federation for Standardization and Metrology)、瑞士标准化协会(Swiss Association for Standardization)和英国标准学会(British Standards Institution)。
8位位模式的一部分ASCII码可见附录A。利用这个附录,我们可以将位模式
解码为报文“Hello.”,如图1-11所示。
图1-11 报文“Hello.”的ASCII或UTF-8编码
国际标准化组织(International Organization for Standardization)简称ISO,这个简称来源于希腊语中的“isos”一词,意思是平等。ISO开发了大量的ASCII扩展,每种扩展都是针对某一主要语种设计的。例如,其中一个标准提供了表达大部分西欧语言文本所需的符号。在其128个附加模式中,有表示英磅和德语元音?、?、ü的符号。
国际标准化组织(常称为ISO)建立于1947年,是一个由各国标准化团体组成的世界范围的联合会。现如今,它的总部设在瑞士日内瓦,有100多个成员团体和许多通信成员。(一个通信成员通常是一个国家的标准化团体,这个国家还没有国家认可的标准化团体。这些成员不能直接参与标准的开发,但可以了解ISO的活动。)ISO的网站是http://www.iso.org。
ISO扩展的ASCII标准在支持全世界多语言通信方面取得了巨大进展,但是仍有两个主要障碍。首先,扩展的ASCII中额外可用的位模式数不足以容纳许多亚洲语言和一些东欧语言的字母表。其次,因为一个特定文档只能使用一个选定标准中的符号,所以无法支持包含不同语种的语言文本的文档。实践证明,这两者都会严重妨碍其国际化使用。为弥补这一不足,Unicode在一些主要软硬件厂商的合作下诞生了,并迅速赢得了计算界的支持。这种代码采用唯一的21位模式来表示每一个符号。当Unicode字符集与Unicode转换格式8位(Unicode transformation format 8-bit,UTF-8)编码标准结合在一起时,原来的ASCII字符仍然可以用8位来表示,而像汉语、日语和希伯来语这样的语言所产生的数以千计的其他字符则可以用16位来表示。除了可以表示世界上所有常用语言所需的字符以外,UTF-8的24位或32位模式还可以表示比较鲜为人知的Unicode符号,为未来的扩展留出了充足的空间。
由一长串根据ASCII或Unicode编码的符号组成的文件常称为文本文件(text file)。区分下面两类文件很重要:一类是由称为文本编辑器(text editor,常简称为编辑器)的实用程序操作的简单文本文件;一类是由字处理程序(word processor),如微软的Word,产生的较复杂的文件。两者都是由文本材料组成的,但是,文本文件只包含文本中各个字符的编码,而由字处理程序产生的文件还包含许多表示字体变化、对齐信息和其他参数的专有代码。
1.4.2 数值的表示
当所记录的信息只有数值时,以字符编码的形式存储信息效率就会很低。为了了解其中的原因,让我们来看看数值25的存储问题。如果我们坚持用ASCII编码符号来存储,每个符号一个字节,那么总共需要16个二进制位。此外,用16个二进制位可以存储的最大数是99。不过,我们马上就可以看到,使用二进制记数法(binary notation),16个二进制位可以存储0~65535范围内的任何一个整数。因此,二进制记数法(或它的变体)被广泛应用于计算机存储器中数值数据的编码。
二进制记数法是一种数值表示方法,只使用数字0和1,区别于传统的使用数字0、1、2、3、4、5、6、7、8和9的十进制记数系统。我们将在1.5节中更详细地学习二进制系统,现在只需要初步了解该系统。我们来考虑一种老式的汽车里程表,它的显示轮只包含数字0和1,而不是传统的十进制数字0~9。里程表以全0读数开始,在汽车行驶的前几英里,最右方的滚动显示轮从0旋转至1;当这个1旋转回0时,会使其左边出现一个1,产生模式10;接着,右边的0旋转至1,产生模式11;现在,最右边的数从1旋转回0,使得它左边的1也旋转回0,这就使另一个1出现在第3位上,产生模式100。简言之,在我们驾驶汽车时,将看到下列顺序的里程表读数:
这个序列包括了整数0~8的二进制表示。尽管这种计数技术有些冗长乏味,但是我们可以扩展它,发现16个1组成的位模式是可以表示数值65 535的,这就证实了我们的说法:0~65 535范围内的任何整数都可以利用16个二进制位进行编码。
由于它的高效性,数字信息通常使用某种形式的二进制记数法来存储,而不用编码符号。我们之所以说“某种形式的二进制记数法”,是因为上面描述的简单二进制系统只是机器里应用的若干数值存储技术的基础。本章后面会讨论一些二进制系统的变体。现在我们只需要知道,称为二进制补码(two’s complement)记数法(见1.6节)的系统通常用于存储整数,因为它提供了一种便利的表示负数和正数的方法。为了表示或这样带有分数部分的数,我们还要使用一种称为浮点记数法(floating-point notation)的技术(见1.7节)。
1.4.3 图像的表示
图像的一种表示方法是:将图像解释为一组点,每一个点称为一个像素(pixel,是picture element的简写);然后,对每个像素的显示进行编码,整个图像就表示成了这些编码像素的集合,这个集合被称为位图(bit map)。这种方法很常用,因为许多显示设备(如打印机和显示器)都是基于像素概念操作的。因此,位图格式的图像更便于格式化显示。
位图中的像素编码方式随着应用的不同而不同。对于简单的黑白图像,每个像素由一个位表示,位的值取决于相对应像素是黑还是白。这是大多数传真机采用的方法。对于更加精致的黑白照片,每个像素由一组位(通常是8个)表示,这使得许多灰色阴影也可以表示出来。对于彩色图像,每个像素通过更为复杂的系统来编码。有两种方法很常用,其中一种是RGB编码,每个像素表示为3种颜色成分——红、绿、蓝,它们分别对应于光线的三原色。每一种颜色成分的强度一般是用一个字节来表示。因此,要表示原始图像中的一个单独像素,就需要3个字节的存储空间。
另一种替代简单RGB编码的方法是,使用一个“亮度”成分和两个颜色成分。在这种方法中,“亮度”成分被称为像素亮度,基本上就是红、绿、蓝三种颜色成分的总和。(事实上,它是像素中白光的数量,但是现在我们不需要考虑这些细节。)其他两种成分,称为蓝色色度和红色色度,分别由像素中的像素亮度与蓝或红光数量之间的差来决定。这3个成分合在一起就是再现这个像素所需要的全部信息。
利用亮度和色度成分进行图像编码这种方式的普及源自彩色电视广播领域,因为这种方法提供的彩色图像编码方式可以兼容老式黑白电视接收器。事实上,只需要利用编码彩色图像的亮度成分就可以制造出图像的灰度版本。
用位图表示图像的缺点在于,图像不能轻易调整到任意大小。基本上,放大图像的唯一途径就是变大像素,而这会使图像模糊。(这就是应用于数字照相机的“数字变焦”技术,与此相对的“光学变焦”是通过调整照相机镜头实现的。)
图像的另外一种表示方法,避免了这个缩放问题,将图像描述成了几何结构(如直线和曲线)的集合,这些几何结构可以用解析几何技术来编码。这种描述允许最终显示图像的设备决定几何结构的显示方式,而不是让设备再现特殊像素模式。这种方法被用在了当今的字处理系统中,用于产生可缩放的字体。例如,TrueType(由微软公司和苹果公司开发)是用几何结构描述文本符号的系统,而PostScript(由Adobe系统开发)提供了一种描述字符及更一般的图形数据的方法。这种表示图像的几何方法在计算机辅助设计(computer-aided design,CAD)系统中也很常见,用于在计算机屏幕上显示和操控三维物体的绘制。
许多绘图软件系统(如微软的画图工具)都允许用户用预先设定的形状(如矩形、椭圆形、基本线条等)画图,对于这些用户来说,用几何结构表示图像与用位图表示图像之间的区别是很明显的。用户只需从菜单中选择所需的几何形状,就可以用鼠标绘制出形状。在绘制过程中,软件保存了所画形状的几何描述。当鼠标给出方向后,内部的几何表示就被修改,再转化成位图形式显示出来。这种方法方便图像的缩放和形状的改变。然而,一旦绘制过程完成,系统就会去除基本的几何描述,仅保存位图,这意味着,再做其他修改需要经历冗长的一个像素接一个像素的修改过程。另外,一些绘图系统会将描述作为几何图形保存下来,并允许在之后进行修改。有了这些系统,就可以轻松地调整图形的大小,并可按各种尺寸显示清晰图像。
1.4.4 声音的表示
为了便于计算机存储和操作,对音频信息进行编码的最常用方法是,按有规律的时间间隔对声波的振幅采样,并记录所得到的数值序列。例如,序列0、1.5、2.0、1.5、2.0、3.0、4.0、3.0、0可以表示这样一种声波:它的振幅先增大,然后经短暂的减小,再回升至较高的幅度,接着又减回至0(见图1-12)。这种技术采用每秒8000次的采样频率,已经在远程语音电话通信中使用了许多年。通信一端的语音被编码为数字值,表示每秒8000次的声音振幅。接着,这些数字值通过通信线路被传输到接收端,用来重现声音。
图1-12 序列0、1.5、2.0、1.5、2.0、3.0、4.0、3.0、0所表示的声波
尽管每秒8000次的采样频率似乎是很快的速率,但它还是满足不了音乐录制的高保真需求。为了实现现在音乐CD重现声音的质量,我们需要采用每秒44 100次的采样频率。每次采样得到的数据要用16位的形式表示(32位用于立体声录制)。因此,录制成立体声的音乐,每一秒需要100多万个存储位。
乐器数字化接口(musical instrument digital interface,MIDI,读作“MID–ee”)是另外一种编码系统,被广泛用于电子键盘的音乐合成器、视频游戏声音,以及网站的辅助音效。MIDI是在合成器上编码产生音乐的指令,而不是对音乐本身进行编码,因此它避免了采样技术那样的大存储容量要求。更精确地说,MIDI是对什么乐器演奏什么音符以及持续时间进行编码。例如,单簧管演奏D音符2秒,可以编码为3个字节,而不必按照每秒44 100次的采样频率用两百多万个二进制位来编码。
简言之,可以把MIDI看作是对演奏者乐谱编码的一种方法,而不是对演奏本身编码。因此,MIDI“录制”的音乐在不同合成器上演奏时声音可能是截然不同的。
问题与练习
1.下面是用ASCII编码的一条消息,每个符号8位。它的含义是什么?(见附录A)
2.在ASCII码中,大写字母码和相应小写字母码之间的关系是什么?(见附录A)
3.用ASCII对下列语句编码:
  a. “Stop!” Cheryl shouted.
  b. Does 2+3=5?
4.描述一种在日常生活中能够呈现两种状态的设备,例如,旗杆上的旗帜,或者升起或者降下。给一种状态赋值1,另一种赋值0,请说明当以这样的位来存储时,字母b的ASCII码会怎样表示?
5.将下列二进制表示分别转化为相应的十进制形式。
  a. 0101     b. 1001
   c. 1011
  d. 0110     e. 10000   f. 10010
6.将下列的十进制表示分别转化为相应的二进制形式。
    b. 13
   c. 11       d. 18     e. 27       f. 4
7.如果每个数字采用每字节一个ASCII码的模式编码,那么3个字节可以表示的最大数字值是多少?如果采用二进制编码,那么又能够表示多大的数字值?
8.除十六进制记数法以外,另一种表示位模式的方法是点分十进制记数法(dotted decimal notation),其中每个字节由相对应的十进制数来表示,而且这些字节表示之间是用句点分开的。例如,12.5表示模式0101(12表示字节表示字节),而136.16.7表示模式。用点分十进制记数法表示下列位模式:
  a. 1111
  c. 0000
9.相对于位图技术,用几何结构表示图像有哪些优点?位图技术相对于几何结构又有哪些优点?
10.假如采用文中所讨论的每秒44 100次的采样频率,对1小时音乐的立体声录音进行编码。请问这段音乐编码的大小与CD的存储容量相比结果如何?
*1.5 二进制系统
在1.4节中我们看到,二进制记数法是表示数字值的一种方法,它只使用数字0和1,而不用较常见的十进制记数系统中的10个数字0到9。现在,我们来深入了解一下二进制记数法。
1.5.1 二进制记数法
回顾十进制系统,表示中的每一个位置都与一个量值相关联。在375这个表示中,5的位置与量1相关联,7的位置与量10相关联,3的位置与量100相关联(见图1-13a)。每一个量值是它右边量值的10倍。整个表达式代表的数值是,每一个数字值与其位置的量值相乘所得积之和。举例说明:模式375表示(3×100)+(7×10)+(5×1),用更加技术性的表示法表示就是(3×102)+(7×101)+(5×100)。
图1-13 十进制和二进制系统
在二进制记数法中,每个数字的位置也与一个量值相关联,只是与每个位置相关联的那个量值是它右边量值的两倍。更精确地说,在二进制表示中,最右边的数字与量值1(即20)相关联,其左边的下一个位置与量值2(即21)相关联,下一个与量值4(即22)相关联,再下一个与量值8(即23)相关联,依次类推。例如,在二进制表示1011中,最右边1的位置与量值1相关联,接下来一个1的位置与量值2相关联,0的位置与量值4相关联,最左边1的位置与量值8相关联(见图1-13b)。
为了求得二进制表示所表示的数值,我们可以采取和十进制相同的步骤:先求得每个数字值与其量值的积,再计算各个乘积之和。例如,100101表示的数值是37,如图1-14所示。需要注意的是,因为二进制记数法仅使用数字0和1,这种求积再求和的步骤就可以简化为求数字值为1的位置对应的量值的和。因此,二进制模式1011表示的是十进制数值11,因为3个1的位置分别与量值1、2和8相关联。
图1-14 二进制表示100101的解码
在1.4节中,我们学习了如何用二进制记数法计数,这就使得我们可以对小整数进行编码。为了求得大数值的二进制表示,你可能更倾向于图1-15所描述的算法。让我们利用这个算法来求十进制数值13的二进制表示(见图1-16)。首先,将13除以2,得到商数6和余数1。因为这个商不是0,步骤2告诉我们还要在商数(6)的基础上除以2,得到新的商数3和余数0。最新的商数仍然不为0,所以再除以2,得出商数1和余数1。再一次,将最新的商数(1)除以2,此时得到商数0和余数1。因为现在的商数是0,我们进入步骤3,从余数列中得到原数(13)的二进制表示1101。
图1-15 求正整数二进制表示的算法
图1-16 利用图1-15的算法求13的二进制表示
1.5.2 二进制加法
为了理解两个用二进制表示的整数的相加过程,首先让我们回顾一下用传统十进制记数法表示的数值的相加过程。例如,考虑下面的问题:
我们先把最右列的8和7相加,得到和15,我们把5记录在这一列的底部,进位1放到下一列中,得到:
现在我们把下一列的5和2相加,并加上进位到这一列的1,得到和8,我们把8记录在这一列的底部,得到:
总之,这个过程就是从右到左相加每一列中的数字,把和中的最低有效位写在列的底部,把和的较高有效位(如果有)进位到下一列。
为了将两个用二进制表示的正整数相加,我们遵照相同的过程,只是所有和的计算都使用图1-17中显示的加法规则,而不是你在小学所学的传统的十进制加法法则。例如,为了解决问题:
首先将最右边的0和1相加,得到1,写于该列下方。接着将下一列的1和1相加,得到10。把其中的0写于该列下方,将1记在了下一列的上面。这时,加法如下:
把下一列的1、0和0相加,得到1,将1写于该列下方。下一列的1和1总和为10,将0写于该列下方,将1记于下一列。这时,加法如下:
下一列的1、1和1总和为11(数值3的二进制表示),将低位1写于该列下方,并将另外一个1写在下一列的上面。把那个1与那列原本的1相加,得到10。再一次,在该列下方写下低位0,并将1写在下一列的上面。现在得到
下一列的唯一项就是1,是上一列进过来的,所以我们将其记录为答案。最终的结果是:
图1-17 二进制加法法则
1.5.3 二进制中的小数
为了扩展二进制记数法,使其包含小数数值,我们使用了小数点(radix point),其功能与十进制记数法中的十进制小数点是相同的,即小数点左边的数字代表数值的整数部分(整个部分),其解释和前面讨论的二进制系统一样。小数点右边的数字代表数值的小数部分,其解释和其他二进制位类似,只是它们的位置被赋予了小数的量值。更确切地说,小数点右边第一位的量值是$\frac{1}{2}$(即2-1),下一位的量值是$\frac{1}{4}$(即2-2),再下一位是$\frac{1}{8}$(即2-3),依次类推。需要注意的是,这仅仅是前面所述规则的延续:即每位所被赋予的量值是它右边大小的两倍。利用这些赋予二进制位位置的量值,对包含小数点和不包含小数点的二进制表示进行解码的步骤基本是相同的。更精确地说,我们是把表示中每一个位值与其对应位位置的量值相乘。举例来说,二进制表示101.101的解码为$5\frac{5}{8}$,如图1-18所示。
图1-18 二进制表示101.101的解码
另外,应用于十进制系统的加法技术同样适用于二进制系统,即两个有小数点的二进制表示相加时,我们只需要对齐小数点,然后应用前面介绍的加法步骤进行计算即可。例如,10.011加100.11得111.001,如下所示:
问题与练习
1.将下列二进制表示转换为相应的十进制形式。
a. 101010     b. 100001
    c. 10111
    d. 0110
    e. 11111
2.将下列十进制表示转换为相应的二进制形式。
    b. 64
      c. 96
    d. 15
    e. 27
3.将下列二进制表示转换为相应的十进制形式。
  b. 101.111
  c. 10.1
  d. 110.011    e. 0.101
4.用二进制记数法表示下列数值。
a.       b.      c.
    d.
    e.
5.按照二进制记数法做下列加法。
a.11011   b. 
    c.11111  d.  111.11
  + 1100    +   1.101     +  0001   + 
*1.6 整数的存储
数学家们长久以来就对数字记数系统很感兴趣,而且他们的许多想法已经被证明与数字电路的设计是相符的。本节,我们将研究其中两种记数系统:二进制补码记数法和余码记数法。它们都是计算设备用于表示整数的方法。虽然这些系统都是基于二进制系统的,但是它们增加了一些其他的特性,因而与计算机设计更加兼容。尽管它们有这么多的优点,但是它们也有缺点。我们的目标是了解这些特性以及它们是如何影响计算机用法的。
在21世纪之前,许多研究人员都在讨论数字技术和模拟技术的优缺点。在数字系统里,一个数值会被编码成一系列数字,存储在若干设备中,每个设备表示一个数字。在模拟系统里,每个数值都存储在一个单独的设备里,这个设备可以表示一个连续范围内的任何数值。
让我们用水桶当存储设备来比较这两种方法。为了模拟数字系统,我们让一个空桶表示数字0,一个满桶表示数字1,然后我们利用浮点记数法(见1.7节)将一个数字值存储在一排水桶中。相反,为了模拟模拟系统,我们用水桶水位表示数字值。乍一看,模拟系统看起来更精确,因为它不会有数字系统中的截断误差(见1.7节)。不过,在模拟系统中,水桶的任何移动都会使水位检测出错,而在数字系统中,则必须有剧烈的晃动才能区分出水桶是满的还是空的。因此,数字系统不像模拟系统对错误那样敏感。由于数字系统的这种健壮性,许多原来基于模拟技术的应用(如电话通信、音频录制和电视)都转而使用数字技术。
1.6.1 二进制补码记数法
现今计算机中最流行的整数表示系统是二进制补码(two’s complement)记数法。这个系统采用固定数目的二进制位来表示系统中的每一个数值。现今设备中普遍采用二进制补码系统,其中每个数值用一个32位的模式表示。这种大系统能表示很大范围的数字,但不方便演示。因此,在学习二进制补码系统的特性时,我们将着重介绍较小的系统。
图1-19列出了两种完整的二进制补码系统,一种基于长度为3的位模式,另一种基于长度为4的位模式。要构造这种系统,首先要规定一组适当长度的二进制0,接着用二进制计数,直到只有一个0、其他都是1的模式形成。这些模式表示数值0, 1, 2, 3, …。要想获得表示负值的模式,首先要规定一组适当长度的二进制1,接着按照二进制反向计数,直到只有一个1、其他都是0的模式形成。这些模式表示数值-1, -2, -3, …。(如果你认为利用二进制反向计数有困难,那么可以从表格底部只有一个1、其他都为0的模式开始,向上计数到全是1的模式。)
图1-19 二进制补码记数法系统
注意,在二进制补码系统中,位模式最左边的二进制位指明了所表示的数值的符号。因此,最左边的位常称为符号位(sign bit)。在二进制补码系统中,符号位为1的模式表示负值,符号位为0的模式表示非负值。
在二进制补码系统中,绝对值相同的正负数值的表示模式之间有一种对应关系,从右向左读时,直到第一个二进制1(包括),它们都是相同的。然后,以这个1为分界线,左面的位模式互为补码。要得到一个模式的补码(complement),需要将所有的二进制0转换为1,并将所有的二进制1转换为0。例如,图1-19中的4位系统,表示2和-2的模式都是以10结束,但是表示2的模式开始为00,而表示-2的模式开始为11。观察到这一点,我们就可以得出在绝对值相同的、表示正负值的位模式之间进行转换的算法。我们只需要从右到左复制原始的模式直到第一个1,接着在将剩余位转换为最终位模式时,对这些剩余位取反(图1-20)。
图1-20 用4位二进制补码记数法对数值6编码
理解了二进制补码系统的这些基本特性,还可以得出二进制补码表示法的一个解码算法。如果要解码的模式有一个符号位0,我们仅仅需要读出这个数值,就好像这个模式是一个二进制表示。例如,0110表示十进制数值6,因为110是6的二进制表示。如果要解码的模式有一个符号位1,我们就知道表示的数值是负的,而我们所要做的就是找到其绝对值。为了实现这个目的,我们首先要利用图1-20中“复制和取反”的步骤,然后对获得的模式进行解码,就仿佛它只是一个简单的二进制表示。例如,为了对模式1010解码,我们首先要意识到,因为这个符号位是1,表示的数值就是负的。因此,我们利用“复制和取反”步骤获得模式0110,认识到这是6的二进制表示,然后得出结论:原始的模式表示-6。
1.二进制补码记数法中的加法
为了计算二进制补码记数法中的数值相加,我们采用了二进制加法中使用的算法,只是包括答案在内的所有位模式长度都相同。这就意味着,在做二进制补码系统的加法时,如果最后一个进位导致答案左边产生了附加位,那么这个附加位一定会被截断。因此,“加法运算”得出和1011得出+,被截断为0010)。
根据这个理解,我们来分析一下图1-21中的3个加法问题。每一个情况,我们都把问题转化为二进制补码记数法(采用长度为4的位模式),演示先前描述过的加法过程,然后对结果进行解码,回到一般的十进制记数法。
图1-21 转换为二进制补码记数法的加法问题
注意,图1-21中的第3个问题涉及正数和负数的加法,它展示了二进制补码记数法的一个主要优点:有符号数的任何组合加法,都可以使用相同的算法从而使用相同的电路来实现。这与人们传统的算术运算截然不同。尽管小学生是先学加法,后学减法,但是应用二进制补码记数法的机器只需要知道如何相加就可以了。
例如,减法问题7-5与加法问题7+(-5)是一样的。因此,如果人们命令计算机执行7(存储为0111)减5(存储为0101),那么它首先要将5转换为-5(表示为1011),然后执行的加法过程,得到代表数值2的0010,如下所示:
由此我们可以看到,当用二进制补码记数法表示数字值时,只需要将一个加法电路与一个取负电路组合在一起,就足以同时解决加法和减法这两个问题了。(这些电路的图示及解释详见附录B。)
2.溢出问题
我们在前面的例子中避开了这样一个问题:任意一个二进制补码系统对所能表示的数值大小都有限制。当使用4位模式二进制补码时,可以表示的最大正整数是7,最小负整数是-8。具体来说,就是无法表示数值9,这就意味着我们不能指望得出5+4的正确答案。事实上,它的结果会为-7。这种现象称为溢出(overflow)。也就是说,溢出指的是这样一个问题,即计算得出的数值超出了可以表示的数值范围。使用二进制补码记数法时,两个正值相加或两个负值相加都可能会出现这种情况。无论哪种情况,检查答案的符号位就可以发现溢出的条件。如果两个正值相加的结果是负值的模式,或者两个负值相加的结果为正,那么就发生了溢出问题。
当然,使用二进制补码系统的大多数计算机的位模式,都比我们上面例子中给出的长,因而在进行较大数值操作时不会产生溢出。现在,人们普遍使用二进制补码记数法的32位模式来存储数值,可以得到的最大正值是2 147 483 647。如果需要更大的数值,我们可以使用更长的位模式,或者改变度量单位。例如,在解答一个问题时用英尺代替英寸,所得的数值就会变小,而且也可以达到所要求的精确度。
关键问题是计算机会制造错误。因此,使用机器的人一定要意识到可能涉及的危险。其中一个问题就是,计算机程序员和使用者会自满而导致忽视一个事实——小数值可以累加成大数值。例如,人们过去普遍使用二进制补码记数法的16位模式表示数值,这就意味着出现大于或等于215 =32 768的数值时就会产生溢出。日,一家医院多年来运行良好的计算机出现了故障。仔细检查后发现,那天距日共32 768天,而计算机的程序正是基于那个起始日期开始计算日期的。因此,由于溢出原因,日的日期产生了负值——设计计算机程序时没有考虑到这种现象。
1.6.2 余码记数法
表示整数值的另外一种方法是余码记数法(excess notation)。与二进制补码记数法相同,余码记数法中的每一个数值也都是用相同长度的位模式表示的。为了建立余码系统,我们首先要选择所使用的模式长度,然后根据二进制记数呈现的顺序写下那个长度的所有位模式。接着我们发现,二进制1作为其最高位的第一个模式大约就在数列的中间。我们用这个模式表示0,其前的模式就分别用于表示-1, -2, -3, …,其后的模式分别用于表示1, 2, 3, …。使用长度为4的模式产生的编码如图1-22所示。我们可以看到,模式1101表示数值5,0011表示数值-5。(注意,余码系统和二进制补码系统的区别就是符号位相反。)
图1-22表示的系统称为余8记数法。为了了解其由来,我们先用传统二进制系统的编码解释每一个模式,然后将其与余码记数法表示的数值进行比较。你会发现,每一个模式的二进制解释值都要比余码记数法解释值大8。例如,模式1100在二进制记数法中表示数值12,在余码系统中表示4;0000在二进制记数法中表示数值0,但是在余码系统中表示-8。与此类似,在基于长度为5的位模式的余码系统中,模式10000表示的是0,而不是通常的数值16,该记数法称为余16记数法。同样,你可以证明3位余码系统应该称为余4记数法(见图1-23)。
图1-22 余8代码转换表     
图1-23 使用长度为3的位模式的余码记数系统
问题与练习
1.将下面每一个二进制补码表示转换为相应的十进制形式。
    b. 01111
    c. 11100
    e. 00000
      f. 10000
2.用8位位模式将下列每一个十进制表示转换为相应的二进制补码形式。
      b. -6
      c. -17
      e. -1
      f. 0
3.假定下列位模式表示的是用二进制补码记数法存储的数值,求出每一个值的负值的二进制补码表示。
    b.
    c.
  e.     f.
4.假定一台机器用二进制补码记数法存储数值,如果机器分别采用下列长度的位模式,那么可以存储的最大数和最小数分别是什么?
a. 4      b. 6        c. 8
5.在下列问题中,每个位模式表示一个用二进制补码存储的数值。请执行文中所述的加法过程,按照二进制补码记数法求出它们的答案。并将问题及答案转换为十进制记数法进行验证。
a.    b.    c.
d.    e.
6.计算下列由二进制补码记数法表示的问题,但这次要观察溢出问题,并指出哪个答案因产生溢出而不正确。
  b.    c.
d.    e.
7.将下列问题从十进制记数法转换为长度为4的位模式的二进制补码记数法,然后将每一个问题转换成一个相应的加法问题(如机器的做法),然后执行加法。将求得的答案转换为十进制记数法进行验证。
a. 6-(-1)    b.   3-(-2)     c.   4- 6
d. 2-(-4)     e.   1- 5  
8.在二进制补码记数法里,一个正数和一个负数相加时会产生溢出吗?请说明理由。
9.将下面每一个余8码表示转换为相应的十进制形式(解题时不要看文中的表格)。
    b. 0111       c. 1000
d. 0010     e. 0000
      f. 1001
10.将下列的每一个十进制表示转换为相应的余8码形式(解题时不要看文中的表格)。
    b. -5
      c. 3
     e. 7
      f. -8
11.数值9可以用余8记数法表示吗?用余4记数法表示6呢?请说明理由。
*1.7 小数的存储
不同于整数存储,对于包括小数部分的数值的存储,我们不仅要存储代表其二进制表示的0和1,还要存储其小数点的位置。有一种流行的基于科学记数法的存储方法,称为浮点(floating-point)记数法。
1.7.1 浮点记数法
为了解释浮点记数法,我们来看一个只用一个字节来存储的例子。尽管机器通常使用更长的模式,但是这种8位格式也可以表示实际的系统,而且既可以展示重要的概念,又避免了长位模式的混乱。
首先,我们规定这个字节的高位端为符号位。再次说明,符号位中的二进制0代表存储的数值为非负值,1代表数值为负值。接着,我们将这个字节剩余的7个位分为2组,或称其为域:指数域(exponent field)和尾数域(mantissa field)。我们规定符号位右边的3个位为指数域,余下的4个位为尾数域。图1-24描述了如何拆分字节。
图1-24 浮点记数法成分
我们可以借助下面的例子解释这些域的含义。假如一个字节由位模式组成。利用前面的形式分析这个模式,可以看出符号位是0,指数是110,尾数是1011。为了对这个字节解码,我们首先要求解它的尾数,并在它的左边放置一个小数点,得到
接着,我们求解指数域(110)的内容,并将其解释为一个用3位余码方法(见图1-23)存储的整数。从而得出,我们所举例子的指数域模式表示正数2。这就要求我们将上面所得结果的小数点向右移动2位。(负指数域就意味着向左移动小数点。)因此,我们可以得到
这就是的二进制表示(二进制中的小数的表示参见图1-18)。接着,我们看到例子中的符号位是0,因此所表示的值为非负值。最后得出结论:字节表示。如果模式是(除了符号位都与之前相同),表示的数值就将为。
再看一个例子,字节。求尾数后得到
因为指数域(011)表示数值-1,所以将小数点向左移动一位,得到
这表示。因为原始模式中的符号位是0,所以存储的数值为非负值。最后得出结论:模式表示。
在用浮点记数法存储数值时,要颠倒前面的过程。例如,为了对编码,我们首先要将其用二进制记数法表示,得到1.001。接着,我们要从左到右从二进制表示的最左边的1开始,将其位模式复制到尾数域。此时,这个字节如下:
& & & & 1 0 0 1
我们现在必须填充指数域。为了达到这个目的,假定尾数域的左边有一个小数点,然后规定位的数量以及小数点移动的方向,以此得到原始的二进制数字。在这个例子中我们可以看到,.1001中的小数点要向右移动一位才能得到1.001,指数因此为正1,所以我们将101(在余4记数法中表示正1,见图1-23)置于指数域。最后,因为存储的数值是非负的,我们用0填充符号位。完成的字节如下:
0 1 0 1 1 0 0 1
当填充尾数域时,你可能会漏掉一个微妙的细节。这个规则是从最左边的1开始,从左到右复制以二进制表示的位模式。为阐述清楚,让我们考虑一下存储数值的过程,它的二进制记数法表示为.011。这时,其尾数为
& & & & 1 1 0 0
& & & & 0 1 1 0
这是因为我们是从二进制表示最左边的1开始填充尾数域。遵循这个规则的表示称为规范化形式(normalized form)。
使用规范化形式减少了同一数值多种表示的可能性。例如,000110都可以解码成,但是只有第一个模式才是规范化形式。遵循规范化形式也意味着,所有非0数值的表示都会有一个以1开始的尾数。不过,数值0是一个特例,它的浮点表示就是全部为0的位模式。
1.7.2 截断误差
下面我们来考虑一下,用我们的1字节浮点记数系统存储数值,看看会出现什么恼人的问题。我们首先用二进制写,得到10.101。但是,当把这个模式复制到尾数域时,我们就用尽了空间,最右边的1(表示最后的)因此丢失了(见图1-25)。如果现在忽视这个问题,继续填充指数域和符号位,那么我们最后得到的位模式将为,它表示的是,而不是。这个现象称为截断误差(truncation error)或舍入误差(round-off error)。这就意味着,由于尾数域空间不够大,存储的部分数值丢失了。
图1-25 数值的编码过程
使用较长的尾数域可以减少这种误差的发生。事实上,现在生产的大多数计算机都至少采用32位存储浮点记数法表示的数值,而不是我们在本书中采用的8位。这同时使得指数域也更长。不过,即使有这样较长的格式,有时候还是需要更高的精确度。
截断误差的另外一个来源是十进制记数法中比较常见的一个现象,即无穷展开式问题,例如,我们在用十进制形式表示的时候。有些数值无论我们用多少位数字都无法精确地表示。传统的十进制记数法与二进制记数法的区别在于,二进制记数法中有无穷展开式的数值多于十进制。例如,数值表示为二进制时为无穷展开式。想象一下,一个粗心的人用浮点记数法存储和处理美元与美分时会产生什么样的问题?尤其是,如果美元被用作度量单位,那么一角就不能被精确地存储。其中一个解决方式就是,以分为单位处理数据,这样所有的数值就都是整数,都可以用诸如二进制补码这样的方法精确存储。
1.7节介绍的浮点记数法过于简单,不能用于实际的计算机中。毕竟,在全部实数中,这一记数法的8位只能表示其中256个数。我们在讨论中使用了8位模式来保持示例的简单性,但依然涵盖了重要的基本概念。
现在的许多计算机都支持32位形式的单精度浮点(single precision floating point)记数法。这一格式使用1位表示符号位,用8位表示指数(余码记数法中的),用23位表示尾数。因此,单精度浮点最多有7位十进制有效数字,可以表示极大的数(数量级为1038)直至极小的数(数量级为10-37)。也就是说,给定一个十进制数,可以非常精确地存储前7位十进制有效数字(但仍有可能存在少量误差),前7位之后的数字一定会因截断误差丢失(虽然数字的近似值会被保留下来)。
另一种形式是64位的双精度浮点(double precision floating point)记数法,最多有15位十进制有效数字。
截断误差和与之相关的问题是工作在数值分析领域的人们每天都很关注的问题。这个数学分支研究的是执行大规模、高精度有效计算所涉及的问题。
下面的例子可以激起任何一位数值分析家的兴趣。假设我们要应用前面定义的1字节浮点记数法来做这3个数值的加法:
如果我们按照上述顺序加数值,首先就是$2\frac{1}{2}$加上$\frac{1}{8}$,得到$2\frac{5}{8}$,二进制表示为10.101。遗憾的是,因为这个数值不能被精确地存储(如同前面所看到的),我们第一步的结果最后被存储为$2\frac{1}{2}$(与其中一个加数相同)。下一步是把这个结果再加到最后的$\frac{1}{8}$上。截断误差在这里再一次出现,最后的结果是错误的$2\frac{1}{2}$。
现在让我们以相反的顺序来加这些数值:首先将$\frac{1}{8}$加到$\frac{1}{8}$上,得到$\frac{1}{4}$,其二进制表示为.01。于是,第一步的结果在一个字节里被存储为,这是精确的。然后,我们将这个$\frac{1}{4}$加到数列中的下一个数值$2\frac{1}{2}$上,得到$2\frac{3}{4}$,我们可以在一个字节里精确地将其存储为。这次的答案是正确的。
总而言之,在浮点记数法表示的数字值加法中,它们相加的顺序很重要。问题是,如果一个很大的数字加上一个很小的数字,那么小数字就可能被截断。因此,多个数值相加的一般规则是先加小数字,即希望在将它们加到一个较大的数值上时,能累计成一个显著的值。这就是前面例子中反映的现象。
现在商用软件包的设计师们在这方面做得很好,他们使没有经过培训的使用者也能很好地避免这种问题的发生。在一个典型的电子表格系统中,除非相加的各个数值大小差别达到1016或更多,否则所得结果都是正确的。因此,如果你认为有必要对数值
10 000 000 000 000 000
加1,那么会得到答案
10 000 000 000 000 000
10 000 000 000 000 001
这样的问题在一些应用(如导航系统)中是很严重的,微小的误差会在加法运算中累加,最终产生严重的后果。但是,对于一般的PC使用者,大多数商用软件提供的精确度已经足够了。
问题与练习
1.用文中所述的浮点格式对下列位模式进行解码。
  b.   c.
  d.      e.
2.将下列数值编码成文中所述的浮点格式。指出截断误差的出现情况。
  a.        
b.      c.     d.       e.
3.根据文中所述的浮点格式,模式111101中哪一个表示的值更大?描述一种确定哪个模式表示的值更大的简单过程。
4.使用文中所述的浮点格式时,可以表示的最大值是什么?可以表示的最小正值是什么?
*1.8 数据与程序设计
虽然人类发明了构成现代计算机的数据表示和基本操作,但很少有人特别擅长于直接在计算机的这个层面上工作。人们喜欢在更高的抽象层次上推理计算问题,他们依赖计算机来处理最底层的细节问题。(programming language)是人类创造的一个计算机系统,通过它,人们能够使用更高层次的抽象向计算机精确地表达算法。
在20世纪,对计算机进行程序设计被认为是少数训练有素的专家们才能涉足的领域;当然,仍然有许多计算问题需要有经验的计算机科学家和软件工程师的关注。不过,到了21世纪,随着计算机和计算与我们现代生活中的方方面面交织在一起的程度的日益加深,更加难以确定哪些职业不需要至少有某种程度的编程技能。事实上,一些人已经把程序设计或确定为现代读写能力中继阅读、写作及算术之后的又一个基础支柱了。
在本节以及后续章节的程序设计补充部分,我们会看到程序设计语言是如何反映本章主要内容,以及如何让人类更容易地解决计算问题的。
1.8.1 Python入门
Python是一门程序设计语言,由吉多·范罗苏姆(Guido van Rossum)于20世纪80年代后期创立。现在它是十大最常用的语言之一,仍然深受网络应用开发领域及科学计算领域人士的喜爱,并被视为学生的入门语言。使用Python的组织,从谷歌到美国国家航空航天局(NASA),从DropBox公司到工业光魔公司(Industrial Light & Magic),范围很广;使用Python的计算机用户也横跨非正式、科学和艺术领域。Python强调可读性,包括命令型程序设计范型、面向对象型程序设计范型和函数式程序设计范型等三大要素,这些将在第6章中介绍。
用于编辑和运行用Python编写的程序的软件可以免费从www.python.org获得,这里还有很多其他的入门资源。Python语言一直在不断演变,本书的所有示例都将使用Python 3这个版本。更早的Python版本能够运行非常类似的程序,但是自Python 2版本开始有了许多细微的变化,如标点符号。
Python是一种(),这意味着初学者可以将Python指令键入交互提示符中,或者将Python指令存储在一个(称为“脚本”的)纯文本文件里以后运行。在下面的示例中,这两种模式都可以使用,但练习和复习题一般都要求用Python脚本来完成。
1.8.2 你好,Python
许多程序设计语言的介绍长久以来一直有一个传统,描述的第一个程序都是“Hello, World”。这个简单的程序输出一个名义上的问候,演示一种特定语言是如何产生结果以及如何表示文本的。在Python中,这个程序的写法如下:
print('Hello, World!')
可以将这条语句键入Python的交互解释器里,也可以把它存为Python脚本后再执行。不管用哪一种方式,最后的结果都应该是:
Hello, World!
Python会把两个引号之间的文本返回给用户。
即使是在这种简单的Python脚本里面,也有几个需要注意的方面。首先,print是一个内置函数,是Python脚本用来产生的一个预定义操作,输出指的是一个能够让用户看到的程序结果。这个打印函数后面有一个开括号和一个闭括号,这两个括号之间的内容就是要打印的值。
其次,Python可以使用单引号来表示文本串。大写字母H前面的引号和感叹号后面的引号,分别表示由字符组成的字符串的开头和结尾,在Python中,这个字符串会被当作值。
程序设计语言能够非常精确地完成它们的指令。即使用户只是稍微修改一下打印语句中开始引号和结束引号之间的消息,最后打印出来的文本也会相应地变化。花一点儿时间,在打印语句中试试不同的大小写、不同的标点符号,甚至不同的单词,读者会看到确实是这样。
1.8.3 变量
Python允许用户给值命名以备日后使用,这是构造简洁、易懂的脚本时的一个重要抽象。这些命名的存储位置被称为(variable),类似于代数课程中的数学变量。下面来看一下略有增强的Hello World版本:
message = 'Hello, World!'
print(message)
在这个脚本中,第一行是一个(assignment statement)。=号的使用可能会让习惯了等号的代数用法的初学者感到迷惑。这个赋值语句应该读作:“变量message被赋予字符串值'Hello, World!'。”通常,赋值语句由等号左边的变量名和等号右边的值构成。
Python是一种(dynamically typed)语言,这意味着,我们不需要在建立脚本时提前创建好一个名为message的变量,或是什么类型的值应该存储在message中,而只需要在这个脚本中说明我们的文本串会被赋给message,接着在后面的print语句中引用这个变量message就行了。
变量的命名很大程度上取决于Python用户。Python的简单规则是:变量名必须以字母开头,可以包含任意数量的字母、数字和下划线字符(_)。虽然对于一个两行的示例脚本来说,可能把变量命名为m就可以了,但有经验的程序员都会在他们的脚本中尽量给变量起一个有意义的、具有描述性的名字。
Python变量名是(case-sensitive),即有大小写问题。名为size的变量,与名为Size或SIZE的变量是截然不同的。有一小部分(keyword),即为Python中的一些特殊含义保留的名字,不能用作变量名。在Python的内置帮助系统里能查看到这个关键字清单。
help('keywords')
变量可用于存储Python能表示的所有类型的值。
my_integer = 5
my_floating_point = 26.2
my_Boolean = True
my_string = 'characters'
观察上面这些值的类型,它们与本章前面介绍的表示方法是相对应的:布尔值真和假(见1.1节)、文本(见1.4节)、整数(见1.6节)和浮点数(见1.7节)。通过Python的其他代码(超出了本书简单介绍的范围),我们还能够用Python变量存储图像和声音数据(见1.4节)。
Python使用0x前缀来表示十六进制值,如
my_integer = 0xFF
print(my_integer)
不管程序员在推理过程中使用什么样的记数系统,指定一个十六进制的值都不会改变计算机存储器中该值的表示,存储器都会把整数值存储为许多个1和0。十六进制记数法仍然是人类在用的一种有助于理解脚本的快捷表示。因此,上面的print语句打印出来的是255,即十六进制0xFF的十进制解释,因为这是print的默认行为。用更复杂的print语句可以输出其他表示形式的值,但本书只讨论我们比较熟悉的十进制表示。
Unicode字符包含着无所不在的ASCII子集中所没有的字符,在文本编辑器支持Unicode字符时,可以直接在字符串里包含Unicode字符:
print('1000']
# Prints 1000, one thousand Indian Rupees
或者用前缀'\u'和4个十六进制数字来指定Unicode字符:
print('\u00A31000')
# Prints ?1000, one thousand British
# Pounds Sterling
字符串的'\u00A3'部分会对英镑符号的Unicode表示进行编码。后面紧跟着写'1000',这样最终输出的货币符号和数量之间就不会有空格了:?1000。
除了Unicode文本串,这些示例语句引入了另外一种语言特性。#号表示(comment)的开始,这是一个人类可读的Python代码符号,在计算机执行时会被忽略。有经验的程序员会在他们的代码中使用注释来解释算法难懂的部分,包括历史或来源信息,或者只写些读代码的人应该注意的问题。#号右边一直到行尾的所有字符都会被Python忽略。
1.8.4 运算符和表达式
Python的内置运算符允许用各种熟悉的方式对值进行操作和组合。
print(3 + 4)
# Prints "7", which is 3 plus 4.
print(5 - 6)
# Prints "-1", which is 5 minus 6
print(7 * 8)
# Prints "56", which is 7 times 8
print(45 / 4)
# Prints "11.25", which is 45 divided by 4
print(2 ** 10)
# Prints "1024", which is 2 to the 10th power
当一个操作(如45除以4)产生的是非整数结果(如11.25)时,Python会将表示类型隐式转换为浮点表示。如果希望结果是整数,就要使用另外一组运算符。
print(45 // 4)
# Prints "11", which is 45 integer divided by 4
print(45 % 4)
# Prints "1", because 4 * 11 + 1 = 45
双斜线(//)表示(integer floor division)运算符,百分号(%)表示(modulus)或取余运算符。将这两个计算综合起来,可读作:“4除45等于11,余数为1。”在前面的示例中,我们用**表示幂运算符,这看起来可能有些奇怪,因为在打印文本甚至一些其他程序设计语言中,幂运算符一般都是用插入符号(^)表示。在Python中,^运算符是一种(bitwise Boolean operation)运算符,有关按位布尔运算的内容将在下一章中介绍。
还可以用一些直观的方式对字符串值进行组合和操作。
s = 'hello' + 'world'
# Prints "helloworldhelloworldhelloworldhelloworld"
其中,加(+)运算符用于(concatenate)字符串值,乘(*)运算符用于(replicate)字符串值。
有些内置运算符的多重含义会导致混淆。下面这个脚本会产出一个错误:
print('USD$' + 1000)
# TypeError: Can't convert 'int' to str implicitly
这个错误指出,字符串拼接运算符不知道在第二个操作数不是字符串的情况下要怎么做。不过幸运的是,Python提供了允许将值从一种表示类型转换为另外一种表示类型的函数。int()函数能把浮点型值转换回整数表示,丢弃小数部分。如果字符串可以正确地拼出一个有效数字,那么int()函数还能将一个文本数字串转换为一个整数表示。同样地,str()函数能把数字表示转换成UTF-8编码的文本串。因此,对上述print语句做如下修改就能改正错误。
print('USD$' + str(1000))
# Prints "USD$1000"
1.8.5 货币转换
下面这个完整的Python脚本示例演示了许多本节要介绍的概念。给定一定数量的美元,脚本会对其进行货币转换,转换成4种其他货币。
# A converter for international currency exchange.
USD_to_GBP = 0.66
# Today's rate, US dollars to British Pounds
USD_to_EUR = 0.77
# Today's rate, US dollars to Euros
USD_to_JPY = 99.18
# Today's rate, US dollars to Japanese Yen
USD_to_INR = 59.52
# Today's rate, US dollars to Indian Rupees
= '\u00A3' # Unicode values for non-ASCII currency symbols.
= '\u20AC'
= '\u00A5'
= '\u20B9'
# The number of dollars to convert
= dollars * USD_to_GBP
# Conversion calculations
= dollars * USD_to_EUR
= dollars * USD_to_JPY
= dollars * USD_to_INR
print('Today, $' + str(dollars))
# Printing the results
print('converts to ' + GBP_sign + str(pounds))
print('converts to ' + EUR_sign + str(euros))
print('converts to ' + JPY_sign + str(yen))
print('converts to ' + INR_sign + str(rupees))
执行该脚本时,输出如下:
Today, $1000
converts to ?660.0
converts to EUR770.0
converts to ?99180.0
converts to
1.8.6 调试
程序设计语言对初学者不是很宽容,初学者在编写软件时,需要花费大量的时间努力查找代码中的bug或者错误。软件中的bug可分为3大类:语法错误(syntax error,键入时产生的符号错误)、语义错误(semantic error,程序含义的错误)和运行时错误(runtime error,程序运行时发生的错误)。
对于新手来说,语法错误是最常见的,包括一些简单的错误,如忘记文本串开头或者结尾的其中一个引号,没有关闭开括号,或者拼错函数名print。当Python解释器遇到这些错误时,通常会努力指出这些错误,显示问题代码所在行的行号以及问题描述。经过一些练习之后,初学者能很快学会识别和解释常见的错误情况。下面来看几个例子:
print(5 + )
SyntaxError: invalid syntax
上述表达式在加法运算符和闭括号之间缺少一个值。
print(5.e)
SyntaxError: invalid token
Python希望小数点后面是数字,而不是字母。
NameError: name 'pront' is not defined
就像叫错一个人的名字一样,拼错已知函数或变量的名字会产生混淆和尴尬。
语义错误是算法中的缺陷,或者某种语言表达算法方式中的缺陷。这样的例子可能包括:在一个计算中使用了错误的变量,或者弄错了复杂表达式中算术运算符的顺序。Python遵循运算符优先级的标准规则,所以在像total_pay = 40 + extra_hours * pay_rate这样的表达式中,乘法会在加法之前执行,错误地计算总工资。(除非你的工资率恰好是1美元/小时。)使用括号正确地指定复杂表达式中的运算顺序,如total_pay = (40 + extra_hours) * pay_rate,既可以避免语义错误,又可以避免写出的代码难于理解。
最后,这个层次上的运行时错误可能包括:无意识地除以0,或者使用未定义的变量。Python是从上到下读取语句的,在表达式里使用变量之前,Python必须看到变量的赋值语句。
是高效编写Python脚本(或者任何种类的实际程序)必不可少的一部分。在编写脚本时要频繁运行它,可能每完成一行代码就需要运行一次。这样做能尽早识别和修复语法错误,把程序设计者的注意力集中在脚本每一步应该做什么上。
问题与练习
1.是什么使Python成为一门程序设计语言?
2.编写Python语句输出下列内容。
  a. “Computer Science Rocks”这些词,后面跟一个感叹号。
  b. 数字42。
π的近似值,精确到小数点后第4位。
3.编写Python语句按下列描述给变量赋值。
  a. 将“programmer”这个词赋值给一个名为rockstar的变量。
  b. 将1小时的秒数赋值给一个名为seconds_per_hour的变量。
  c. 将人体的平均温度赋值给一个名为bodyTemp的变量。
4.编写一条Python语句,给定一个已有的名为bodyTemp的变量,单位为华氏度,将与其相等的摄氏度存储到一个名为metricBodyTemp的新变量中。
*1.9 数据压缩
为了存储或传输数据,在保留原有内容的同时,缩小所涉及数据的大小是有益的(有时也是必须的)。用于完成这一过程的技术称为数据压缩(data compression)。本节,我们首先要学习一些普通的数据压缩方法,然后要了解一些为特殊应用设计的方法。
1.9.1 通用的数据压缩技术
数据压缩方案有两类,一类是无损的(lossless),另一类是有损的(lossy)。无损方案在压缩过程中是不丢失信息的,有损方案在压缩过程中会丢失信息。通常有损技术比无损技术更能压缩,因此在可以容忍小错误的应用中很流行,如图像和音频压缩。
对于被压缩数据由一长串相同的数值组成的情况,普遍使用称为行程长度编码(run-length encoding)的压缩技术,这是一种无损方法。它的过程是,将一组相同的数据成分替换成一个编码,指出重复的成分以及其在序列中出现的次数。例如,指出一个位模式包括253个1,接着118个0,接着87个1,这样要比实际列出458个位节省空间。
另外一种无损数据压缩技术是频率相关编码(frequency-dependent encoding),在这个系统里,用于表示数据项的位模式的长度与这个项的使用频率是反相关的。这种编码是变长编码的例子,意思是项由不同长度的模式表示。戴维·赫夫曼的功劳是发现了一般用于开发频率相关编码的算法,人们一般称用这种方法开发的编码为赫夫曼编码(Huffman code)。因此,现在使用的大多数频率相关编码都是赫夫曼编码。
让我们看一个频率相关编码的例子,考虑一下编码英文文本的任务。在英文中,字母e、t、a和i的使用频率要大于字母z、q和x。因此,当为英文文本编码时,如果用短位模式表示前面的字母,用长位模式表示后面的字母,那么就能节省空间。结果得到的编码中,英文文本的表示长度要比用统一长度编码时的短。
在某些情况下,压缩的数据流由各个数据单元组成,每一个数据单元与其前面一个差别很小。动画的连续帧就是一个例子。这时,使用相对编码(relative encoding)——也称为差分编码(differential encoding)——技术是很有用的。这些技术记录下了两个连续数据单元之间的区别,而不是全部单元;也就是说,每个单元是根据其与前一个单元的关系被编码的。相对编码用无损形式或有损形式都可以实现,具体取决于两个连续数据单元之间的差别是精确编码还是近似编码。
还有其他流行的基于字典编码(dictionary encoding)技术的压缩系统,这里的术语字典(dictionary)指的是一组构造块,压缩的信息通过它们建造起来,而信息本身被编码成字典的一系列参照符。我们一般认为字典编码系统是无损系统,不过在学习图像压缩时我们将看到,有时候字典条目仅仅是正确数据成分的近似值,这就使其成了有损压缩系统。
字处理程序可以使用字典编码来压缩文本文档,因为这些字处理程序中已经包含了用于拼写检查的字典,而这些字典都是出色的压缩字典。特别值得一提的是,一个完整的单词可以编码成字典的一个单独参照符,而不是像使用UTF-8系统那样编码成一系列单独的字符。在字处理程序中,一个典型的字典大约有25 000个条目,这就意味着,单个的字典条目可以用0到24999范围内的整数来标识。也就是说,字典中的一个特定条目用15位的模式就足可标识。相反,如果用到的单词包括6个字母,则它的逐字符编码在使用UTF-8时需要48位。
字典编码的一个变体是自适应字典编码(adaptive dictionary encoding,也称动态字典编码)。在自适应字典编码系统中,字典在编码过程中是可以改变的。一个流行的例子是LZW编码(Lempel-Ziv-Welsh encoding),这个编码的名称是根据它的创造者Abraham Lempel、Jacob Ziv和Terry Welsh的姓氏命名的。要用LZW对信息编码,首先要从包含基础构造块的字典开始,用字典中的构造块来构造信息。但是,当人们在信息中发现更大的单元时,会把它们加到字典上——这意味着,这些单元在未来出现时可以被编码为一个(而不是多个)字典参照符。例如,当对英文文本编码时,人们首先要从包含单独字符、数字和标点符号的字典开始。但是,当信息中的单词被标识后,会被加到字典中。因此,随着信息的不断编码,字典会不断增大,而随着字典的不断增大,信息中会有更多的单词(或者重复的单词模式)被编码为单}

我要回帖

更多关于 计算机怎么样存储数据 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信