迷你书-深入理解计算机系统(原书第2版)


机械工业出版社华章公司 国际视野 专业出版 华章 IT 15 周年,优惠活动尽在 www.china-pub.com 深入理解计算机系统(原书第 2 版)(CSAPP)  这本书被誉为“价值超过等重量黄金的无价资源宝库”;  这本书是 Amazon 五星图书,最伟大的计算机科学教材之一;  这本书由卡耐基梅隆大学计算机学院院长,IEEE 和 ACM 双院士倾力推荐;  这本书被超过 80 所美国和世界一流大学计算机专业选用本书为教材。 预计出版日期:2010.11 有学生如此评价本书第一版—— 起点低,覆盖面广,适合大三,大四的本科生;一个优秀程序员必须理解底层操作,对体系 结构、操作系统必须有认识,这本书的内容很适合作为起点。 如果你是个在校生,如果你学完了组成原理和体系结构,强烈建议你看看这本书。 这本书的确是好书,我没有理由不为此书而拍案叫绝 ,如果学计算机的能够依此书为教材 那就再好不过了。看过此书你会对计算机原理、汇编语言和 C 语言有根本性的认识。 这本书写得太棒了! 我在很多地方都弄不清楚的概念在此书中都有准确明了的阐述。每个 概念都很简洁却都抓住重点。 经典中的经典。写代码的都应该买一本。 特别好的一本书,特别是对在校大学生。整个知识体系非常完善,相关阐述也是由浅入深, 精品! 计算机系统漫游 计算机系统是由硬件和系统软件组成的,它们共同工作来运行应用程序。虽然系统的具体实 现方式随着时间不断变化,但是系统内在的概念却没有改变。所有计算机系统都有相似的硬件和 软件组件,它们执行着相似的功能。一些程序员希望深入了解这些组件是如何工作的,以及这些 组件是如何影响程序的正确性和性能的,以此来提高自身的技能。本书便是为这些读者而写的。 现在就要开始一次有趣的漫游历程了。如果你全力投身学习本书中的概念,完全理解底层计 算机系统以及它对应用程序的影响,那么你将会逐渐成为凤毛麟角的“权威”程序员。 你将会学习一些实践技巧,比如如何避免由计算机表示数字的方式导致的奇怪的数字错误。 你将学会怎样通过一些聪明的小窍门来优化你的 C 代码,以充分利用现代处理器和存储器系统 的设计。你将了解到编译器是如何实现过程调用的,以及如何利用这些知识避免缓冲区溢出错误 带来的安全漏洞,这些弱点会给网络和因特网软件带来了巨大的麻烦。你将学会如何识别和避免 链接时那些令人讨厌的错误,它们困扰着普通的程序员。你将学会如何编写自己的 Unix 外壳、 自己的动态存储分配包,甚至是自己的 Web 服务器。你会认识到并发带来的希望和陷阱,当单 个芯片上集成了多个处理器核时,这个主题变得越来越重要。 在关于 C 编程语言的经典文献 [58] 中,Kernighan 和 Ritchie 通过图 1-1 所示的 hello 程序 来向读者介绍 C 语言。尽管 hello 程序非常简单,但是为了让它完成运行,系统的每个主要组 成部分都需要协调工作。从某种意义上来说,本书的目的就是要帮助你了解当你在系统上执行 hello 程序时,系统发生了什么以及为什么会这样。 我们通过跟踪 hello 程序的生命周期来开始对系统的学习—从它被程序员创建,到在系 统上运行,输出简单的消息,然后终止。我们将沿着这个程序的生命周期,简要地介绍一些逐步 出现的关键概念、专业术语和组成部分。后面的章节将围绕这些内容展开。 1.1 信息就是位 + 上下文 hello 程序的生命周期是从一个源程序(或者说源文件)开始的,即程序员利用编辑器创 建并保存的文本文件,文件名是 hello.c。源程序实际上就是一个由值 0 和 1 组成的位(bit) 序列,8 个位被组织成一组,称为字节。每个字节表示程序中某个文本字符。 大部分的现代系统都使用 ASCII 标准来表示文本字符,这种方式实际上就是用一个唯一的 单字节大小的整数值来表示每个字符。例如图 1-2 中给出了 hello.c 程序的 ASCII 码表示。 hello.c 程序以字节序列的方式存储在文件中。每个字节都有一个整数值,而该整数值对 图 1-1 hello 程序 36 Chapter 1 A Tour of Computer Systems A computer system consists of hardware and systems software that work together to run application programs. Specific implementations of systems change over time, but the underlying concepts do not. All computer systems have similar hardware and software components that perform similar functions. This book is written for programmers who want to get better at their craft by understanding how these components work and how they affect the correctness and performance of their programs. You are poised for an exciting journey. If you dedicate yourself to learning the concepts in this book, then you will be on your way to becoming a rare “power pro- grammer,” enlightened by an understanding of the underlying computer system and its impact on your application programs. You are going to learn practical skills such as how to avoid strange numerical errors caused by the way that computers represent numbers. You will learn how to optimize your C code by using clever tricks that exploit the designs of modern processors and memory systems. You will learn how the compiler implements procedure calls and how to use this knowledge to avoid the security holes from buffer overflow vulnerabilities that plague network and Internet software. Youwill learn how to recognize and avoid the nasty errors during linking that confound the average programmer. You will learn how to write your own Unix shell, your own dynamic storage allocation package, and even your own Web server. You will learn the promises and pitfalls of concurrency, a topic of increasing importance as multiple processor cores are integrated onto single chips. In their classic text on the C programming language [58], Kernighan and Ritchie introduce readers to C using the hello program shown in Figure 1.1. Although hello is a very simple program, every major part of the system must work in concert in order for it to run to completion. In a sense, the goal of this book is to help you understand what happens and why, when you run hello on your system. We begin our study of systems by tracing the lifetime of the hello program, from the time it is created by a programmer, until it runs on a system, prints its simple message, and terminates. As we follow the lifetime of the program, we will briefly introduce the key concepts, terminology, and components that come into play. Later chapters will expand on these ideas. code/intro/hello.c 1 #include 2 3 int main() 4 { 5 printf("hello, world\n"); 6 } code/intro/hello.c Figure 1.1 The hello program. 第1章 Computer Systems :A Programmer’s Perspective,2E 正文.indd 1 2010-10-19 14:17:17  2  第 1 章 计算机系统漫游  应于某个字符。例如,第一个字节的整数值是 35,它对应的就是字符‘#’;第二个字节整数值 为 105,它对应的字符是‘i’,依次类推。注意,每个文本行都是以一个不可见的换行符‘\n’ 来结束的,它所对应的整数值为 10。像 hello.c 这样只由 ASCII 字符构成的文件称为文本文 件,所有其他文件都称为二进制文件。 hello.c 的表示方法说明了一个基本的思想 :系统中所有的信息—包括磁盘文件、存储 器中的程序、存储器中存放的用户数据以及网络上传送的数据,都是由一串位表示的。区分不同 数据对象的唯一方法是我们读到这些数据对象时的上下文。比如,在不同的上下文中,一个同样 的字节序列可能表示一个整数、浮点数、字符串或者机器指令。 作为程序员,我们需要了解数字的机器表示方式,因为它们与实际的整数和实数是不同的。 它们是对真值的有限近似值,有时候会有意想不到的行为表现。这方面的基本原理将在第 2 章中 详细描述。 C 编程语言的起源 C 语言是贝尔实验室的 Dennis Ritchie 于 1969 年~ 1973 年间创建的。美国国家标准学会 (American National Standards Institute,ANSI)在 1989 年颁布了 ANSI C 的标准,后来使 C 语 言标准化成为了国际标准化组织(International Standards Organization,ISO)的责任。这些标准 定义了 C 语言和一系列函数库,即所谓的“C 标准库”。Kernighan 和 Ritchie 在他们的经典著 作中描述了 ANSI C,这本著作被人们满怀感情地称为“K&R”[58]。用 Ritchie 的话来说 [88], C 语言是“古怪的、有缺陷的,但也是一个巨大的成功”。为什么会成功呢? • C 语言与 Unix 操作系统关系密切。C 语言从一开始就是作为一种用于 Unix 系统的程序 设计语言而开发出来的。大部分 Unix 内核,以及所有支撑工具和函数库都是用 C 语言编 写的。20 世纪 70 年代后期到 80 年代初期,Unix 在高等院校兴起,许多人开始接触 C 语 言并喜欢上了它。因为 Unix 几乎全部是用 C 编写的,所以可以很方便地移植到新的机器 上,这种特点为 C 和 Unix 赢得了更为广泛的支持。 • C 语言小而简单。掌控 C 语言设计的是一个人而非一个协会,因此这是一个简洁明了、 没有什么冗赘的一致的设计。K&R 这本书用了大量的例子和练习描述了完整的 C 语言及 其标准库,而全书不过 261 页。C 语言的简单使它相对而言易于学习,也易于移植到不同 的计算机上。 • C 语言是为实践目的设计的。C 语言是为实现 Unix 操作系统而设计的。后来,其他人发 现能够用这门语言无障碍地编写他们想要的程序。 C 语言是系统级编程的首选,同时它也非常适用于应用级程序的编写。然而,它也并非适 图 1-2 hello.c 的 ASCII 文本表示 Section 1.1 Information Is Bits + Context 37 1.1 Information Is Bits + Context Our hello program begins life as a source program (or source file) that the programmer creates with an editor and saves in a text file called hello.c. The source program is a sequence of bits, each with a value of 0 or 1, organized in 8-bit chunks called bytes. Each byte represents some text character in the program. Most modern systems represent text characters using the ASCII standard that represents each character with a unique byte-sized integer value. For example, Figure 1.2 shows the ASCII representation of the hello.c program. The hello.c program is stored in a file as a sequence of bytes. Each byte has an integer value that corresponds to some character. For example, the first byte has the integer value 35, which corresponds to the character ‘#’. The second byte has the integer value 105, which corresponds to the character ‘i’, and so on. Notice that each text line is terminated by the invisible newline character ‘\n’, which is represented by the integer value 10. Files such as hello.c that consist exclusively of ASCII characters are known as text files. All other files are known as binary files. The representation of hello.c illustrates a fundamental idea: All information in a system—including disk files, programs stored in memory, user data stored in memory, and data transferred across a network—is represented as a bunch of bits. The only thing that distinguishes different data objects is the context in which we view them. For example, in different contexts, the same sequence of bytes might represent an integer, floating-point number, character string, or machine instruction. As programmers, we need to understand machine representations of numbers because they are not the same as integers and real numbers. They are finite approximations that can behave in unexpected ways. This fundamental idea is explored in detail in Chapter 2. #include \n \n i n t main()\n{ 104 62 10 10 105 110 116 32 109 97 105 110 40 41 10 123 \n printf("hel 10 32 32 32 32 112 114 105 110 116 102 40 34 104 101 108 l o , world\n");\n} 108 111 44 32 119 111 114 108 100 92 110 34 41 59 10 125 Figure 1.2 The ASCII text representation of hello.c. 正文.indd 2 2010-10-19 14:17:17 第 1 章 计算机系统漫游  3  用于所有的程序员和所有的情况。C 语言的指针是造成困惑和程序错误的一个常见原因。同时, C 语言还缺乏对非常有用的抽象(例如类、对象和异常)的显式支持。像 C++ 和 Java 这样针 对应用级程序的新程序设计语言解决了这些问题。 1.2 程序被其他程序翻译成不同的格式 hello 程序的生命周期是从一个高级 C 语言程序开始的,因为这种形式能够被人读懂。然 而,为了在系统上运行 hello.c 程序,每条 C 语句都必须被其他程序转化为一系列的低级机器 语言指令。然后这些指令按照一种称为可执行目标程序的格式打好包,并以二进制磁盘文件的形 式存放起来。目标程序也称为可执行目标文件。 在 Unix 系统上,从源文件到目标文件的转化是由编译器驱动程序完成的 : unix> gcc -o hello hello.c 在这里,GCC 编译器驱动程序读取源程序文件 hello.c,并把它翻译成一个可执行目标文件 hello。这个翻译的过程可分为四个阶段完成,如图 1-3 所示。执行这四个阶段的程序(预处理 器、编译器、汇编器和链接器)一起构成了编译系统(compilation system)。 • 预处理阶段。预处理器(cpp)根据以字符 # 开头的命令,修改原始的 C 程序。比如 hello.c 中第 1 行的 #include 命令告诉预处理器读取系统头文件 stdio.h 的内容, 并把它直接插入到程序文本中。结果就得到了另一个 C 程序,通常是以 .i 作为文件扩 展名。 • 编译阶段。编译器(cc1)将文本文件 hello.i 翻译成文本文件 hello.s,它包含一个 汇编语言程序。汇编语言程序中的每条语句都以一种标准的文本格式确切地描述了一条低 级机器语言指令。汇编语言是非常有用的,因为它为不同高级语言的不同编译器提供了通 用的输出语言。例如,C 编译器和 Fortran 编译器产生的输出文件用的都是一样的汇编语言。 • 汇编阶段。接下来,汇编器(as)将 hello.s 翻译成机器语言指令,把这些指令打包成 一种叫做可重定位目标程序(relocatable object program)的格式,并将结果保存在目标文 件 hello.o 中。hello.o 文件是一个二进制文件,它的字节编码是机器语言指令而不是 字符。如果我们在文本编辑器中打开 hello.o 文件,看到的将是一堆乱码。 • 链接阶段。请注意,hello 程序调用了 printf 函数,它是每个 C 编译器都会提供的标 准 C 库中的一个函数。printf 函数存在于一个名为 printf.o 的单独的预编译好了的目 标文件中,而这个文件必须以某种方式合并到我们的 hello.o 程序中。链接器(ld)就 负责处理这种合并。结果就得到 hello 文件,它是一个可执行目标文件(或者简称为可 执行文件),可以被加载到内存中,由系统执行。 GNU 项目 GCC 是 GNU(GNU 是 GNU’s Not Unix 的缩写)项目开发出来的众多有用工具之一。GNU 项目是 1984 年由 Richard Stallman 发起的一个免税的慈善项目。该项目的目标非常宏大,就是开 图 1-3 编译系统 正文.indd 3 2010-10-19 14:17:22  4  第 1 章 计算机系统漫游  发出一个完整的类 Unix 的系统,其源代码能够不受限制地被修改和传播。GNU 项目已经开发出 了一个包含 Unix 操作系统的所有主要部件的环境,但内核除外,内核是由 Linux 项目独立发展 而来的。GNU 环境包括 EMACS 编辑器、GCC 编译器、GDB 调试器、汇编器、链接器、处理 二进制文件的工具以及其他一些部件。GCC 编译器已经发展到支持许多不同的语言,能够针对 许多不同的机器生成代码。支持的语言包括 C、C++、Fortran、Java、Pascal、面向对象 C 语言 (Objective-C)和 Ada。 GNU 项目取得了非凡的成绩,但是却常常被忽略。现代开放源码运动(通常和 Linux 联系 在一起)的思想起源是 GNU 项目中自由软件(free software)的概念。(此处的 free 为自由言论 (free speech)中“自由”之意,而非免费啤酒(free beer)中“免费”之意。)而且,Linux 如此 受欢迎在很大程度上还要归功于 GNU 工具,因为它们给 Linux 内核提供了环境。 1.3 了解编译系统如何工作是大有益处的 对于像 hello.c 这样简单的程序,我们可以依靠编译系统生成正确有效的机器代码。但 是,有一些重要的原因促使程序员必须知道编译系统是如何工作的,其原因如下 : • 优化程序性能。现代编译器都是成熟的工具,通常可以生成很好的代码。作为程序员,我 们无需为了写出高效代码而去了解编译器的内部工作。但是,为了在 C 程序中做出好的编 码选择,我们确实需要了解一些机器代码以及编译器将不同的 C 语句转化为机器代码的方 式。例如,一个 switch 语句是否总是比一系列的 if-then-else 语句高效得多?一个 函数调用的开销有多大? while 循环比 for 循环更有效吗?指针引用比数组索引更有效 吗?为什么将循环求和的结果放到一个本地变量中,与将其放到一个通过引用传递过来的 参数中相比,运行速度要快很多呢?为什么我们只是简单地重新排列一下一个算术表达式 中的括号就能让一个函数运行得更快? 在第 3 章中,我们将介绍两种相关的机器语言 :IA32 和 x86-64。IA32 是 32 位的, 目前普遍应用于运行 Linux、Windows 以及较新版本的 Macintosh 操作系统的机器上 ; x86-64 是 64 位的,可以用在比较新的微处理器上。我们会介绍编译器是如何把不同的 C 语言结构转换成它们的机器语言的。第 5 章,你将学习如何通过简单转换 C 语言代 码,以帮助编译器更好地完成工作,从而调整 C 程序的性能。在第 6 章,你将学习到 存储器系统的层次结构特性,C 语言编译器将数组存放在存储器中的方式,以及 C 程 序又是如何能够利用这些知识从而更高效地运行。 • 理解链接时出现的错误。根据我们的经验,一些最令人困扰的程序错误往往都与链接器操 作有关,尤其是当你试图构建大型的软件系统时。例如,链接器报告它无法解析一个引 用,这是什么意思?静态变量和全局变量的区别是什么?如果你在不同的 C 文件中定义了 名字相同的两个全局变量会发生什么?静态库和动态库的区别是什么?我们在命令行上排 列库的顺序有什么影响?最严重的是,为什么有些链接错误直到运行时才会出现?在第 7 章,你将得到这些问题的答案。 • 避免安全漏洞。多年来,缓冲区溢出错误是造成大多数网络和 Internet 服务器上安全漏洞 的主要原因。存在这些错误是因为很少有人能理解限制他们从不受信任的站点接收数据 的数量和格式的重要性。学习安全编程的第一步就是理解数据和控制信息存储在程序栈 上的方式会引起的后果。作为学习汇编语言的一部分,我们将在第 3 章中描述堆栈原理 和缓冲区溢出错误。我们还将学习程序员、编译器和操作系统可以用来降低攻击威胁的 方法。 正文.indd 4 2010-10-19 14:17:23 第 1 章 计算机系统漫游  5  1.4 处理器读并解释存储在存储器中的指令 此刻,hello.c 源程序已经被编译系统翻译成了可执行目标文件 hello,并存放在磁盘 上。要想在 Unix 系统上运行该可执行文件,我们将它的文件名输入到称为外壳(shell)的应 用程序中 : unix> ./hello hello, world unix> 外壳是一个命令行解释器,它输出一个提示符,等待你输入一个命令行,然后执行这个命 令。如果该命令行的第一个单词不是一个内置的外壳命令,那么外壳就会假设这是一个可执行文 件的名字,它将加载并运行这个文件。所以在此例中,外壳将加载并运行 hello 程序,然后等 待程序终止。hello 程序在屏幕上输出它的信息,然后终止。外壳随后输出一个提示符,等待 下一个输入的命令行。 1.4.1 系统的硬件组成 为了理解运行 hello 程序时发生了什么,我们需要了解一个典型系统的硬件组织,如图 1-4 所示。这张图是 Intel Pentium 系统产品系列的模型,但是所有其他系统也有相同的外观和特 性。现在不要担心这张图的复杂性—我们将在本书分阶段介绍大量的细节。 CPU :中央处理单元 ;ALU :算术 / 逻辑单元 ;PC :程序计数器 ;USB :通用串行总线 1. 总线 贯穿整个系统的是一组电子管道,称做总线,它携带信息字节并负责在各个部件间传递。通 常总线被设计成传送定长的字节块,也就是字(word)。字中的字节数(即字长)是一个基本的 系统参数,在各个系统中的情况都不尽相同。现在的大多数机器字长有的是 4 个字节(32 位),有 的是 8 个字节(64 位)。为了讨论的方便,假设字长为 4 个字节,并且总线每次只传送 1 个字。 2. I/O 设备 输入 / 输出(I/O)设备是系统与外部世界的联系通道。我们的示例系统包括 4 个 I/O 设备 : 作为用户输入的键盘和鼠标,作为用户输出的显示器,以及用于长期存储数据和程序的磁盘驱动 图 1-4 一个典型系统的硬件组成 正文.indd 5 2010-10-19 14:17:23  6  第 1 章 计算机系统漫游  器(简单地说就是磁盘)。最初,可执行程序 hello 就存放在磁盘上。 每个 I/O 设备都通过一个控制器或适配器与 I/O 总线相连。控制器和适配器之间的区别主要 在于它们的封装方式。控制器是置于 I/O 设备本身的或者系统的主印制电路板(通常称为主板) 上的芯片组,而适配器则是一块插在主板插槽上的卡。无论如何,它们的功能都是在 I/O 总线和 I/O 设备之间传递信息。 第 6 章会更多地说明磁盘之类的 I/O 设备是如何工作的。在第 10 章,你将学习如何在应用 程序中利用 Unix I/O 接口访问设备。我们将特别关注网络类设备,不过这些技术对于其他设备 来说也是通用的。 3. 主存 主存是一个临时存储设备,在处理器执行程序时,用来存放程序和程序处理的数据。从物理 上来说,主存是由一组动态随机存取存储器(DRAM)芯片组成的。从逻辑上来说,存储器是一 个线性的字节数组,每个字节都有其唯一的地址(即数组索引),这些地址是从零开始的。一般 来说,组成程序的每条机器指令都由不同数量的字节构成。与 C 程序变量相对应的数据项的大 小是根据类型变化的。例如,在运行 Linux 的 IA32 机器上,short 类型的数据需要 2 个字节, int、float 和 long 类型需要 4 个字节,而 double 类型需要 8 个字节。 第 6 章将具体介绍存储技术,如 DRAM 芯片是如何工作的,以及它们又是如何组合起来构 成主存的。 4. 处理器 中央处理单元(CPU),简称处理器,是解释(或执行)存储在主存中指令的引擎。处理器 的核心是一个字长的存储设备(或寄存器),称为程序计数器(PC)。在任何时刻,PC 都指向主 存中的某条机器语言指令(即含有该条指令的地址)。 从系统通电开始,直到系统断电,处理器一直在不断地执行程序计数器指向的指令,再更新 程序计数器,使其指向下一条指令。处理器看上去是按照一个非常简单的指令执行模型来操作 的,这个模型是由指令集结构决定的。在这个模型中,指令按照严格的顺序执行,而执行一条指 令包含执行一系列的步骤。处理器从程序计数器(PC)指向的存储器处读取指令,解释指令中 的位,执行该指令指示的简单操作,然后更新 PC,使其指向下一条指令,而这条指令并不一定 与存储器中刚刚执行的指令相邻。 这样的简单操作并不多,而且操作是围绕着主存、寄存器文件(register file)和算术 / 逻辑 单元(ALU)进行的。寄存器文件是一个小的存储设备,由一些 1 字长的寄存器组成,每个寄 存器都有唯一的名字。ALU 计算新的数据和地址值。下面列举一些简单操作的例子,CPU 在指 令的要求下可能会执行以下操作 : • 加载 :把一个字节或者一个字从主存复制到寄存器,以覆盖寄存器原来的内容。 • 存储 :把一个字节或者一个字从寄存器复制到主存的某个位置,以覆盖这个位置上原来 的内容。 • 操作 :把两个寄存器的内容复制到 ALU,ALU 对这两个字做算术操作,并将结果存放到 一个寄存器中,以覆盖该寄存器中原来的内容。 • 跳转 :从指令本身中抽取一个字,并将这个字复制到程序计数器(PC)中,以覆盖 PC 中 原来的值。 处理器看上去只是它的指令集结构的简单实现,但是实际上现代处理器使用了非常复杂的机 制来加速程序的执行。因此,我们可以这样区分处理器的指令集结构和微体系结构 :指令集结构 PC 也普遍地被用来作为“个人计算机”的缩写。然而,两者之间的区别应该可以很清楚地从上下文中看出来。 正文.indd 6 2010-10-19 14:17:23 第 1 章 计算机系统漫游  7  描述的是每条机器代码指令的效果 ;而微体系结构描述的是处理器实际上是如何实现的。第 3 章 我们研究机器代码时考虑的是机器的指令集结构所提供的抽象性。第 4 章将更详细地介绍处理器 实际上是如何实现的。 1.4.2 运行 hello 程序 前面简单描述了系统的硬件组成和操作,现在开始介绍当我们运行示例程序时到底发生了 些什么。在这里我们必须省略很多细节稍后再做补充,但是从现在起我们将很满意这种整体上 的描述。 初始时,外壳程序执行它的指令,等待我们输入一个命令。当我们在键盘上输入字符串 “./hello”后,外壳程序将字符逐一读入寄存器,再把它存放到存储器中,如图 1-5 所示。 当我们在键盘上敲回车键时,外壳程序就知道我们已经结束了命令的输入。然后外壳执行一 系列指令来加载可执行的 hello 文件,将 hello 目标文件中的代码和数据从磁盘复制到主存。 数据包括最终会被输出的字符串“hello, world\n”。 利用直接存储器存取(DMA,将在第 6 章讨论)的技术,数据可以不通过处理器而直接从 磁盘到达主存。这个步骤如图 1-6 所示。 一旦目标文件 hello 中的代码和数据被加载到主存,处理器就开始执行 hello 程序的 main 程序中的机器语言指令。这些指令将“hello, world\n”字符串中的字节从主存复制到 寄存器文件,再从寄存器文件中复制到显示设备,最终显示在屏幕上。这个步骤如图 1-7 所示。 1.5 高速缓存至关重要 这个简单的示例揭示了一个重要的问题,即系统花费了大量的时间把信息从一个地方挪到另 一个地方。hello 程序的机器指令最初是存放在磁盘上的,当程序加载时,它们被复制到主存 ; 当处理器运行程序时,指令又从主存复制到处理器。相似地,数据串“hello, world\n”初 始时在磁盘上,然后复制到主存,最后从主存上复制到显示设备。从程序员的角度来看,这些复 制就是开销,减缓了程序“真正”的工作。因此,系统设计者的一个主要目标就是使这些复制操 作尽可能快地完成。 图 1-5 从键盘上读取 hello 命令 正文.indd 7 2010-10-19 14:17:23  8  第 1 章 计算机系统漫游  根据机械原理,较大的存储设备要比较小的存储设备运行得慢,而快速设备的造价远高于同 类的低速设备。例如,一个典型系统上的磁盘驱动器可能比主存大 1000 倍,但是对处理器而言, 从磁盘驱动器上读取一个字的时间开销要比从主存中读取的开销大 1000 万倍。 类似地,一个典型的寄存器文件只存储几百字节的信息,而主存里可存放几十亿字节。然 而,处理器从寄存器文件中读数据的速度比从主存中读取几乎要快 100 倍。更麻烦的是,随着这 些年半导体技术的进步,这种处理器与主存之间的差距还在持续增大。加快处理器的运行速度比 加快主存的运行速度要容易和便宜得多。 针对这种处理器与主存之间的差异,系统设计者采用了更小、更快的存储设备,即高速缓 存存储器(简称高速缓存),作为暂时的集结区域,用来存放处理器近期可能会需要的信息。图 1-8 展示了一个典型系统中的高速缓存存储器。位于处理器芯片上的 L1 高速缓存的容量可以达 图 1-6 从磁盘加载可执行文件到主存 图 1-7 将输出字符串从内存写到显示器 正文.indd 8 2010-10-19 14:17:24 第 1 章 计算机系统漫游  9  到数万字节,访问速度几乎和访问寄存器文件一样快。一个容量为数十万到数百万字节的更大的 L2 高速缓存通过一条特殊的总线连接到处理器。进程访问 L2 高速缓存的时间要比访问 L1 高速 缓存的时间长 5 倍,但是这仍然比访问主存的时间快 5 ~ 10 倍。L1 和 L2 高速缓存是用一种叫 做静态随机访问存储器(SRAM)的硬件技术实现的。比较新的、处理能力更强大的系统甚至有 三级高速缓存 :L1、L2 和 L3。系统可以获得一个很大的存储器,同时访问速度也很快,原因是 利用了高速缓存的局部性原理,即程序具有访问局部区域里的数据和代码的趋势。通过让高速缓 存里存放可能经常访问的数据的方法,大部分的存储器操作都能在快速的高速缓存中完成。 本书得出的重要结论之一,就是意识到高速缓存存在的应用程序员可以利用高速缓存将他们 程序的性能提高一个数量级。你将在第 6 章学习这些重要的设备以及如何利用它们。 1.6 存储设备形成层次结构 在处理器和一个又大又慢的设备(例如主存)之间插入一个更小更快的存储设备(例如高速 缓存)的想法已经成为了一个普遍的观念。实际上,每个计算机系统中的存储设备都被组织成了 一个存储器层次结构,如图 1-9 所示。在这个层次结构中,从上至下,设备变得访问速度越来越 慢、容量越来越大,并且每字节的造价也越来越便宜。寄存器文件在层次结构中位于最顶部,也 就是第 0 级或记为 L0。这里我们展示的是三层高速缓存 L1 到 L3,占据存储器层次结构的第 1 图 1-8 高速缓存存储器 图 1-9 一个存储器层次结构的示例 正文.indd 9 2010-10-19 14:17:24  10  第 1 章 计算机系统漫游  层到第 3 层。主存在第 4 层,以此类推。 存储器层次结构的主要思想是一层上的存储器作为低一层存储器的高速缓存。因此,寄存器 文件就是 L1 的高速缓存,L1 是 L2 的高速缓存,L2 是 L3 的高速缓存,L3 是主存的高速缓存, 而主存又是磁盘的高速缓存。在某些具有分布式文件系统的网络系统中,本地磁盘就是存储在其 他系统中磁盘上的数据的高速缓存。 正如可以运用不同的高速缓存的知识来提高程序性能一样,程序员同样可以利用对整个存储 器层次结构的理解来提高程序性能。第 6 章将更详细地讨论这个问题。 1.7 操作系统管理硬件 我们继续讨论 hello 程序的例子。当外壳加载和运行 hello 程序,以及 hello 程序输出 自己的消息时,外壳和 hello 程序都没 有直接访问键盘、显示器、磁盘或者主 存。取而代之的是,它们依靠操作系统提 供的服务。我们可以把操作系统看成是应 用程序和硬件之间插入的一层软件,如图 1-10 所示。所有应用程序对硬件的操作尝 试都必须通过操作系统。 操作系统有两个基本功能 :1)防止 硬件被失控的应用程序滥用。2)向应用 程序提供简单一致的机制来控制复杂而又 通常大相径庭的低级硬件设备。操作系统 通过几个基本的抽象概念(进程、虚拟 存储器和文件)来实现这两个功能。如 图 1-11 所示,文件是对 I/O 设备的抽象表 示,虚拟存储器是对主存和磁盘 I/O 设备的抽象表示,进程则是对处理器、主存和 I/O 设备的抽 象表示。我们将依次讨论每种抽象表示。 Unix 和 Posix 20 世纪 60 年代是大型、复杂操作系统盛行的年代,如 IBM 的 OS/360 和 Honeywell 的 Multics 系统。OS/360 是历史上最成功的软件项目之一,而 Multics 虽然持续存在了多年,却从 来没有被广泛应用。贝尔实验室曾经是 Multics 项目的最初参与者,但是考虑到该项目的复杂性 和缺乏进展于 1969 年退出。鉴于 Multics 项目不愉快的经历,一组贝尔实验室的研究人员— Ken Thompson、Dennis Ritchie、Doug Mcllroy 和 Joe Ossanna,从 1969 年开始在 DEC PDP-7 计 算机上完全用机器语言编写了一个简单得多的操作系统。这个新系统中的很多思想,如层次文件 系统、作为用户级进程的外壳概念,都是来自于 Multics,只不过在一个更小、更简单的程序包 里实现。1970 年,Brian Kernighan 给新系统命名为“Unix”,这也是一个双关语,暗指“Multics” 的复杂性。1973 年用 C 语言重新编写其内核,1974 年,Unix 开始正式对外发布 [89]。 贝尔实验室以慷慨的条件向学校提供源代码,所以 Unix 在大专院校里获得了很多支持并继 续发展。最有影响的工作是 20 世纪 70 年代晚期到 80 年代早期,在美国加州大学伯克利分校, 伯克利的研究人员在一系列发布版本中增加了虚拟存储器和 Internet 协议,称为 Unix 4.xBSD (Berkeley Software Distribution)。与此同时,贝尔实验室也在发布自己的版本,即 System V Unix。其他厂商的版本,如 Sun Microsystems 的 Solaris 系统,则是从这些最初的 BSD 和 System 图 1-10 计算机系统的分层视图 图 1-11 操作系统提供的抽象表示 正文.indd 10 2010-10-19 14:17:25 第 1 章 计算机系统漫游  11  V 版本中衍生而来。 20 世纪 80 年代中期,Unix 厂商试图通过加入新的、往往不兼容的特性来使它们的程序与众不 同,麻烦也就随之而来了。为了阻止这种趋势,IEEE(电气和电子工程师协会)开始努力标准化 Unix 的开发,后来由 Richard Stallman 命名为“Posix”。结果就得到了一系列的标准,称做 Posix 标准。这套标准涵盖了很多方面,比如 Unix 系统调用的 C 语言接口、外壳程序和工具、线程及网 络编程。随着越来越多的系统日益完全地遵从 Posix 标准,Unix 版本之间的差异正在逐渐消失。 1.7.1 进程 像 hello 这样的程序在现代系统上运行时,操作系统会提供一种假象,就好像系统上只有 这个程序在运行,看上去只有这个程序在使用处理器、主存和 I/O 设备。处理器看上去就像在不 间断地一条接一条地执行程序中的指令,即该程序的代码和数据是系统存储器中唯一的对象。这 些假象是通过进程的概念来实现的,进程是计算机科学中最重要和最成功的概念之一。 进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程, 而每个进程都好像在独占地使用硬件。而并发运行,则是说一个进程的指令和另一个进程的指令 是交错执行的。在大多数系统中,需要运行的进程数是多于可以运行它们的 CPU 个数的。传统 系统在一个时刻只能执行一个程序,而先进的多核处理器同时能够执行多个程序。无论是在单核 还是多核系统中,一个 CPU 看上去都像是在并发地执行多个进程,这是通过处理器在进程间切 换来实现的。操作系统实现这种交错执行的机制称为上下文切换。为了简化讨论,我们只考虑包 含一个 CPU 的单处理器系统的情况。我们会在 1.9.1 节讨论多处理器系统。 操作系统保持跟踪进程运行所需的所有状态信息。这种状态,也就是上下文,它包括许多信 息,例如 PC 和寄存器文件的当前值,以及主存的内容。在任何一个时刻,单处理器系统都只能 执行一个进程的代码。当操作系统决定要把控制权从当前进程转移到某个新进程时,就会进行上 下文切换,即保存当前进程的上下文、恢复新进程的上下文,然后将控制权传递到新进程。新进 程就会从上次停止的地方开始。图 1-12 展示了示例 hello 程序运行场景的基本理念。 示例场景中有两个并发的进程 :外壳进程和 hello 进程。起初,只有外壳进程在运行,即 等待命令行上的输入。当我们让它运行 hello 程序时,外壳通过调用一个专门的函数,即系统 调用,来执行我们的请求,系统调用会将控制权传递给操作系统。操作系统保存外壳进程的上下 文,创建一个新的 hello 进程及其上下文,然后将控制权传递给新的 hello 进程。hello 进 程终止后,操作系统恢复外壳进程的上下文,并将控制权传回给它,外壳进程将继续等待下一个 命令行输入。 实现进程这个抽象概念需要低级硬件和操作系统软件之间的紧密合作。我们将在第 8 章揭示 这项工作的原理,以及应用程序是如何创建和控制它们的进程的。 图 1-12 进程的上下文切换 正文.indd 11 2010-10-19 14:17:25  12  第 1 章 计算机系统漫游  1.7.2 线程 尽管通常我们认为一个进程只有单一的控制流,但是在现代系统中,一个进程实际上可以由 多个称为线程的执行单元组成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数 据。由于网络服务器对并行处理的需求,线程成为越来越重要的编程模型,因为多线程之间比多 进程之间更容易共享数据,也因为线程一般来说都比进程更高效。当有多处理器可用的时候,多 线程也是一种使程序可以更快运行的方法,我们将在 1.9.1 节讨论这个问题。你将在第 12 章学习 到并发的基本概念,以及如何写线程化的程序。 1.7.3 虚拟存储器 虚拟存储器是一个抽象概念,它为每个进程提供了一个假象,即每个进程都在独占地使用主 存。每个进程看到的是一致的存储器,称为虚拟地址空间。图 1-13 所示的是 Linux 进程的虚拟 地址空间(其他 Unix 系统的设计也与此类似)。在 Linux 中,地址空间最上面的区域是为操作系 统中的代码和数据保留的,这对所有进程来说都是一样的。地址空间的底部区域存放用户进程定 义的代码和数据。请注意,图中的地址是从下往上增大的。 每个进程看到的虚拟地址空间由大量准确定义的区构成,每个区都有专门的功能。本书的后 续章节将介绍更多的有关这些区的知识,但是先简单了解每一个区将是非常有益的。我们是从最 低的地址开始,逐步向上介绍。 • 程序代码和数据。对于所有的进程来说,代码是从同一固定地址开始,紧接着的是和 C 全 局变量相对应的数据位置。代码和数据区是直接按照可执行目标文件的内容初始化的,在 示例中就是可执行文件 hello。第 7 章我们研究链接和加载,你将会学习到更多有关地址 空间的内容。 • 堆。代码和数据区后紧随着的是运行时堆。代码和数据区是在进程一开始运行时就被规定 了大小,与此不同,当调用如 malloc 和 free 这样的 C 标准库函数时,堆可以在运行时 动态地扩展和收缩。第 9 章学习管理虚拟存储器时,我们将更详细地研究堆。 • 共享库。大约在地址空间的中间部分是一块用来存放像 C 标准库和数学库这样共享库的代 码和数据的区域。共享库的概念非常强大,也相当难懂。第 7 章介绍动态链接时,我们将 图 1-13 进程的虚拟地址空间 正文.indd 12 2010-10-19 14:17:25 第 1 章 计算机系统漫游  13  学习共享库是如何工作的。 • 栈。位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数调用。和堆一样,用 户栈在程序执行期间可以动态地扩展和收缩。特别是每次我们调用一个函数时,栈就会增 长 ;从一个函数返回时,栈就会收缩。在第 3 章,你将学习编译器是如何使用栈的。 • 内核虚拟存储器。内核总是驻留在内存中,是操作系统的一部分。地址空间顶部的区域是 为内核保留的,不允许应用程序读写这个区域的内容或者直接调用内核代码定义的函数。 虚拟存储器的运作需要硬件和操作系统软件之间精密复杂的交互,包括对处理器生成的每个 地址的硬件翻译。其基本思想是把一个进程虚拟存储器的内容存储在磁盘上,然后用主存作为磁 盘的高速缓存。第 9 章将解释虚拟存储器如何工作,以及它为什么对现代系统的运行如此重要。 1.7.4 文件 文件就是字节序列,仅此而已。每个 I/O 设备,包括磁盘、键盘、显示器,甚至网络,都可 以视为文件。系统中的所有输入输出都是通过使用一小组称为 Unix I/O 的系统函数调用读写文 件来实现的。 文件这个简单而精致的概念其内涵是非常丰富的,因为它向应用程序提供了一个统一的视 角,来看待系统中可能含有的所有各式各样的 I/O 设备。例如,处理磁盘文件内容的应用程序员 非常幸福,因为他们无需了解具体的磁盘技术。进一步说,同一个程序可以在使用不同磁盘技术 的不同系统上运行。第 10 章将介绍 Unix I/O。 Linux 项目 1991 年 8 月,芬兰研究生 Linus Torvalds 谨慎地发布了一个新的类 Unix 的操作系统内核, 内容如下 : 来自 :torvalds@klaava.Helsinki.FI (Linus Benedict Torvalds) 新闻组 :comp.os.minix 主题 :在 minix 中你最想看到什么? 摘要 :关于我的新操作系统的小调查 时间 :1991 年 8 月 25 日 20:57:08 格林尼治时间 每个使用 minix 的朋友,你们好。 我正在做一个(免费的)用在 386(486)AT 上的操作系统(只是业余爱好,它不会像 GNU 那样庞大和专业)。这个想法从 4 月份起就开始酝酿,现在快要完成了。我希望得到各位对 minix 的任何反馈意见,因为我的操作系统在某些方面是与它相类似的(其中包括相同的文件系 统的物理设计(因为某些实际的原因))。 我现在已经移植了 bash(1.08)和 gcc(1.40),并且看上去能运行。这意味着我需要用几个 月的时间使它变得更实用一些,并且我想知道大多数人想要的特性。欢迎提出任何建议,但是我 无法保证都能实现。: - ) Linus (torvalds@kruuna.helsinki.fi) 接下来,如他们所说,这就成为了历史。Linux 逐渐发展成为一个技术和文化现象。通过结 合 GNU 项目的力量,Linux 项目发展成为一个完整的、符合 Posix 标准的 Unix 操作系统的版本, 包括内核和所有支撑的基础设施。从手持设备到大型计算机,Linux 在范围如此广泛的计算机上 得到了应用。IBM 的一个工作组甚至把 Linux 移植到了一块腕表中! 1.8 系统之间利用网络通信 系统漫游至此,我们一直是把系统视为一个孤立的硬件和软件的集合体。实际上,现代系 正文.indd 13 2010-10-19 14:18:03  14  第 1 章 计算机系统漫游  统经常通过网络和其他系统连接到一起。从一个单独的系统来看,网络可视为一个 I/O 设备,如 图 1-14 所示。当系统从主存将一串字节复制到网络适配器时,数据流经过网络到达另一台机器, 而不是其他地方,例如本地磁盘驱动器。相似地,系统可以读取从其他机器发送来的数据,并把 数据复制到自己的主存。 随着 Internet 这样的全球网络的出现,将一台主机的信息复制到另外一台主机已经成为计算 机系统最重要的用途之一。例如,电子邮件、即时通信、万维网、FTP 和 telnet 这样的应用都是 基于网络复制信息的功能。 继续讨论我们的 hello 示例,我们可以使用熟悉的 telnet 应用在一个远程主机上运行 hello 程序。假设用本地主机上的 telnet 客户端连接远程主机上的 telnet 服务器。在我们登录到 远程主机并运行外壳后,远端的外壳就在等待接收输入命令。从这点开始,远程运行 hello 程 序包括(如图 1-15 所示)的五个基本步骤。 当我们在 telnet 客户端键入“hello”字符串并敲下回车键后,客户端软件就会将这个字符 串发送到 telnet 的服务器。telnet 服务器从网络上接收到这个字符串后,会把它传递给远端外壳 程序。接下来,远端外壳运行 hello 程序,并将输出行返回给 telnet 服务器。最后,telnet 服务 器通过网络把输出串转发给 telnet 客户端,客户端就将输出串输出到我们的本地终端上。 这种客户端和服务器之间交互的类型在所有的网络应用中是非常典型的。在第 11 章,你将 学到如何构造网络应用程序,并利用这些知识创建一个简单的 Web 服务器。 图 1-14 网络也是一种 I/O 设备 图 1-15 利用 telnet 通过网络远程运行 hello 程序 正文.indd 14 2010-10-19 14:18:04 第 1 章 计算机系统漫游  15  1.9 重要主题 在此,总结一下我们旋风式的系统漫游。这次讨论得出一个很重要的观点,那就是系统不仅 仅只是硬件。系统是硬件和系统软件互相交织的集合体,它们必须共同协作以达到运行应用程序 的最终目的。本书的余下部分会讲述硬件和软件的详细内容,通过了解这些详细内容,你可以写 出更快速、更可靠和更安全的程序。 我们在此强调几个贯穿计算机系统所有方面的重要概念作为本章的结束。我们还会在本书中 的多处讨论这些概念的重要性。 1.9.1 并发和并行 数字计算机的整个历史中,有两个需求是驱动进步的持续动力 :一个是我们想要计算机做得 更多,另一个是我们想要计算机运行得更快。当处理器同时能够做更多事情时,这两个因素都会 改进。我们用的术语并发(concurrency)是一个通用的概念,指一个同时具有多个活动的系统 ; 而术语并行(parallelism)指的是用并发使一个系统运行得更快。并行可以在计算机系统的多个 抽象层次上运用。在此,我们按照系统层次结构中由高到低的顺序重点强调三个层次。 1. 线程级并发 构建进程这个抽象,我们能够设计出同时执行多个程序的系统,这就导致了并发。使用线 程,我们甚至能够在一个进程中执行多个控制流。从 20 世纪 60 年代初期出现时间共享以来,计 算机系统中就开始有了对并发执行的支持。传统意义上,这种并发执行只是模拟出来的,是通 过使一台计算机在它正在执行的进程间快速切换的方式实现的,就好像一个杂技演员保持多个球 在空中飞舞。这种并发形式允许多个用户同时与系统交互,例如,当许多人想要从一个 Web 服 务器获取页面时。它还允许一个用户同时从事多个任务,例如,在一个窗口中开启 Web 浏览器, 在另一窗口中运行字处理器,同时又播放音乐。在以前,即使处理器必须在多个任务间切换,大 多数实际的计算也都是由一个处理器来完成的。这种配置称为单处理器系统。 当构建一个由单操作系统内核控制的多处理器 组成的系统时,我们就得到了一个多处理器系统。 其实从 20 世纪 80 年代开始,在大规模的计算中就 采用了这种系统,但是直到最近,随着多核处理器 和超线程(hyperthreading)的出现,这种系统才变 得常见。图 1-16 列出了这些不同处理器类型的分类。 多核处理器是将多个 CPU(称为“核”)集 成到一个集成电路芯片上。图 1-17 描述的是 Intel Core i7 处理器的组织结构,其中微处理器芯片有 4 个 CPU 核,每个核都有自己的 L1 和 L2 高速缓存, 但是它们共享更高层次的高速缓存,以及到主存的接口。工业界的专家预言他们能够将几十个、 最终会是上百个核做到一个芯片上。 超线程,有时称为同时多线程(simultaneous multi-threading),是一项允许一个 CPU 执行多 个控制流的技术。它涉及 CPU 某些硬件有多个备份,比如程序计数器和寄存器文件 ;而其他的 硬件部分只有一份,比如执行浮点算术运算的单元。常规的处理器需要大约 20 000 个时钟周期 做不同线程间的转换,而超线程的处理器可以在单个周期的基础上决定要执行哪一个线程。这使 得 CPU 能够更好地利用它的处理资源。例如,假设一个线程必须等到某些数据被装载到高速缓 存中,那 CPU 就可以继续去执行另一个线程。举例来说,Intel Core i7 处理器可以让一个核执行 两个线程,所以一个 4 核的系统实际上可以并行地执行 8 个线程。 图 1-16 不同的处理器配置分类。随着多核处 理器和超线程的出现,多处理器变得普遍了 正文.indd 15 2010-10-19 14:18:04  16  第 1 章 计算机系统漫游  多处理器的使用可以从两个方面提高系统性能。首先,它减少了在执行多个任务时模拟并发 的需要。正如前面提到的,即使是只有一个用户使用的个人计算机也需要并发地执行多个活动。 其次,它可以使应用程序运行得更快。当然,这必须要求程序是以多线程方式来书写的,这些线 程可以并行地高效执行。因此,虽然并发原理的形成和研究已经超过 50 年的时间了,但是直到 多核和超线程系统的出现才极大地激发了人们的一种愿望,即找到书写应用程序的方法利用硬件 开发线程级并行性。第 12 章将更深入地探讨并发,以及使用并发来提供处理器资源的共享,使 得程序的执行允许有更多的并行。 2. 指令级并行 在较低的抽象层次上,现代处理器可以同时执行多条指令的属性称为指令级并行。早期的微 处理器,如 1978 年的 Intel 8086,需要多个(通常是 3 ~ 10 个)时钟周期来执行一条指令。比 较先进的处理器可以保持每个时钟周期 2 ~ 4 条指令的执行速率。其实每条指令从开始到结束需 要长得多的时间,大约 20 个或者更多的周期,但是处理器使用了非常多的聪明技巧来同时处理 多达 100 条的指令。在第 4 章,我们将研究流水线(pipelining)的使用。在流水线中,将执行 一条指令所需要的活动划分成不同的步骤,将处理器的硬件组织成一系列的阶段,每个阶段执行 一个步骤。这些阶段可以并行地操作,用来处理不同指令的不同部分。我们会看到一个相当简单 的硬件设计,它能够达到接近于一个时钟周期一条指令的执行速率。 如果处理器可以达到比一个周期一条指令更快的执行速率,就称之为超标量(superscalar) 处理器。大多数现代处理器都支持超标量操作。第 5 章,将介绍超标量处理器的高级模型。应用 程序员可以用这个模型来理解他们程序的性能。然后,他们就能写出拥有更高程度的指令级并行 性的程序代码,因而也运行得更快。 3. 单指令、多数据并行 在最低层次上,许多现代处理器拥有特殊的硬件,允许一条指令产生多个可以并行执行的操 作,这种方式称为单指令、多数据,即 SIMD 并行。例如,较新的 Intel 和 AMD 处理器都具有 图 1-17 Intel Core i7 的组织结构。4 个处理器核集成到一个芯片上 正文.indd 16 2010-10-19 14:18:04 第 1 章 计算机系统漫游  17  并行地对 4 对单精度浮点数(C 数据类型 float)做加法的指令。 提供这些 SIMD 指令多是为了提高处理影像、声音和视频数据应用的执行速度。虽然有些编 译器试图从 C 程序中自动抽取 SIMD 并行性,但是更可靠的方法是使用编译器支持的特殊向量 数据类型来写程序,例如 GCC 就支持向量数据类型。作为对比较通用的程序优化讲述的补充, 在网络旁注 OPT:SIMD 中描述了这种编程方式。 1.9.2 计算机系统中抽象的重要性 抽象的使用是计算机科学中最为重要的概念之一。例如,为一组函数规定一个简单的应用 程序接口(API)就是一个很好的编程习惯,程序员无需了解它内部的工作便可以使用这些 代码。不同的编程语言提供不同形式和等级的抽象支持,例如 Java 类的声明和 C 语言的函 数原型。 我们已经介绍了计算机系统中使用的几个抽象,如图 1-18 所示。在处理器里,指令集结构 提供了对实际处理器硬件的抽象。使用这个抽象,机器代码程序表现得就好像它是运行在一个一 次只执行一条指令的处理器上。底层的硬件比抽象描述的要复杂精细得多,它并行地执行多条指 令,但又总是与那个简单有序的模型保持一致。只要执行模型一样,不同的处理器实现也能执行 同样的机器代码,而又提供不同的开销和性能。 在学习操作系统时,我们介绍了三个抽象 :文件是对 I/O 的抽象,虚拟存储器是对程序存储 器的抽象,而进程是对一个正在运行的程序的抽象。我们再增加一个新的抽象 :虚拟机,它提供 对整个计算机(包括操作系统、处理器和程序)的抽象。虚拟机的思想是 IBM 在 20 世纪 60 年 代提出来的,但是最近才显示出其管理计算机方式上的优势,因为一些计算机必须能够运行为不 同操作系统(例如,Microsoft Windows、MacOS 和 Linux)或同一操作系统的不同版本而设计的 程序。 在本书后续的章节中,我们会具体介绍这些抽象。 1.10 小结 计算机系统是由硬件和系统软件组成的,它们共同协作以运行应用程序。计算机内部的信息 被表示为一组组的位,它们依据上下文有不同的解释方式。程序被其他程序翻译成不同的形式, 开始时是 ASCII 文本,然后被编译器和链接器翻译成二进制可执行文件。 处理器读取并解释存放在主存里的二进制指令。因为计算机把大量的时间用于存储器、I/O 设备和 CPU 寄存器之间复制数据,所以将系统中的存储设备划分成层次结构—CPU 寄存器在 顶部,接着是多层的硬件高速缓存存储器、DRAM 主存和磁盘存储器。在层次模型中,位于更 注 :计算机系统中的一个重大主题就是提供不同层次的抽象表示,来隐藏实际实现的复杂性。 图 1-18 计算机系统提供的一些抽象 正文.indd 17 2010-10-19 14:18:05  18  第 1 章 计算机系统漫游  高层的存储设备比低层的存储设备要更快,单位比特开销也更高。层次结构中较高层次存储设备 可以作为较低层次设备的高速缓存。通过理解和运用这种存储层次结构的知识,程序员可以优化 C 程序的性能。 操作系统内核是应用程序和硬件之间的媒介。它提供三个基本的抽象 :1)文件是对 I/O 设备的抽象 ;2)虚拟存储器是对主存和磁盘的抽象 ;3)进程是对处理器、主存和 I/O 设备的 抽象。 最后,网络提供了计算机系统之间通信的手段。从特殊系统的角度来看,网络就是一种 I/O 设备。 参考文献说明 Ritchie 写了关于早期 C 和 Unix 的有趣的第一手资料 [87,88]。Ritchie 和 Thompson 提供了 最早出版的 Unix 资料 [89]。Silberschatz、Gavin 和 Gagne[98] 提供了关于 Unix 不同版本的详尽 历史。GNU(www.gnu.org)和 Linux(www.linux.org)的网站上有大量的当前信息和历史资料。 Posix 标准可以在线获得(www.unix.org)。 正文.indd 18 2010-10-19 14:18:05 第一部分 Computer Systems :A Programmer’s Perspective,2E 程序结构和执行 我们对计算机系统的探索是从学习计算机本身开始的,它由处理器和 存储器子系统组成。在核心部分,我们需要方法来表示基本数据类型,比 如整数和实数运算的近似值。然后,我们考虑机器级指令如何操作这样的 数据,以及编译器如何将 C 程序翻译成这样的指令。接下来,研究几种实 现处理器的方法,帮助我们更好地了解硬件资源是如何被用来执行指令。 一旦理解了编译器和机器级代码,我们就能通过编写最高性能的 C 程序, 来分析如何最大化程序的性能。本部分以存储器子系统的设计作为结束, 这是现代计算机系统最复杂的部分之一。 本书的这一部分将领着你深入了解如何表示和执行应用程序。你将学 会一些技巧,它们将帮助你写出安全、可靠且充分利用计算资源的程序。 正文.indd 19 2010-10-19 14:18:05 第2章 Computer Systems :A Programmer’s Perspective,2E 信息的表示和处理 现代计算机存储和处理的信息以二值信号表示。这些微不足道的二进制数字,或者称为位 (bit),奠定了数字革命的基础。大家熟悉并且使用了 1000 多年的十进制(以十为基数)起源于 印度,12 世纪被阿拉伯数学家改进,并在 13 世纪被意大利数学家 Leonardo Pisano(公元 1170- 1250,更为大家所熟知的名字是 Fibonacci)带到西方。对于有 10 个手指的人类来说,使用十进 制表示法是很自然的事情,但是当构造存储和处理信息的机器时,二进制的值工作得更好。二值 信号能够很容易地被表示、存储和传输,例如,可以表示为穿孔卡片上有洞或无洞、导线上的高 电压或低电压,或者顺时针或逆时针的磁场。对二值信号进行存储和执行计算的电子电路非常简 单和可靠,制造商能够在一个单独的硅片上集成数百万甚至数十亿个这样的电路。 孤 立 地 讲, 单 个 的 位 不 是 非 常 有 用。 然 而, 当 把 位 组 合 在 一 起, 再 加 上 某 种 解 释 (interpretation),即给不同的可能位模式赋予含义,我们就能够表示任何有限集合的元素。比如, 使用一个二进制数字系统,我们能够用位组来编码非负数。通过使用标准的字符码,我们能够对 文档中的字母和符号进行编码。在本章中,我们将讨论这两种编码,以及表示负数和对实数近似 值的编码。 我们研究三种最重要的数字表示。无符号(unsigned)编码基于传统的二进制表示法,表示 大于或者等于零的数字。补码(two’s-complement)编码是表示有符号整数的最常见的方式,有 符号整数就是可以为正或者为负的数字。浮点数(floating-point)编码是表示实数的科学记数法 的以二为基数的版本。计算机用这些不同的表示方法实现算术运算,例如加法和乘法,类似于对 应的整数和实数运算。 计算机的表示法是用有限数量的位来对一个数字编码,因此,当结果太大以至不能表示时, 某些运算就会溢出(overflow)。溢出会导致某些令人吃惊的后果。例如,现在的大多数计算机 (使用 32 位来表示数据类型 int),计算表达式 200*300*400*500 会得出结果 -884 901 888。 这违背了整数运算的特性,计算一组正数的乘积不应产生一个为负的结果。 另一方面,整数的计算机运算满足人们所熟知的真正整数运算的定律。例如,利用乘法的结 合律和交换律,计算下面任何一个 C 表达式,都会得出结果 -884 901 888 : (500*400)*(300*200) ((500*400)*300)*200 ((200*500)*300)*400 400*(200*(300*500)) 计算机可能没有产生期望的结果,但是至少结果是一致的! 浮点运算有完全不同的数学属性。虽然溢出会产生特殊的值 + ∞,但是一组正数的乘积 总是正的。由于表示的精度有限,浮点运算是不可结合的。例如,在大多数机器上,C 表达式 (3.14+1e20)-1e20 求得的值会是 0.0,而 3.14+(le20-le20)求得的值会是 3.14。整数 运算和浮点数运算会有不同的数学属性是因为它们处理数字表示有限性的方式不同—整数的表 示虽然只能编码一个相对较小的数值范围,但是这种表示是精确的 ;而浮点数虽然可以编码一个 较大的数值范围,但是这种表示只是近似的。 通过研究数字的实际表示,我们能够理解可以表示的值的范围和不同算术运算的属性。为了 使编写的程序能在全部数值范围内正确工作,而且具有可以跨越不同机器、操作系统和编译器组 正文.indd 20 2010-10-19 14:18:05 第 2 章 信息的表示和处理  21  合的可移植性,了解这些属性是非常重要的。后面我们会讲到,大量计算机的安全漏洞都是由于 计算机算术运算的微妙细节引发的。在早期,当人们碰巧触发了程序漏洞,只会给人们带来一些 不便 ;但是现在,有许多的黑客企图利用他们能找到的任何漏洞,不经过授权就进入他人的系 统。这就要求程序员有更多的责任和义务,去了解他们的程序如何工作,以及如何被迫产生不良 的行为。 计算机用几种不同的二进制表示形式来编码数值。第 3 章随着进入机器级编程,你需要熟悉 这些表示方式。在本章中,我们描述这些编码,并且教你如何推出数字的表示。 通过直接操作数字的位级表示,我们得到了几种进行算术运算的方式。理解这些技术对于理 解编译器产生的机器级代码是很重要的,编译器会试图优化算术表达式求值的性能。 我们对这部分内容的处理是基于一组核心的数学原理的。我们从编码的基本定义开始,然后 得出一些属性,例如可表示的数字的范围、它们的位级表示以及算术运算的属性。相信从这样一 个抽象的观点来分析这些内容,对你来说是很重要的,因为程序员需要对计算机运算与更为人熟 悉的整数和实数运算之间的关系有清晰的理解。 怎样阅读本章 如果你觉得等式和公式令人生畏,不要让它阻止你学习本章的内容!为了内容的完整性,我 们提供全部的数学概念的推导,但是阅读这些内容的最好方法是在你首次阅读时跳过这些推导。 但是,要研究我们提供的例题,并且要做完所有的练习题。这些例题会让你对概念有一些感性的 认识,并且练习题让你能够主动学习,帮助你理论联系实际。有了这些例题和练习题作为背景知 识,再回头看那些推导,你会发现理解起来会容易许多。同时,请放心,每个掌握了高中代数知 识的人都具备理解这些内容所需要的数学技能。 C++ 编程语言建立在 C 语言的基础之上,它们使用完全相同的数字表示和运算。本章中关 于 C 的所有内容对 C++ 都有效。另一方面,Java 语言创造了一套新的数字表示和运算标准。C 标准的设计允许多种实现方式,而 Java 标准在数据的格式和编码上是非常精确具体的。本章中 多处着重介绍了 Java 支持的表示和运算。 C 编程语言的演变 前面提到过,C 编程语言是贝尔实验室的 Dennis Ritchie 最早开发出来的,目的是和 Unix 操 作系统一起使用(Unix 也是贝尔实验室开发的)。在那个时候,大多数系统程序,例如操作系 统,为了访问不同数据类型的低级表示,都必须用大量的汇编代码编写。比如说,像 malloc 库函数提供的内存分配那样的功能,用当时的其他高级语言是无法编写的。 Brian Kernighan 和 Dennis Richie 的著作的第 1 版 [57] 记录了最初贝尔实验室的 C 语言版 本。随着时间的推移,经过多个标准化组织的努力,C 语言也在不断地演变。1989 年,美国国 家标准学会下的一个工作组推出了 ANSI C 标准,对最初的贝尔实验室的 C 语言做了重大修改。 ANSI C 与贝尔实验室的 C 有了很大的不同,尤其是函数声明的方式。Brian Kernighan 和 Dennis Richie 在著作的第 2 版 [58] 中描述了 ANSI C,这本书至今仍然被公认为是关于 C 语言最好的参 考手册之一。 国际标准化组织接管了对 C 语言进行标准化的任务,在 1990 年推出了几乎和 ANSI C 一样 的版本,称为“ISO C90”。该组织在 1999 年又对 C 语言做了更新,得到“ISO C99”。这一版 本,引入了一些新的数据类型,对使用不符合英语语言字符的文本字符串提供了支持。 GNU 编译器套装(GNU Compiler Collection,GCC)可以基于不同的命令行选项,依照多 个不同版本的 C 语言规则来编译程序,如图 2-1 所示。例如,根据 ISO C99 来编译程序 prog.c, 正文.indd 21 2010-10-19 14:18:05  22  第一部分 程序结构和执行  我们就使用命令行 : unix> gcc -std=c99 prog.c 编译选项 -ansi 和 -std=c89 的用法是一样的—会根据 ANSI 或者 ISO C90 标准来编译程 序。(C90 有时也称为“C89”,因为它的标准化工作是从 1989 年开始的。)编译选项 -std=c99 会让编译器按照 ISO C99 的规则进行编译。 本书中由于没有指定任何编译选项,程序会按照基于 ISO C90 的 C 语言版本进行编译,但 是又包括一些 C99 的特性,一些 C++ 的特性,还有一些是与 GCC 相关的特性。可以显式地用编 译选项 -std=gnu89 来指定这个 ISO C90 版本。GNU 项目正在开发一个结合了 ISO C99 和其他 一些特性的版本,可以通过命令行选项 -std=gnu99 来指定。(目前,这个实现还未完成。)以 后,这个版本会成为默认的版本。 2.1 信息存储 大多数计算机使用 8 位的块,或者字节(byte),作为 最小的可寻址的存储器单位,而不是在存储器中访问单独 的位。机器级程序将存储器视为一个非常大的字节数组, 称为虚拟存储器(virtual memory)。存储器的每个字节都由 一个唯一的数字来标识,称为它的地址(address),所有可 能地址的集合称为虚拟地址空间(virtual address space)。顾 名思义,这个虚拟地址空间只是一个展现给机器级程序的 概念性映像。实际的实现(见第 9 章)是将随机访问存储器(RAM)、磁盘存储器、特殊硬件和操作 系统软件结合起来,为程序提供一个看上去统一的字节数组。 接下来的几章,我们将讲述编译器和运行时系统是如何将存储器空间划分为更可管理的单 元,以存放不同的程序对象(program object),即程序数据、指令和控制信息。可以用各种机制 来分配和管理程序不同部分的存储。这种管理完全是在虚拟地址空间里完成的。例如,C 语言中 一个指针的值(无论它是指向一个整数、一个结构或是某个其他程序对象)都是某个存储块的第 一个字节的虚拟地址。C 编译器还把每个指针和类型信息联系起来,这样就可以根据指针值的类 型,生成不同的机器级代码来访问存储在指针所指向位置处的值。尽管 C 编译器维护着这个类 型信息,但是它生成的实际机器级程序并不包含关于数据类型的信息。每个程序对象可以简单地 视为一个字节块,那么程序本身就是一个字节序列。 给 C 语言初学者 :C 语言中指针的角色 指针是 C 语言的一个重要特性。它提供了引用数据结构(包括数组)的元素的机制。与变 量类似,指针也有两个方面 :值和类型。它的值表示某个对象的位置,而它的类型表示那个位置 上所存储对象的类型(比如整数或者浮点数)。 2.1.1 十六进制表示法 一个字节由 8 位组成。在二进制表示法中,它的值域是 000000002 ~ 111111112 ;如果用十 进制整数表示,它的值域就是 010 ~ 25510。两种表示法对于描述位模式来说都不是非常方便。二 进制表示法太冗长,而十进制表示法与位模式的互相转化又很麻烦。替代的方法是,以 16 为基 数,或者叫十六进制(hexadecimal)数,来表示位模式。十六进制(简写为“hex”)使用数字 ‘0’~‘9’,以及字符‘A’~‘F’来表示 16 个可能的值。图 2-2 展示了 16 个十六进制数字 对应的十进制值和二进制值。用十六进制书写,一个字节的值域为 0016 ~ FF16。 图 2-1 向 GCC 指定不同的 C 语言版本 正文.indd 22 2010-10-19 14:18:06 第 2 章 信息的表示和处理  23  在 C 语言中,以 0x 或 0X 开头的数字常量被认为是十六进制的值。字符‘A’~‘F’既可以 是大写,也可以是小写,甚至是大小写混合。例如,我们可以将数字 FA1D37B16 写作 0xFA1D37B, 或者 0xfald37b,也可以写作 0xFa1D37b。在本书中,我们将使用 C 表示法来表示十六进制值。 十六进制数字 0 1 2 3 4 5 6 7 十进制值 0 1 2 3 4 5 6 7 二进制值 0000 0001 0010 0011 0100 0101 0110 0111 十六进制数字 8 9 A B C D E F 十进制值 8 9 10 11 12 13 14 15 二进制值 1000 1001 1010 1011 1100 1101 1110 1111 图 2-2 十六进制表示法。每个十六进制数字都对 16 个值中的一个进行了编码 编写机器级程序的一个常见任务就是在位模式的十进制、二进制和十六进制表示之间人工进 行转换。二进制和十六进制之间的转换比较简单直接,因为可以一次执行一个十六进制数字的转 换。数字的转换可以参考如图 2-2 所示的表。一个简单的窍门是,记住十六进制数字 A、C 和 F 相对应的十进制值。而对于把十六进制值 B、D 和 E 转换成十进制值时,则可以通过计算它们与 前三个值的相对关系来完成。 比如,假设给你一个数字 0x173A4C,可以通过展开每个十六进制数字,将它转换为二进 制格式,如下所示 : 十六进制 1 7 3 A 4 C 二进制 0001 0111 0011 1010 0100 1100 这样就得到了二进制表示 000101110011101001001100。 反过来,如果给定一个二进制数字 1111001010110110110011,你可以首先把它分为每 4 位一 组,再把它转换为十六进制。不过要注意,如果位的总数不是 4 的倍数,最左边的一组可以少于 4 位,前面用 0 补足,然后将每个 4 位组转换为相应的十六进制数字 : 二进制 11 1100 1010 1101 1011 0011 十六进制 3 C A D B 3 练习题 2.1 完成下列数字转换 : A. 将 0x39A7F8 转换为二进制。 B. 将二进制 1100100101111011 转换为十六进制。 C. 将 0xD5E4C 转换为二进制。 D. 将二进制 1001101110011110110101 转换为十六进制。 当值 x 是 2 的非负整数 n 次幂时,也就是 x = 2n,我们可以很容易地将 x 写成十六进制形式, 只要记住 x 的二进制表示就是 1 后面跟 n 个 0。十六进制数字 0 代表 4 个二进制 0。所以,当 n 表 示成 i + 4j 的形式,其中 0 ≤ i ≤ 3 时,我们可以把 x 写成开头的十六进制数字为 1(i = 0)、2(i = 1)、 4(i = 2)或者 8(i = 3),后面跟随着 j 个十六进制的 0。比如,x = 2048 = 211,我们有 n = 11 = 3 + 4×2,从而得到十六进制表示 0x800。 练习题 2.2 填写下表中的空白项,给出 2 的不同次幂的二进制和十六进制表示 : n 2n(十进制) 2n(十六进制) 9 512 0x200 19 16 384 0x10000 17 32 0x80 正文.indd 23 2010-10-19 14:18:06  24  第一部分 程序结构和执行  十进制和十六进制表示之间的转换需要使用乘法或者除法来处理一般情况。将一个十进制数 字 x 转换为十六进制,可以反复地用 16 除 x,得到一个商 q 和一个余数 r,也就是 x = q×16 + r。 然后,我们用十六进制数字表示的 r 作为最低位数字,并且通过对 q 反复进行这个过程得到剩下 的数字。例如,考虑十进制 314156 的转换 : 314156 = 19634×16 + 12  (C) 19634 = 1227×16 + 2    ( 2) 1227 = 76×16 + 11    (B) 76 = 4×16 + 12    (C) 4 = 0×16 + 4    (4) 从这里,我们能读出十六进制表示为 0x4CB2C。 反过来,将一个十六进制数字转换为十进制数字,我们可以用相应的 16 的幂乘以每个十六 进制数字。比如,给定数字 0x7AF,我们计算它对应的十进制值为 7×162 + 10×16 + 15=7×256 + 10×16+15=1792+160+15=1967。 练习题 2.3 一个字节可以用两个十六进制数字来表示。填写下表中缺失的项,给出不同字节模式的 十进制、二进制和十六进制值。 十进制 二进制 十六进制 0 0000 0000 0x00 167 62 188 0011 0111 1000 1000 1111 0011 0x52 0xAC 0xE7 十进制和十六进制间的转换 较大数值的十进制和十六进制之间的转换,最好是让计算机或者计算器来完成。例如,下面 的 Perl 语言脚本将(命令行给出的)一列数字从十进制转换为十六进制 : 一旦这个文件被设置为可执行的,命令 unix> ./d2h 100 500 751 会产生输出 : 100=0x64 500=0x1f4 751=0x2ef 正文.indd 24 2010-10-19 14:18:06 第 2 章 信息的表示和处理  25  类似地,下面的脚本将十六进制转换为十进制 : 练习题 2.4 不将数字转换为十进制或者二进制,试着解答下面的算术题,答案要用十六进制表示。 提示 :将执行十进制加法和减法所使用的方法改成以 16 为基数。 A. 0x503c+0x8= B. 0x503c-0x40= C. 0x503c+64= D. 0x50ea-0x503c= 2.1.2 字 每台计算机都有一个字长(word size),指明整数和指针数据的标称大小(nominal size)。因 为虚拟地址是以这样的一个字来编码的,所以字长决定的最重要的系统参数就是虚拟地址空间的 最大大小。也就是说,对于一个字长为 w 位的机器而言,虚拟地址的范围为 0 ~ 2w-1,程序最多 访问 2w 个字节。 今天大多数计算机的字长都是 32 位。这就限定了虚拟地址空间为 4 千兆字节(写作 4GB),也就是说,刚刚超过 4×109 字节。虽然对大多数应用而言,这个空间足够大了,但是 现在已经有许多大规模的科学和数据库应用需要更大的存储。因此,随着存储器价格的降低, 字长为 64 位的高端机器正逐渐变得普遍起来。硬件价格随着时间降低,台式机和笔记本电脑 也会变成 64 位字长,所以我们会考虑 w 位字长的通用情况,也会考虑 w = 32 和 w = 64 的特殊 情况。 2.1.3 数据大小 计算机和编译器支持多种不同方式编码的数字格式,如整数和浮点数,以及其他长度的数 字。比如,许多机器都有处理单个字节的指令,也有处理表示为 2 字节、4 字节或者 8 字节整数 的指令,还有些指令支持表示为 4 字节和 8 字节的浮点数。 C 语言支持整数和浮点数的多种数据格式。C 的数据类型 char 表示一个单独的字节。尽管 “char”是由于它被用来存储文本串中的单个字符这一事实而得名,但它也能用来存储整数值。 C 的数据类型 int 之前还能加上限定词 short、long,以及最近的 long long,以提供各种 大小的整数表示。图 2-3 展示了为 C 语言中不同数据类型分配的字节数。准确的字节数依赖于机 器和编译器。我们给出的是 32 位和 64 位机器的典型值。可以观察到,“短”整数分配有 2 个字 节,而不加限定的 int 为 4 个字节。“长”整数使用机器的全字长。ISO C99 引入的“长长”整 数数据类型允许 64 位整数。对于 32 位机器来说,编译器必须把这种数据类型的操作编译成执行 一系列 32 位操作的代码。 图 2-3 给出了指针(例如,一个被声明为“char*”类型的变量)使用机器的全字长。大多 数机器还支持两种不同的浮点数格式 :单精度(在 C 中声明为 float)和双精度(在 C 中声明为 double)。格式分别使用 4 字节和 8 字节。 正文.indd 25 2010-10-19 14:18:07  26  第一部分 程序结构和执行  C 声明 32 位机器 64 位机器 char 1 1 short int 2 2 int 4 4 long int 4 8 long long int 8 8 char * 4 8 float 4 4 double 8 8 图 2-3 C 语言中数字数据类型的字节数 给 C 语言初学者 :声明指针 对于任何数据类型 T,声明 T *p; 表明 p 是一个指针变量,指向一个类型为 T 的对象。例如, char *p; 表示将一个指针声明为指向一个 char 类型的对象。 程序员应该力图使他们的程序在不同的机器和编译器上是可移植的。可移植性的一个方面就 是使程序对不同数据类型的确切大小不敏感。C 语言标准对不同数据类型的数字范围设置了下界 (这点在后面还将讲到),但是却没有上界。因为自 1980 年以来 32 位机器一直是标准,许多程序 的编写都假设为图 2-3 中列出的 32 位机器对应的字节分配。随着 64 位机器的日益普及,在将这 些程序移植到新机器上时,许多隐藏的对字长的依赖性就会显现出来,成为错误。比如,许多程 序员假设一个声明为 int 类型的程序对象能被用来存储一个指针。这在大多数 32 位的机器上能 正常工作,但是在一台 64 位的机器上却会导致问题。 2.1.4 寻址和字节顺序 对于跨越多字节的程序对象,我们必须建立两个规则 :这个对象的地址是什么,以及在存 储器中如何排列这些字节。在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对 象的地址为所使用字节中最小的地址。例如,假设一个类型为 int 的变量 x 的地址为 0x100, 也就是说,地址表达式 &x 的值为 0x100。那么,x 的 4 个字节将被存储在存储器的 0x100、 0x101、0x102 和 0x103 位置。 排列表示一个对象的字节有两个通用的规则。考虑一个 w 位的整数,位表示为 [xw-1, xw-2,…,x1,x0],其中 xw-1 是最高有效位,而 x0 是最低有效位。假设 w 是 8 的倍数,这些位 就能被分组成为字节,其中最高有效字节包含位 [xw-1,xw-2,…,xw-8],而最低有效字节包含位 [x7,x6,…,x0],其他字节包含中间的位。某些机器选择在存储器中按照从最低有效字节到最高 有效字节的顺序存储对象,而另一些机器则按照从最高有效字节到最低有效字节的顺序存储。前 一种规则—最低有效字节在最前面的方式,称为小端法(little endian)。大多数 Intel 兼容机都 采用这种规则。后一种规则—最高有效字节在最前面的方式,称为大端法(big endian)。大多 数 IBM 和 Sun Microsystems 的机器都采用这种规则。注意我们说的是“大多数”。这些规则并没 有严格按照企业界限来划分。比如,IBM 和 Sun 制造的个人计算机使用的是 Intel 兼容的处理器, 因此用的就是小端法。许多比较新的微处理器使用双端法(bi-endian),也就是说可以把它们配 置成作为大端或者小端的机器运行。 继续我们前面的示例,假设变量 x 类型为 int,位于地址 0x100 处,它的十六进制值为 0x01234567。地址范围为 0x100 ~ 0x103 的字节,其排列顺序依赖于机器的类型。 正文.indd 26 2010-10-19 14:18:07 第 2 章 信息的表示和处理  27  大端法 0x100 0x101 0x102 0x103 … 01 23 45 67 … 小端法 0x100 0x101 0x102 0x103 … 67 45 23 01 … 注意,在字 0x01234567 中,高位字节的十六进制值为 0x01,而低位字节值为 0x67。 令人吃惊的是,在哪种字节顺序是合适的这个问题上,人们表现得非常情绪化。实际上,术 语“little endian”(小端)和“big endian”(大端)出自 Jonathan Swift 的《格列佛游记》(Gulliver’s Travels)一书,其中交战的两个派别无法就应该从哪一端(小端还是大端)打开一个半熟的鸡蛋达 成一致。就像鸡蛋的问题一样,没有技术上的原因来选择字节顺序规则,因此,争论沦为关于社会政 治问题的争论。只要选择了一种规则并且始终如一地坚持,其实对于哪种字节排序的选择都是任意的。 “端”(endian) 的起源 以下是 Jonathan Swift 在 1726 年关于大小端之争历史的描述 : “……我下面要告诉你的是,Lilliput 和 Blefuscu 这两大强国在过去 36 个月里一直在苦战。 战争开始是由于以下的原因 :我们大家都认为,吃鸡蛋前,原始的方法是打破鸡蛋较大的一端, 可是当今皇帝的祖父小时候吃鸡蛋,一次按古法打鸡蛋时碰巧将一个手指弄破了,因此他的父 亲,当时的皇帝,就下了一道敕令,命令全体臣民吃鸡蛋时打破鸡蛋较小的一端,违令者重罚。 老百姓们对这项命令极为反感。历史告诉我们,由此曾发生过 6 次叛乱,其中一个皇帝送了命, 另一个丢了王位。这些叛乱大多都是由 Blefuscu 的国王大臣们煽动起来的。叛乱平息后,流亡 的人总是逃到那个帝国去寻救避难。据估计,先后几次有 11 000 人情愿受死也不肯去打破鸡蛋 较小的一端。关于这一争端,曾出版过几百本大部著作,不过大端派的书一直是受禁的,法律也 规定该派的任何人不得做官。”(此段译文摘自网上蒋剑锋译的《格列佛游记》第一卷第 4 章。) 在他那个时代,Swift 是在讽刺英国(Lilliput)和法国(Blefuscu)之间持续的冲突。Danny Cohen,一位网络协议的早期开创者,第一次使用这两个术语来指代字节顺序 [25],后来这个术 语被广泛接纳了。 对于大多数应用程序员来说,他们机器所使用的字节顺序是完全不可见的,无论为哪种类型的 机器编译的程序都会得到同样的结果。不过有时候,字节顺序会成为问题。首先是在不同类型的机 器之间通过网络传送二进制数据时,一个常见的问题是当小端法机器产生的数据被发送到大端法机 器或者反方向发送时会发现,接收程序字里的字节成了反序的。为了避免这类问题,网络应用程序 的代码编写必须遵守已建立的关于字节顺序的规则,以确保发送方机器将它的内部表示转换成网络 标准,而接收方机器则将网络标准转换为它的内部表示。我们将在第 11 章中看到这种转换的例子。 第二种情况是,当阅读表示整数数据的字节序列时字节顺序也很重要。通常在检查机器级程 序时会出现这种情况。举一个示例,从某个文件中摘出了下面这行代码,该文件给出了一个针对 Intel IA32 处理器的机器级代码的文本表示 : 80483bd: 01 05 64 94 04 08 add %eax, 0x8049464 这一行是由反汇编器(disassembler)生成的,反汇编器是一种确定可执行程序文件所表示的指 令序列的工具。我们将在第 3 章学习有关这些工具的更多知识,以及怎样解释像这样的行。现 在,我们只需注意这行表述了十六进制字节串 01 05 64 94 04 08 是一条指令的字节级表 示,这条指令把一个字长的数据加到存储在主存地址 0x8049464 的值上。如果取出这个序列的 正文.indd 27 2010-10-19 14:18:07  28  第一部分 程序结构和执行  后 4 个字节 :64 94 04 08,按照相反的顺序写出,我们得到 08 04 94 64。去掉开头的 0 得到值 0x8049464,这就是右边的数值。当阅读此类小端法机器生成的机器级程序表示时,经 常会将字节按照相反的顺序显示。书写字节序列的自然方式是最低位字节在左边,而最高位字节 在右边,这正好和通常书写数字时最高有效位在左边,最低有效位在右边的方式相反。 字节顺序变得可见的第三种情况是当编写规避正常的类型系统的程序时。在 C 语言中,可 以使用强制类型转换(cast)来允许以一种数据类型引用一个对象,而这种数据类型与创建这个 对象时定义的数据类型不同。大多数应用编程都强烈不推荐这种编码技巧,但是它们对系统级编 程来说是非常有用,甚至是必需的。 图 2-4 展示了一段 C 代码,它使用强制类型转换来访问和打印不同程序对象的字节表示。我 们用 typedef 将数据类型 byte_pointer 定义为一个指向类型为“unsigned char”的对 象的指针。这样一个字节指针引用一个字节序列,其中每个字节都被认为是一个非负整数。第一 个例程 show_bytes 的输入是一个字节序列的地址,它用一个字节指针以及一个字节数来指示。 show_bytes 打印出每个以十六进制表示的字节。C 格式化指令“%.2x”表明整数必须用至少 两个数字的十六进制格式输出。76 Chapter 2 Representing and Manipulating Information 1 #include 2 3 typedef unsigned char *byte_pointer; 4 5 void show_bytes(byte_pointer start, int len) { 6 int i; 7 for (i = 0; i < len; i++) 8 printf(" %.2x", start[i]); 9 printf("\n"); 10 } 11 12 void show_int(int x) { 13 show_bytes((byte_pointer) &x, sizeof(int)); 14 } 15 16 void show_float(float x) { 17 show_bytes((byte_pointer) &x, sizeof(float)); 18 } 19 20 void show_pointer(void *x) { 21 show_bytes((byte_pointer) &x, sizeof(void *)); 22 } Figure 2.4 Code to print the byte representation of program objects. This code uses casting to circumvent the type system. Similar functions are easily defined for other data types. machines such as this one. The natural way to write a byte sequence is to have the lowest-numbered byte on the left and the highest on the right, but this is contrary to the normal way of writing numbers with the most significant digit on the left and the least on the right. A third case where byte ordering becomes visible is when programs are written that circumvent the normal type system. In the C language, this can be done using a cast to allow an object to be referenced according to a different data type from which it was created. Such coding tricks are strongly discouraged for most application programming, but they can be quite useful and even necessary for system-level programming. Figure 2.4 shows C code that uses casting to access and print the byte rep- resentations of different program objects. We use typedef to define data type byte_pointer as a pointer to an object of type “unsigned char.” Such a byte pointer references a sequence of bytes where each byte is considered to be a non- negative integer. The first routine show_bytes is given the address of a sequence of bytes, indicated by a byte pointer, and a byte count. It prints the individual bytes in hexadecimal. The C formatting directive “%.2x” indicates that an integer should be printed in hexadecimal with at least two digits.       图 2-4   打印程序对象的字节表示这段代码使用强制类型转换来规避类型系统。 很容易定义针对其他数据类型的类似函数 给 C 语言初学者 :使用 typedef 命名数据类型 C 语言中的 typedef 声明提供了一种给数据类型命名的方式。这能够极大地改善代码的可 读性,因为深度嵌套的类型声明很难读懂。 typedef 的语法与声明变量的语法十分相似,除了它使用的是类型名,而不是变量名。因此, 图 2-4 中 byte_pointer 的声明和将一个变量声明为类型“unsigned char*”有相同的形式。 例如,声明 : typedef int *int_pointer; int_pointer ip; 将类型“int_pointer”定义为一个指向 int 的指针,并且声明了一个这种类型的变量 ip。 我们还可以将这个变量直接声明为 : 正文.indd 28 2010-10-19 14:18:07 第 2 章 信息的表示和处理  29  int *ip; 给 C 语言初学者 :使用 printf 格式化输出 printf 函数(还有它的同类 fprintf 和 sprintf)提供了一种打印信息的方式,这种 方式对格式化细节有相当大的控制能力。第一个参数是格式串(format string),而其余的 参数都是要打印的值。在格式串里,每个以 '%' 开始的字符序列都表示如何格式化下一个参数。 典型的示例有 :'%d' 是输出一个十进制整数,'%f' 是输出一个浮点数,而 '%c' 是输出一个 字符,其编码由参数给出。 给 C 语言初学者 :指针和数组 在函数 show_bytes(图 2-4)中,我们看到指针和数组之间紧密的联系,这将在 3.8 节中 详细描述。这个函数有一个类型为 byte_pointer(被定义为一个指向 unsigned char 的 指针)的参数 start,但是我们在第 8 行上看到数组引用 start[i]。在 C 语言中,我们能够 用数组表示法来引用指针,同时我们也能用指针表示法来引用数组元素。在这个例子中,引用 start[i] 表示我们想要读取以 start 指向的位置为起始的第 i 个位置处的字节。 过程 show_int、show_float 和 show_pointer 展示了如何使用程序 show_bytes 来 分别输出类型为 int、float 和 void * 的 C 程序对象的字节表示。可以观察到它们仅仅传递 给 show_bytes 一个指向它们参数 x 的指针 &x,且这个指针被强制类型转换为“unsigned char *”。这种强制类型转换告诉编译器,程序应该把这个指针看成指向一个字节序列,而不 是指向一个原始数据类型的对象。然后,这个指针会被看成是对象使用的最低字节地址。 给 C 语言初学者 :指针的创建和间接引用 在图 2-4 的第 13、17 和 21 行,我们看到对 C 和 C++ 中两种独有操作的使用。C 的“取地址” 运算符 & 创建一个指针。在这三行中,表达式 &x 创建了一个指向保存变量 x 的位置的指针。这 个指针的类型取决于 x 的类型,因此这三个指针的类型分别为 int*、float* 和 void**。(数 据类型 void* 是一种特殊类型的指针,没有相关联的类型信息。) 强制类型转换运算符可以将一种数据类型转换为另一种。因此,强制类型转换(byte_ pointer)&x 表明无论指针 &x 以前是什么类型,它现在就是一个指向数据类型为 unsigned char 的指针。这里给出的这些强制类型转换不会改变真实的指针,它们只是告诉编译器以新的 数据类型来看待被指向的数据。 这些过程使用 C 语言的运算符 sizeof 来确定对象使用的字节数。一般来说,表达式 sizeof(T ) 返回存储一个类型为 T 的对象所需要的字节数。使用 sizeof,而不是一个固定的 值,是向编写在不同机器类型上可移植的代码迈进了一步。 在几种不同的机器上运行如图 2-5 所示的代码,得到如图 2-6 所示的结果。我们分别使用了 以下几种机器 : Linux 32 :运行 Linux 的 Intel IA32 处理器 Windows :运行 Windows 的 Intel IA32 Sun : 运行 Solaris 的 Sun Microsystems SPARC 处理器 Linux 64 :运行 Linux 的 Intel x86-64 处理器 参数 12 345 的十六进制表示为 0x00003039。对于 int 类型的数据,除了字节顺序以外, 我们在所有机器上都得到相同的结果。特别地,我们可以看到在 Linux 32、Windows 和 Linux 64 上,最低有效字节值 0x39 最先输出,这说明它们是小端法机器 ;而在 Sun 上却最后输出,这说 明 Sun 是大端法机器。同样地,float 数据的字节,除了字节顺序以外,也都是相同的。另一方 正文.indd 29 2010-10-19 14:18:08  30  第一部分 程序结构和执行  面,指针值却是完全不同的。不同的机器 / 操作系统配置使用不同的存储分配规则。一个值得注 意的特性是 Linux 32、Windows 和 Sun 的机器使用 4 字节地址,而 Linux 64 使用 8 字节地址。 图 2-5 字节表示的示例。这段代码打印示例数据对象的字节表示 图 2-6 不同数据值的字节表示。除了字节顺序以外,int 和 float 的结果是一样的。指针值与机器相关 可以观察到,尽管浮点型和整型数据都是对数值 12 345 编码,但是它们有非常不同的字节 模式 :整型为 0x00003039,而浮点数为 0x4640E400。一般而言,这两种格式使用不同的编 码方法。如果我们将这些十六进制模式扩展为二进制形式,并且适当地将它们移位,我们就会发 现一个有 13 个相匹配的位的序列,用一串星号标识出来 : Section 2.1 Information Storage 79 Machine Value Type Bytes (hex) Linux 32 12,345 int 39 30 00 00 Windows 12,345 int 39 30 00 00 Sun 12,345 int 00 00 30 39 Linux 64 12,345 int 39 30 00 00 Linux 32 12,345.0 float 00 e4 40 46 Windows 12,345.0 float 00 e4 40 46 Sun 12,345.0 float 46 40 e4 00 Linux 64 12,345.0 float 00 e4 40 46 Linux 32 &ival int * e4 f9 ff bf Windows &ival int * b4 cc 22 00 Sun &ival int * ef ff fa 0c Linux 64 &ival int * b8 11 e5 ff ff 7f 00 00 Figure 2.6 Byte representations of different data values. Results for int and float are identical, except for byte ordering. Pointer values are machine dependent. use different conventions for storage allocation. One feature to note is that the Linux 32, Windows, and Sun machines use 4-byte addresses, while the Linux 64 machine uses 8-byte addresses. Observe that although the floating-point and the integer data both encode the numeric value 12,345, they have very different byte patterns: 0x00003039 for the integer, and 0x4640E400 for floating point. In general, these two formats use different encoding schemes. If we expand these hexadecimal patterns into binary form and shift them appropriately, we find a sequence of 13 matching bits, indicated by a sequence of asterisks, as follows: 00003039 00000000000000000011000000111001 ************* 4640E400 01000110010000001110010000000000 This is not coincidental. We will return to this example when we study floating- point formats. Practice Problem 2.5 Consider the following three calls to show_bytes: int val = 0x87654321; byte_pointer valp = (byte_pointer) &val; show_bytes(valp, 1); /* A. */ show_bytes(valp, 2); /* B. */ show_bytes(valp, 3); /* C. */ 这并不是巧合。当我们研究浮点数格式时,还将再回到这个例子。 练习题 2.5 思考下面对 show_bytes 的三次调用 : int val=0x87654321; byte_pointer valp=(byte_pointer)&val; show_bytes(valp, 1);/* A.*/ 78 Chapter 2 Representing and Manipulating Information New to C? Pointer creation and dereferencing In lines 13, 17, and 21 of Figure 2.4, we see uses of two operations that give C (and therefore C++) its distinctive character. The C “address of” operator & creates a pointer. On all three lines, the expression &x creates a pointer to the location holding the object indicated by variable x. The type of this pointer depends on the type of x, and hence these three pointers are of type int *, float *, and void **, respectively. (Data type void * is a special kind of pointer with no associated type information.) The cast operator converts from one data type to another. Thus, the cast (byte_pointer) &x indicates that whatever type the pointer &x had before, the program will now reference a pointer to data of type unsigned char. The casts shown here do not change the actual pointer; they simply direct the compiler to refer to the data being pointed to according to the new data type. These procedures use the C sizeof operator to determine the number of bytes used by the object. In general, the expression sizeof(T ) returns the number of bytes required to store an object of type T . Using sizeof rather than a fixed value is one step toward writing code that is portable across different machine types. We ran the code shown in Figure 2.5 on several different machines, giving the results shown in Figure 2.6. The following machines were used: Linux 32: Intel IA32 processor running Linux Windows: Intel IA32 processor running Windows Sun: Sun Microsystems SPARC processor running Solaris Linux 64: Intel x86-64 processor running Linux Our argument 12,345 has hexadecimal representation 0x00003039. For the int data, we get identical results for all machines, except for the byte ordering. In particular, we can see that the least significant byte value of 0x39 is printed first for Linux 32, Windows, and Linux 64, indicating little-endian machines, and last for Sun, indicating a big-endian machine. Similarly, the bytes of the float data are identical, except for the byte ordering. On the other hand, the pointer values are completely different. The different machine/operating system configurations code/data/show-bytes.c 1 void test_show_bytes(int val) { 2 int ival = val; 3 float fval = (float) ival; 4 int *pval = &ival; 5 show_int(ival); 6 show_float(fval); 7 show_pointer(pval); 8 } code/data/show-bytes.c Figure 2.5 Byte representation examples. This code prints the byte representations of sample data objects. 正文.indd 30 2010-10-19 14:18:08 第 2 章 信息的表示和处理  31  show_bytes(valp, 2);/* B.*/ show_bytes(valp, 3);/* C.*/ 指出在小端法机器和大端法机器上,每次调用的输出值。 A. 小端法 : 大端法 : B. 小端法 : 大端法 : C. 小端法 : 大端法 : 练习题 2.6 使用 show_int 和 show_float,我们确定整数 3510593 的十六进制表示为 0x00359141, 而浮点数 3510593.0 的十六进制表示为 0x4A564504。 A. 写出这两个十六进制值的二进制表示。 B. 移动这两个二进制串的相对位置,使得它们相匹配的位数最多。有多少位相匹配呢? C. 串中的什么部分不相匹配? 2.1.5 表示字符串 C 语言中字符串被编码为一个以 null(其值为 0)字符结尾的字符数组。每个字符都由某个 标准编码来表示,最常见的是 ASCII 字符码。因此,如果我们以参数“12345”和 6(包括终止 符)来运行例程 show_bytes,我们得到结果 31 32 33 34 35 00。请注意,十进制数字 x 的 ASCII 码正好是 0x3x,而终止字节的十六进制表示为 0x00。在使用 ASCII 码作为字符码的 任何系统上都将得到相同的结果,与字节顺序和字大小规则无关。因而,文本数据比二进制数据 具有更强的平台独立性。 生成一张 ASCII 表 通过执行命令 man ascii, 你可以得到一张 ASCII 字符码的表。 练习题 2.7 下面对 show_bytes 的调用将输出什么结果? const char *s = "abcdef"; show_bytes((byte_pointer) s, strlen(s)); 注意字母‘a’~ ‘z’的 ASCII 码为 0x61 ~ 0x7A。 文字编码的 Unicode 标准 ASCII 字符集适合于编码英语文档,但是在表达一些特殊字符方面却没有太多办法,它完全 不适合编码希腊语、俄语和中文这样语言的文档。近几年,开发出很多方法来对不同语言的文字 编码。Unicode 联合会(Unicode Consortium)修订了最复杂且最普遍接受的文字编码标准。当前 的 Unicode 标准(5.0 版)的字库包括近 100 000 个字符,支持的语言范围广,从阿尔巴尼亚语 到 Xamtanga(埃塞俄比亚 Xamir 人所说的语言)。 基本编码,也称为 Unicode 的“统一字符集”,使用 32 位来表示字符。这好像是要求文本 串中每个字符要占用 4 个字节。不过,可以用一些替代编码,常见的字符只需要 1 个或 2 个字 节,而不太常用的字符需要多一些的字节数。特别地,UTF-8 表示将每个字符编码为一个字节序 列,这样标准 ASCII 字符还是使用和它们在 ASCII 中一样的单字节编码,这也就意味着所有的 ASCII 字节序列用 ASCII 码表示和用 UTF-8 表示是一样的。 Java 编程语言使用 Unicode 来表示字符串。对于 C 语言也有支持 Unicode 的程序库。 2.1.6 表示代码 考虑下面的 C 函数 : Section 2.1 Information Storage 81 Aside The Unicode standard for text encoding The ASCII character set is suitable for encoding English-language documents, but it does not have much in the way of special characters, such as the French ‘¸c.’ It is wholly unsuited for encoding documents in languages such as Greek, Russian, and Chinese. Over the years, a variety of methods have been developed to encode text for different languages. The Unicode Consortium has devised the most comprehensive and widely accepted standard for encoding text. The current Unicode standard (version 5.0) has a repertoire of nearly 100,000 characters supporting languages ranging from Albanian to Xamtanga (a language spoken by the Xamir people of Ethiopia). The base encoding, known as the “Universal Character Set” of Unicode, uses a 32-bit representa- tion of characters. This would seem to require every string of text to consist of 4 bytes per character. However, alternative codings are possible where common characters require just 1 or 2 bytes, while less common ones require more. In particular, the UTF-8 representation encodes each character as a sequence of bytes, such that the standard ASCII characters use the same single-byte encodings as they have in ASCII, implying that all ASCII byte sequences have the same meaning in UTF-8 as they do in ASCII. The Java programming language uses Unicode in its representations of strings. Program libraries are also available for C to support Unicode. 2.1.6 Representing Code Consider the following C function: 1 int sum(int x, int y) { 2 returnx+y; 3 } When compiled on our sample machines, we generate machine code having the following byte representations: Linux 32: 55 89 e5 8b 45 0c 03 45 08 c9 c3 Windows: 55 89 e5 8b 45 0c 03 45 08 5d c3 Sun: 81 c3 e0 08 90 02 00 09 Linux 64: 55 48 89 e5 89 7d fc 89 75 f8 03 45 fc c9 c3 Here we find that the instruction codings are different. Different machine types use different and incompatible instructions and encodings. Even identical proces- sors running different operating systems have differences in their coding conven- tions and hence are not binary compatible. Binary code is seldom portable across different combinations of machine and operating system. A fundamental concept of computer systems is that a program, from the perspective of the machine, is simply a sequence of bytes. The machine has no information about the original source program, except perhaps some auxiliary tables maintained to aid in debugging. We will see this more clearly when we study machine-level programming in Chapter 3. 正文.indd 31 2010-10-19 14:18:08 正文.indd 32 2010-10-19 14:18:09 练习题 2.8 填写下表,给出位向量的布尔运算的求值结果。 and only if i ∈ A. For example, recalling that we write aw−1 on the left and a0 on the any subset A ⊆{0, 1,...,w− 1} with a bit vector [aw−1,...,a1,a0], where ai = 1if One useful application of bit vectors is to represent finite sets. We can encode results and clever tricks, as we will explore in Problem 2.10. and combine them in a different order, and so (a ^ b) ^ a = b. This property leads to some interesting 0 ^ 0 = 1 ^ 1 = 0, and it extends to bit vectors as well. This property holds even when we rearrange terms where we use 0 here to represent a bit vector of all zeros. We can see this holds for single bits, since operation, but in this case each element is its own additive inverse. That is, a ^ a = 0 for any value a, inverse −x, such that x +−x = 0. A similar property holds for Boolean rings, where ^ is the “addition” integer arithmetic. For example, one property of integer arithmetic is that every value x has an additive mathematical form, known as a Boolean ring. Boolean rings have many properties in common with When we consider operations ^, &, and ~ operating on bit vectors of length w, we get a different (a & c). In addition, however, Boolean operation | distributes over &, and so we can write a | (b & c) =(a | b) & (a | c), whereas we cannot say that a + (b . c) = (a + b) . (a + c) holds for all integers. written a . (b + c) = (a . b) + (a . c), Boolean operation & distributes over |, written a & (b | c) = (a & b) | properties as arithmetic over integers. For example, just as multiplication distributes over addition, the more general case there are 2w bit vectors of length w. Boolean algebra has many of the same any integer w>0. The simplest is the case where w = 1, and there are just two elements, but for The Boolean operations |, &, and ~ operating on bit vectors of length w form a Boolean algebra, for Web Aside DATA:BOOL More on Boolean algebra and Boolean rings a ^ b a | b a & b ~b ~a b [01010101] a [01101001] Operation Result bit vectors. Fill in the following table showing the results of evaluating Boolean operations on Practice Problem 2.8 0100 1110 1010 0011 & 1100 | 1100 ^ 1100 ~ 1100 0110 0110 0110 and b = [1100]. Then the four operations a & b, a | b, a ^ b, and ~b yield As examples, consider the case where w = 4, and with arguments a = [0110] Section 2.1 Information Storage 83 到以下结果 : 举个例子,假设 w = 4,参数 a =[0110],b =[1100]。那么 4 种运算 a & b、a | b、a ^ b 和 ~b 分别得 其中第 i 个元素等于 ai&bi,0 ≤ i 0,长度为 w 的位向量上的布尔运算 |、& 和 ~ 形成了一个布尔代数。最简单 DATA:BOOL :关于布尔代数和布尔环的更多内容 and only if i ∈ A. For example, recalling that we write aw−1 on the left and a0 on the any subset A ⊆{0, 1,...,w− 1} with a bit vector [aw−1,...,a1,a0], where ai = 1if One useful application of bit vectors is to represent finite sets. We can encode results and clever tricks, as we will explore in Problem 2.10. and combine them in a different order, and so (a ^ b) ^ a = b. This property leads to some interesting 0 ^ 0 = 1 ^ 1 = 0, and it extends to bit vectors as well. This property holds even when we rearrange terms where we use 0 here to represent a bit vector of all zeros. We can see this holds for single bits, since operation, but in this case each element is its own additive inverse. That is, a ^ a = 0 for any value a, inverse −x, such that x +−x = 0. A similar property holds for Boolean rings, where ^ is the “addition” integer arithmetic. For example, one property of integer arithmetic is that every value x has an additive mathematical form, known as a Boolean ring. Boolean rings have many properties in common with When we consider operations ^, &, and ~ operating on bit vectors of length w, we get a different (a & c). In addition, however, Boolean operation | distributes over &, and so we can write a | (b & c) =(a | b) & (a | c), whereas we cannot say that a + (b . c) = (a + b) . (a + c) holds for all integers. written a . (b + c) = (a . b) + (a . c), Boolean operation & distributes over |, written a & (b | c) = (a & b) | properties as arithmetic over integers. For example, just as multiplication distributes over addition, the more general case there are 2w bit vectors of length w. Boolean algebra has many of the same any integer w>0. The simplest is the case where w = 1, and there are just two elements, but for The Boolean operations |, &, and ~ operating on bit vectors of length w form a Boolean algebra, for Web Aside DATA:BOOL More on Boolean algebra and Boolean rings a ^ b a | b a & b ~b ~a b [01010101] a [01101001] Operation Result bit vectors. Fill in the following table showing the results of evaluating Boolean operations on Practice Problem 2.8 0100 1110 1010 0011 & 1100 | 1100 ^ 1100 ~ 1100 0110 0110 0110 and b = [1100]. Then the four operations a & b, a | b, a ^ b, and ~b yield As examples, consider the case where w = 4, and with arguments a = [0110] Section 2.1 Information Storage 83 运算 结果  第 2 章 信息的表示和处理  33  34  第一部分 程序结构和执行  那么基于光源 R(红)、G(绿)、B(蓝)的关闭(0)或打开(1),我们就能够创建 8 种不同的颜色 : R G B 颜色 R G B 颜色 0 0 0 黑色 1 0 0 红色 0 0 1 蓝色 1 0 1 红紫色 0 1 0 绿色 1 1 0 黄色 0 1 1 蓝绿色 1 1 1 白色 这些颜色中的每一种都能用一个长度为 3 的位向量来表示,我们可以对它们进行布尔运算。 A. 一种颜色的补是通过关掉打开的光源,且打开关闭的光源而形成的。那么上面 列出的 8 种颜色每一 种的补是什么? B. 描述下列颜色应用布尔运算的结果 : 蓝色 | 绿色 = 黄色 & 蓝绿色 = 红色 ^ 红紫色 = 2.1.8 C 语言中的位级运算 C 语言的一个很有用的特性就是它支持按位布尔运算。事实上,我们在布尔运算中使用的那 些符号就是 C 语言所使用的 :| 就是 OR(或),& 就是 AND(与),~ 就是 NOT(取反),而 ^ 就 是 EXCLUSIVE-OR(异或)。这些运算能运用到任何“整型”的数据类型上,也就是那些声明为 char 或者 int 的数据类型,无论它们有没有像 short、long、long long 或者 unsigned 这样的限定词。以下是一些对 char 数据类型表达式求值的例子。 C 的表达式 二进制表达式 二进制结果 十六进制结果 ~0x41 ~ [0100 0001] [1011 1110] 0xBE ~0x00 ~ [0000 0000] [1111 1111] 0xFF 0x69&0x55 [0110 1001]&[0101 0101] [0100 0001] 0x41 0x69|0x55 [0110 1001] | [0101 0101] [0111 1101] 0x7D 正如示例说明的那样,确定一个位级表达式的结果最好的方法,就是将十六进制的参数扩展 成二进制表示并执行二进制运算,然后再转换回十六进制。 练习题 2.10 对于任一位向量 a,有 a ^ a = 0。应用这一属性,考虑下面的程序 : Section 2.1 Information Storage 85 A. The complement of a color is formed by turning off the lights that are on and turning on the lights that are off. What would be the complement of each of the eight colors listed above? B. Describe the effect of applying Boolean operations on the following colors: Blue | Green =Yellow & Cyan =Red ^ Magenta = 2.1.8 Bit-Level Operations in C One useful feature of C is that it supports bit-wise Boolean operations. In fact, the symbols we have used for the Boolean operations are exactly those used by C: | for Or, & for And, ~ for Not, and ^ for Exclusive-Or. These can be applied to any “integral” data type, that is, one declared as type char or int, with or without qualifiers such as short, long, long long, or unsigned. Here are some examples of expression evaluation for data type char: C expression Binary expression Binary result Hexadecimal result ~0x41 ~[0100 0001] [1011 1110] 0xBE ~0x00 ~[0000 0000] [1111 1111] 0xFF 0x69 & 0x55 [0110 1001] & [0101 0101] [0100 0001] 0x41 0x69 | 0x55 [0110 1001] | [0101 0101] [0111 1101] 0x7D As our examples show, the best way to determine the effect of a bit-level ex- pression is to expand the hexadecimal arguments to their binary representations, perform the operations in binary, and then convert back to hexadecimal. Practice Problem 2.10 As an application of the property that a ^ a = 0 for any bit vector a, consider the following program: 1 void inplace_swap(int *x, int *y) { 2 *y = *x ^ *y; /* Step 1 */ 3 *x = *x ^ *y; /* Step 2 */ 4 *y = *x ^ *y; /* Step 3 */ 5 } As the name implies, we claim that the effect of this procedure is to swap the values stored at the locations denoted by pointer variables x and y. Note that unlike the usual technique for swapping two values, we do not need a third location to temporarily store one value while we are moving the other. There is no performance advantage to this way of swapping; it is merely an intellectual amusement. 正如程序名字所暗示的那样,我们认为这个过程的效果是交换指针变量 x 和 y 所指向的存储位置 处存放的值。注意,与通常的交换两个数值的技术不一样,当移动一个值时,我们不需要第三个位置 来临时存储另一个值。这种交换方式并没有性能上的优势,它仅仅是一个智力游戏。 以指针 x 和 y 指向的位置存储的值分别是 a 和 b 作为开始,填写下表,给出在程序的每一步之后,存 储在这两个位置中的值。利用 ^ 的属性证明达到了所希望的效果。回想一下,每个元素就是它自身的 加法逆元(a ^ a = 0)。 步骤 *x *y 初始 a b 第一步 第二步 第三步 正文.indd 34 2010-10-19 14:18:10 第 2 章 信息的表示和处理  35  练习题 2.11 在练习题 2.10 中的 inplace_swap 函数的基础上,你决定写一段代码,实现将一个数 组中的元素头尾两端依次对调。你写出下面这个函数 : 86 Chapter 2 Representing and Manipulating Information Starting with values a and b in the locations pointed to by x and y, respectively, fill in the table that follows, giving the values stored at the two locations after each step of the procedure. Use the properties of ^ to show that the desired effect is achieved. Recall that every element is its own additive inverse (that is, a ^ a = 0). Step *x *y Initially ab Step 1 Step 2 Step 3 Practice Problem 2.11 Armed with the function inplace_swap from Problem 2.10, you decide to write code that will reverse the elements of an array by swapping elements from opposite ends of the array, working toward the middle. You arrive at the following function: 1 void reverse_array(int a[], int cnt) { 2 int first, last; 3 for (first = 0, last = cnt-1; 4 first <= last; 5 first++,last--) 6 inplace_swap(&a[first], &a[last]); 7 } When you apply your function to an array containing elements 1, 2, 3, and 4, you find the array now has, as expected, elements 4, 3, 2, and 1. When you try it on an array with elements 1, 2, 3, 4, and 5, however, you are surprised to see that the array now has elements 5, 4, 0, 2, and 1. In fact, you discover that the code always works correctly on arrays of even length, but it sets the middle element to 0 whenever the array has odd length. A. For an array of odd length cnt = 2k + 1, what are the values of variables first and last in the final iteration of function reverse_array? B. Why does this call to function xor_swap set the array element to 0? C. What simple modification to the code for reverse_array would eliminate this problem? One common use of bit-level operations is to implement masking operations, where a mask is a bit pattern that indicates a selected set of bits within a word. As an example, the mask 0xFF (having ones for the least significant 8 bits) indicates the low-order byte of a word. The bit-level operation x & 0xFF yields a value consisting of the least significant byte of x, but with all other bytes set to 0. For example, with x = 0x89ABCDEF, the expression would yield 0x000000EF. The expression ~0 will yield a mask of all ones, regardless of the word size of 当你对一个包含元素 1、2、3 和 4 的数组使用这个函数时,正如预期的那样,现在数组的元素变成了 4、 3、2 和 1。不过,当你对一个包含元素 1、2、3、4 和 5 的数组使用这个函数时,你会很惊奇地看到得 到数字的元素为 5、4、0、2 和 1。实际上,你会发现这段代码对所有偶数长度的数组都能正确地工作, 但是当数组的长度为奇数时,它就会把中间的元素设置成 0。 A. 对于一个长度为奇数的数组,长度 cnt=2k+1,函数 reverse_array 最后一次循环中,变量 first 和 last 的值分别是什么? B. 为什么这时调用函数 xor_swap 会将数组元素设置为 0 ? C. 对 reverse_array 的代码做哪些简单改动就能消除这个问题? 位级运算的一个常见用法就是实现掩码运算,这里掩码是一个位模式,表示从一个字中选出 的位的集合。让我们来看一个例子,掩码 0xFF(最低的 8 位为 1)表示一个字的低位字节。位 级运算 x&0xFF 生成一个由 x 的最低有效字节组成的值,而其他的字节就被置为 0。比如,对于 x=0x89ABCDEF,其表达式将得到 0x000000EF。表达式 ~0 将生成一个全 1 的掩码,不管机器 的字大小是多少。尽管对于一个 32 位机器来说,同样的掩码可以写成 0xFFFFFFFF,但是这样 的代码不是可移植的。 练习题 2.12 对于下面的值,写出变量 x 的 C 语言表达式。你的代码应该对任何字长 w ≥ 8 都能工 作。我们给出了当 x=0x87654321 以及 w = 32 时表达式求值的结果,仅供参考。 A. x 的最低有效字节,其他位均置为 0。[0x00000021]。 B. 除了 x 的最低有效字节外,其他的位都取补,最低有效字节保持不变。[0x789ABC21]。 C. x 的最低有效字节设置成全 1,其他字节都保持不变。[0x876543FF]。 练习题 2.13 从 20 世纪 70 年代末到 80 年代末,Digital Equipment 的 VAX 计算机是一种非常流行的 机型。它没有布尔运算 AND 和 OR 指令,只有 bis(位设置)和 bic(位清除)这两种指令。两种 指令的输入都是一个数据字 x 和一个掩码字 m。它们生成一个结果 z,z 是由根据掩码 m 的位来修改 x 的位得到的。使用 bis 指令,这种修改就是在 m 为 1 的每个位置上,将 z 对应的位设置为 1。使用 bic 指令,这种修改就是在 m 为 1 的每个位置,将 z 对应的位设置为 0。   为了清楚因为这些运算与 C 语言位级运算的关系,假设我们有两个函数 bis 和 bic 来实现位设 置和位清除操作。只想用这两个函数,而不使用任何其他 C 语言运算,来实现按位 | 和 ^ 运算。填写 下列代码中缺失的代码。提示 :写出 bis 和 bic 运算的 C 语言表达式。 Section 2.1 Information Storage 87 the machine. Although the same mask can be written 0xFFFFFFFF for a 32-bit machine, such code is not as portable. Practice Problem 2.12 Write C expressions, in terms of variable x, for the following values. Your code should work for any word size w ≥ 8. For reference, we show the result of evalu- ating the expressions for x = 0x87654321, with w = 32. A. The least significant byte of x, with all other bits set to 0. [0x00000021]. B. All but the least significant byte of x complemented, with the least significant byte left unchanged. [0x789ABC21]. C. The least significant byte set to all 1s, and all other bytes of x left unchanged. [0x876543FF]. Practice Problem 2.13 The Digital Equipment VAX computer was a very popular machine from the late 1970s until the late 1980s. Rather than instructions for Boolean operations And and Or, it had instructions bis (bit set) and bic (bit clear). Both instructions take a data word x and a mask word m. They generate a result z consisting of the bits of x modified according to the bits of m. With bis, the modification involves setting z to 1 at each bit position where m is 1. With bic, the modification involves setting z to 0 at each bit position where m is 1. To see how these operations relate to the C bit-level operations, assume we have functions bis and bic implementing the bit set and bit clear operations, and that we want to use these to implement functions computing bit-wise operations | and ^, without using any other C operations. Fill in the missing code below. Hint: Write C expressions for the operations bis and bic. /* Declarations of functions implementing operations bis and bic */ int bis(int x, int m); int bic(int x, int m); /* Compute x|y using only calls to functions bis and bic */ int bool_or(int x, int y) { int result = ; return result; } /* Compute x^y using only calls to functions bis and bic */ int bool_xor(int x, int y) { int result = ; return result; } Section 2.1 Information Storage 87 the machine. Although the same mask can be written 0xFFFFFFFF for a 32-bit machine, such code is not as portable. Practice Problem 2.12 Write C expressions, in terms of variable x, for the following values. Your code should work for any word size w ≥ 8. For reference, we show the result of evalu- ating the expressions for x = 0x87654321, with w = 32. A. The least significant byte of x, with all other bits set to 0. [0x00000021]. B. All but the least significant byte of x complemented, with the least significant byte left unchanged. [0x789ABC21]. C. The least significant byte set to all 1s, and all other bytes of x left unchanged. [0x876543FF]. Practice Problem 2.13 The Digital Equipment VAX computer was a very popular machine from the late 1970s until the late 1980s. Rather than instructions for Boolean operations And and Or, it had instructions bis (bit set) and bic (bit clear). Both instructions take a data word x and a mask word m. They generate a result z consisting of the bits of x modified according to the bits of m. With bis, the modification involves setting z to 1 at each bit position where m is 1. With bic, the modification involves setting z to 0 at each bit position where m is 1. To see how these operations relate to the C bit-level operations, assume we have functions bis and bic implementing the bit set and bit clear operations, and that we want to use these to implement functions computing bit-wise operations | and ^, without using any other C operations. Fill in the missing code below. Hint: Write C expressions for the operations bis and bic. /* Declarations of functions implementing operations bis and bic */ int bis(int x, int m); int bic(int x, int m); /* Compute x|y using only calls to functions bis and bic */ int bool_or(int x, int y) { int result = ; return result; } /* Compute x^y using only calls to functions bis and bic */ int bool_xor(int x, int y) { int result = ; return result; } 正文.indd 35 2010-10-19 14:18:10  36  第一部分 程序结构和执行  Section 2.1 Information Storage 87 the machine. Although the same mask can be written 0xFFFFFFFF for a 32-bit machine, such code is not as portable. Practice Problem 2.12 Write C expressions, in terms of variable x, for the following values. Your code should work for any word size w ≥ 8. For reference, we show the result of evalu- ating the expressions for x = 0x87654321, with w = 32. A. The least significant byte of x, with all other bits set to 0. [0x00000021]. B. All but the least significant byte of x complemented, with the least significant byte left unchanged. [0x789ABC21]. C. The least significant byte set to all 1s, and all other bytes of x left unchanged. [0x876543FF]. Practice Problem 2.13 The Digital Equipment VAX computer was a very popular machine from the late 1970s until the late 1980s. Rather than instructions for Boolean operations And and Or, it had instructions bis (bit set) and bic (bit clear). Both instructions take a data word x and a mask word m. They generate a result z consisting of the bits of x modified according to the bits of m. With bis, the modification involves setting z to 1 at each bit position where m is 1. With bic, the modification involves setting z to 0 at each bit position where m is 1. To see how these operations relate to the C bit-level operations, assume we have functions bis and bic implementing the bit set and bit clear operations, and that we want to use these to implement functions computing bit-wise operations | and ^, without using any other C operations. Fill in the missing code below. Hint: Write C expressions for the operations bis and bic. /* Declarations of functions implementing operations bis and bic */ int bis(int x, int m); int bic(int x, int m); /* Compute x|y using only calls to functions bis and bic */ int bool_or(int x, int y) { int result = ; return result; } /* Compute x^y using only calls to functions bis and bic */ int bool_xor(int x, int y) { int result = ; return result; } 2.1.9 C 语言中的逻辑运算 C 语言还提供了一组逻辑运算符 ||、&& 和!,分别对应于命题逻辑中的 OR、AND 和 NOT 运算。逻辑运算很容易和位级运算相混淆,但是它们的功能是完全不同的。逻辑运算认为所有非 零的参数都表示 TRUE,而参数 0 表示 FALSE。它们返回 1 或者 0,分别表示结果为 TRUE 或者 为 FALSE。以下是一些表达式求值的示例。 表达式 结果 !0x41 0x00 !0x00 0x01 !!0x41 0x01 0x69&&0x55 0x01 0x69||0x55 0x01 可以观察到,按位运算只有在特殊情况下,也就是参数被限制为 0 或者 1 时,才和与其对应 的逻辑运算有相同的行为。 逻辑运算符 && 和 || 与它们对应的位级运算 & 和 | 之间第二个重要的区别是,如果对第一个 参数求值就能确定表达式的结果,那么逻辑运算符就不会对第二个参数求值。因此,例如,表达 式 a&&5/a 将不会造成被零除,而表达式 p&&*p++ 也不会导致间接引用空指针。 练习题 2.14 假设 x 和 y 的字节值分别为 0x66 和 0x39。填写下表,指明各个 C 表达式的字节值。 表达式 值 表达式 值 x & y x && y x | y x || y ~x | ~y !x || !y x & !y x && ~y 练习题 2.15 只使用位级和逻辑运算,编写一个 C 表达式,它等价于 x==y。换句话说,当 x 和 y 相 等时它将返回 1,否则就返回 0。 2.1.10 C 语言中的移位运算 C 语言还提供了一组移位运算,以便向左或者向右移动位模式。对于一个位表示为 [xn-1, xn-2,…,x0] 的操作数 x,C 表达式 x<>k,但是它的行为有点微妙。一般而言,机器支持两种形式的右 移 :逻辑右移和算术右移。逻辑右移在左端补 k 个 0,得到的结果是 [0,…,0,xn-1,xn-2,…, xk]。算术右移是在左端补 k 个最高有效位的值,得到的结果是 [xn-1,…,xn-1,xn-1,xn-2,…, xk]。这种做法看上去可能有点奇特,但是我们会发现它对有符号整数数据的运算非常有用。 正文.indd 36 2010-10-19 14:18:10 第 2 章 信息的表示和处理  37  让我们来看一个例子,下面的表给出了对某些实例 8 位数据做不同的移位操作得到的结果。 操作 值 参数 x [01100011] [10010101] x << 4 [00110000] [01010000] x >> 4(逻辑右移) [00000110] [00001001] x >> 4(算术右移) [00000110] [11111001] 斜体的数字表示的是最右端(左移)或最左端(右移)填充的值。可以看到除了一个条目之外, 其他的都涉及填充 0。唯一的例外是算术右移 [10010101] 的情况。因为操作数的最高位是 1,填 充的值就是 1。 C 语言标准并没有明确定义应该使用哪种类型的右移。对于无符号数据(也就是以限定词 unsigned 声明的整型对象),右移必须是逻辑的。而对于有符号数据(默认的声明的整型对 象),算术的或者逻辑的右移都可以。不幸的是,这就意味着任何假设一种或者另一种右移形式 的代码都潜在着可移植性问题。然而,实际上,几乎所有的编译器 / 机器组合都对有符号数据使 用算术右移,且许多程序员也都假设机器会使用这种右移。 另一方面,Java 对于如何进行右移有明确的定义。表达式 x>>k 会将 x 算术右移 k 个位置, 而 x>>>k 会对 x 做逻辑右移。 移动 k 位,这里 k 很大 对于一个由 w 位组成的数据类型,如果要移动 k ≥ w 位会得到什么结果呢?例如,在一个 32 位机器上计算下面的表达式会得到什么结果 : C 语言标准很小心地规避了说明在这种情况下该如何做。在许多机器上,当移动一个 w 位 的值时,移位指令只考虑位移量的低 log2w 位,因此实际上位移量就是通过计算 k mod w 得到 的。例如,在一台采用这个规则的 32 位机器上,上面三个移位运算分别是移动 0、4 和 8 位,得 到结果 : 不过这种行为对于 C 程序来说是没有保证的,所以移位数量应该保持小于字长。 另一方面,Java 特别要求位移数量应该按照我们前面所讲的求模的方法来计算。 与移位运算有关的操作符优先级问题 常常有人会写这样的表达式 1<<2+3<<4,其本意是 (1<<2)+(3<<4)。但是在 C 语言中, 前面的表达式等价于 1<<(2+3)<<4,这是由于加法(和减法)的优先级比移位运算要高。然 后,按照从左至右结合性规则,括号应该是这样打的 (1<<(2+3))<<4,因此得到的结果是 512,而不是期望的 52。 在 C 表达式中搞错优先级是一种常见的程序错误,而且常常很难检查出来。所以当你拿不 准的时候,请加上括号! 练习题 2.16 填写下表,说明不同移位运算对单字节数的影响。思考移位运算的最好方式是使用二进 正文.indd 37 2010-10-19 14:18:11  38  第一部分 程序结构和执行  制表示。将最初的值转换为二进制执行移位运算,然后再转换回十六进制。每个答案都应该是 8 个二 进制数字或者 2 个十六进制数字。 x x<<3 x>>2(逻辑的) x>>2(算术的) 十六进制 二进制 二进制 十六进制 二进制 十六进制 二进制 十六进制 0xC3 0x75 0x87 0x66 2.2 整数表示 在本节中,我们描述用位来编码整数的两种不同的方式 :一种只能表示非负数,而另一种能 够表示负数、零和正数。后面我们将会看到它们的数学属性和机器级实现方面密切关联。我们还 会研究扩展或者收缩一个已编码整数以适应不同长度表示的效果。 2.2.1 整型数据类型 C 语言支持多种整型数据类型—表示有限范围的整数。这些类型如图 2-8 和图 2-9 所示, 其中还给出了“典型的”32 位和 64 位机器的取值范围。每种类型都能用关键字来指定大小,这 些关键字包括 char、short、long 或者 long long,同时还可以指示被表示的数字是非负 数(声明为 unsigned),或者可能是负数(默认)。如图 2-3 所示,这些不同大小的分配的字节 数会根据机器的字长和编译器有所不同。根据字节分配,不同的大小所能表示的值的范围是不同 的。这里给出来的唯一一个与机器相关的取值范围是大小指示符 long 的。大多数 64 位机器使 用 8 个字节表示,比 32 位机器上使用的 4 个字节表示的取值范围大很多。 图 2-8 和图 2-9 中一个值得注意的特点是取值范围不是对称的—负数的范围比整数的范围 大 1。当我们考虑如何表示负数的时候,会看到为什么会这样。 图 2-8 32 位机器上 C 语言的整型数据类型的典型取值范围(方括号中的文字是可选的) C 语言标准定义了每种数据类型必须能够表示的最小的取值范围。如图 2-10 所示,它们 的取值范围与图 2-8 和图 2-9 所示的典型实现一样或者小一些。特别地,我们看到它们只要 求正数和负数的取值范围是对称的。此外,数据类型 int 可以用 2 个字节的数字来实现,而 这几乎退回到了 16 位机器的时代。还看到,long 的大小可以用 4 个字节的数字来实现,而 实际上也常常是这样。数据类型 long long 是在 ISO C99 中引入的,它需要至少 8 个字节 表示。 正文.indd 38 2010-10-19 14:18:12 第 2 章 信息的表示和处理  39  图 2-9 64 位机器上 C 语言的整型数据类型的典型取值范围(方括号中的文字是可选的) 图 2-10 C 语言的整型数据类型的保证的取值范围(方括号中的文字是可选的) 注 :C 语言标准要求这些数据类型必须至少具有这样的取值范围。 给 C 语言初学者 :C、C++ 和 Java 中的有符号和无符号数 C 和 C++ 都支持有符号(默认)和无符号数。Java 只支持有符号数。 2.2.2 无符号数的编码 假设一个整数数据类型有 w 位。我们可以将位向量写成 x→,表示整个向量,或者写成 [xw-1 , xw-2,…,x0],表示向量中的每一位。把 x→ 看做一个二进制表示的数,就获得了 x→ 的无符号表示。 我们用一个函数 B2Uw(Binary to Unsigned)的缩写,长度为 w)来表示 : Section 2.2 Integer Representations 93 Figure 2.11 Unsigned number examples for w = 4. When bit i in the binary representation has value 1, it contributes 2i to the value. 161514131211109876543210 20 = 1 21 = 2 22 = 4 23 = 8 [0001] [0101] [1011] [1111] unsigned interpretation of �x. We express this interpretation as a function B2Uw (for “binary to unsigned,” length w): B2Uw(�x) . = w−1 �i=0 xi2i (2.1) In this equation, the notation “ . =” means that the left-hand side is defined to be equal to the right-hand side. The function B2Uw maps strings of zeros and ones of length w to nonnegative integers. As examples, Figure 2.11 shows the mapping, given by B2U, from bit vectors to integers for the following cases: B2U4([0001]) = 0 . 23 + 0 . 22 + 0 . 21 + 1 . 20 = 0 + 0 + 0 + 1 = 1 B2U4([0101]) = 0 . 23 + 1 . 22 + 0 . 21 + 1 . 20 = 0 + 4 + 0 + 1 = 5 B2U4([1011]) = 1 . 23 + 0 . 22 + 1 . 21 + 1 . 20 = 8 + 0 + 2 + 1 = 11 B2U4([1111]) = 1 . 23 + 1 . 22 + 1 . 21 + 1 . 20 = 8 + 4 + 2 + 1 = 15 (2.2) In the figure, we represent each bit position i by a rightward-pointing blue bar of length 2i. The numeric value associated with a bit vector then equals the combined length of the bars for which the corresponding bit values are 1. Let us consider the range of values that can be represented using w bits. The least value is given by bit vector [00 ...0] having integer value 0, and the greatest value is given by bit vector [11 ...1] having integer value UMaxw . = � w−1 i=0 2i =2w − 1. Using the 4-bit case as an example, we have UMax4 = B2U4([1111]) =24 − 1= 15. Thus, the function B2Uw can be defined as a mapping B2Uw: {0, 1} w → {0,...,2w − 1}. The unsigned binary representation has the important property that every number between 0 and 2w − 1has a unique encoding as a w-bit value. For example, there is only one representation of decimal value 11 as an unsigned, 4-bit number, namely [1011]. This property is captured in mathematical terms by stating that function B2Uw is a bijection—it associates a unique value to each bit vector of            (2-1) 在这个等式中,符号“=4 ”表示左边被定义为等于右边。函数 B2Uw 将一个长度为 w 的 0、1 串映射 到非负整数。举一个示例,图 2-11 展示的是下面几种情况下 B2U 给出的从位向量到整数的映射。 Section 2.2 Integer Representations 93 Figure 2.11 Unsigned number examples for w = 4. When bit i in the binary representation has value 1, it contributes 2i to the value. 161514131211109876543210 20 = 1 21 = 2 22 = 4 23 = 8 [0001] [0101] [1011] [1111] unsigned interpretation of �x. We express this interpretation as a function B2Uw (for “binary to unsigned,” length w): B2Uw(�x) . = w−1 �i=0 xi2i (2.1) In this equation, the notation “ . =” means that the left-hand side is defined to be equal to the right-hand side. The function B2Uw maps strings of zeros and ones of length w to nonnegative integers. As examples, Figure 2.11 shows the mapping, given by B2U, from bit vectors to integers for the following cases: B2U4([0001]) = 0 . 23 + 0 . 22 + 0 . 21 + 1 . 20 = 0 + 0 + 0 + 1 = 1 B2U4([0101]) = 0 . 23 + 1 . 22 + 0 . 21 + 1 . 20 = 0 + 4 + 0 + 1 = 5 B2U4([1011]) = 1 . 23 + 0 . 22 + 1 . 21 + 1 . 20 = 8 + 0 + 2 + 1 = 11 B2U4([1111]) = 1 . 23 + 1 . 22 + 1 . 21 + 1 . 20 = 8 + 4 + 2 + 1 = 15 (2.2) In the figure, we represent each bit position i by a rightward-pointing blue bar of length 2i. The numeric value associated with a bit vector then equals the combined length of the bars for which the corresponding bit values are 1. Let us consider the range of values that can be represented using w bits. The least value is given by bit vector [00 ...0] having integer value 0, and the greatest value is given by bit vector [11 ...1] having integer value UMaxw . = � w−1 i=0 2i =2w − 1. Using the 4-bit case as an example, we have UMax4 = B2U4([1111]) =24 − 1= 15. Thus, the function B2Uw can be defined as a mapping B2Uw: {0, 1} w → {0,...,2w − 1}. The unsigned binary representation has the important property that every number between 0 and 2w − 1has a unique encoding as a w-bit value. For example, there is only one representation of decimal value 11 as an unsigned, 4-bit number, namely [1011]. This property is captured in mathematical terms by stating that function B2Uw is a bijection—it associates a unique value to each bit vector of    (2-2) 在图中,我们用长度为 2i 的指向右侧箭头的条表示每个位的位置 i。每个位向量对应的数值 =4 正文.indd 39 2010-10-19 14:18:15  40  第一部分 程序结构和执行  就等于所有值为 1 的位对应的条的长度之和。 图 2-11 w = 4 的无符号数示例。当二进制表示中为 i 为 1,数值就会相应的加上 2i 让我们来考虑一下 w 位所能表示的值的范围。最小值是用位向量 [00…0] 表示,也就是整数值 0,而最大值是用位向量 [11…1] 表示,也就是整数值 Section 2.2 Integer Representations 93 Figure 2.11 Unsigned number examples for w = 4. When bit i in the binary representation has value 1, it contributes 2i to the value. 161514131211109876543210 20 = 1 21 = 2 22 = 4 23 = 8 [0001] [0101] [1011] [1111] unsigned interpretation of �x. We express this interpretation as a function B2Uw (for “binary to unsigned,” length w): B2Uw(�x) . = w−1 �i=0 xi2i (2.1) In this equation, the notation “ . =” means that the left-hand side is defined to be equal to the right-hand side. The function B2Uw maps strings of zeros and ones of length w to nonnegative integers. As examples, Figure 2.11 shows the mapping, given by B2U, from bit vectors to integers for the following cases: B2U4([0001]) = 0 . 23 + 0 . 22 + 0 . 21 + 1 . 20 = 0 + 0 + 0 + 1 = 1 B2U4([0101]) = 0 . 23 + 1 . 22 + 0 . 21 + 1 . 20 = 0 + 4 + 0 + 1 = 5 B2U4([1011]) = 1 . 23 + 0 . 22 + 1 . 21 + 1 . 20 = 8 + 0 + 2 + 1 = 11 B2U4([1111]) = 1 . 23 + 1 . 22 + 1 . 21 + 1 . 20 = 8 + 4 + 2 + 1 = 15 (2.2) In the figure, we represent each bit position i by a rightward-pointing blue bar of length 2i. The numeric value associated with a bit vector then equals the combined length of the bars for which the corresponding bit values are 1. Let us consider the range of values that can be represented using w bits. The least value is given by bit vector [00 ...0] having integer value 0, and the greatest value is given by bit vector [11 ...1] having integer value UMaxw . = � w−1 i=0 2i =2w − 1. Using the 4-bit case as an example, we have UMax4 = B2U4([1111]) =24 − 1= 15. Thus, the function B2Uw can be defined as a mapping B2Uw: {0, 1} w → {0,...,2w − 1}. The unsigned binary representation has the important property that every number between 0 and 2w − 1has a unique encoding as a w-bit value. For example, there is only one representation of decimal value 11 as an unsigned, 4-bit number, namely [1011]. This property is captured in mathematical terms by stating that function B2Uw is a bijection—it associates a unique value to each bit vector of Section 2.2 Integer Representations 93 Figure 2.11 Unsigned number examples for w = 4. When bit i in the binary representation has value 1, it contributes 2i to the value. 161514131211109876543210 20 = 1 21 = 2 22 = 4 23 = 8 [0001] [0101] [1011] [1111] unsigned interpretation of �x. We express this interpretation as a function B2Uw (for “binary to unsigned,” length w): B2Uw(�x) . = w−1 �i=0 xi2i (2.1) In this equation, the notation “ . =” means that the left-hand side is defined to be equal to the right-hand side. The function B2Uw maps strings of zeros and ones of length w to nonnegative integers. As examples, Figure 2.11 shows the mapping, given by B2U, from bit vectors to integers for the following cases: B2U4([0001]) = 0 . 23 + 0 . 22 + 0 . 21 + 1 . 20 = 0 + 0 + 0 + 1 = 1 B2U4([0101]) = 0 . 23 + 1 . 22 + 0 . 21 + 1 . 20 = 0 + 4 + 0 + 1 = 5 B2U4([1011]) = 1 . 23 + 0 . 22 + 1 . 21 + 1 . 20 = 8 + 0 + 2 + 1 = 11 B2U4([1111]) = 1 . 23 + 1 . 22 + 1 . 21 + 1 . 20 = 8 + 4 + 2 + 1 = 15 (2.2) In the figure, we represent each bit position i by a rightward-pointing blue bar of length 2i. The numeric value associated with a bit vector then equals the combined length of the bars for which the corresponding bit values are 1. Let us consider the range of values that can be represented using w bits. The least value is given by bit vector [00 ...0] having integer value 0, and the greatest value is given by bit vector [11 ...1] having integer value UMaxw . = � w−1 i=0 2i =2w − 1. Using the 4-bit case as an example, we have UMax4 = B2U4([1111]) =24 − 1= 15. Thus, the function B2Uw can be defined as a mapping B2Uw: {0, 1} w → {0,...,2w − 1}. The unsigned binary representation has the important property that every number between 0 and 2w − 1has a unique encoding as a w-bit value. For example, there is only one representation of decimal value 11 as an unsigned, 4-bit number, namely [1011]. This property is captured in mathematical terms by stating that function B2Uw is a bijection—it associates a unique value to each bit vector of 。以 w = 4 位为例, UMax4 = B2U4([1111]) = 24-1 = 15。因此,函数 B2Uw 能够被定义为一个映射 B2Uw:{0,1}w → {0,…, 2w-1}。 无符号数的二进制表示有一个很重要的属性,就是每个介于 0 ~ 2w-1 之间的数都有唯一一 个 w 位的值编码。例如,十进制值 11 作为无符号数,只有一个 4 位的表示,即 [1011]。这个属 性用数学语言来描述就是函数 B2Uw 是一个双射—对于每一个长度为 w 的位向量,都有一个唯 一的值与之对应 ;反过来,在 0 ~ 2w-1 之间的每一个整数都有一个唯一的长度为 w 的位向量二 进制表示与之对应。 2.2.3 补码编码 对于许多应用,我们还希望表示负数值。最常见的有符号数的计算机表示方式就是补码 (two’s-complement)形式。在这个定义中,将字的最高有效位解释为负权(negative weight)。 我们用函数 B2Tw(Binary to Two’s-complement 的缩写,长度为 w)来表示 : 94 Chapter 2 Representing and Manipulating Information length w; conversely, each integer between 0 and 2w − 1 has a unique binary representation as a bit vector of length w. 2.2.3 Two’s-Complement Encodings For many applications, we wish to represent negative values as well. The most com- mon computer representation of signed numbers is known as two’s-complement form. This is defined by interpreting the most significant bit of the word to have negative weight. We express this interpretation as a function B2Tw (for “binary to two’s-complement” length w): B2Tw(�x) . = −xw−12w−1 + w−2 �i=0 xi2i (2.3) The most significant bit xw−1 is also called the sign bit. Its “weight” is − 2w−1, the negation of its weight in an unsigned representation. When the sign bit is set to 1, the represented value is negative, and when set to 0 the value is nonnegative. As examples, Figure 2.12 shows the mapping, given by B2T, from bit vectors to integers for the following cases: B2T4([0001]) =−0 . 23 + 0 . 22 + 0 . 21 + 1 . 20 = 0 + 0 + 0 + 1 = 1 B2T4([0101]) =−0 . 23 + 1 . 22 + 0 . 21 + 1 . 20 = 0 + 4 + 0 + 1 = 5 B2T4([1011]) =−1 . 23 + 0 . 22 + 1 . 21 + 1 . 20 =−8 + 0 + 2 + 1 =−5 B2T4([1111]) =−1 . 23 + 1 . 22 + 1 . 21 + 1 . 20 =−8 + 4 + 2 + 1 =−1 (2.4) In the figure, we indicate that the sign bit has negative weight by showing it as a leftward-pointing gray bar. The numeric value associated with a bit vector is then given by the combination of the possible leftward-pointing gray bar and the rightward-pointing blue bars. Figure 2.12 Two’s-complement number examples for w = 4. Bit 3 serves as a sign bit, and so, when set to 1, it contributes − 23 =−8 to the value. This weighting is shown as a leftward-pointing gray bar. 876543210–1–2–3–4–5–6–7–8 20 = 1 21 = 2 22 = 4 –23 = –8 [0001] [0101] [1011] [1111]            (2-3) 最高有效位 xw-1 也称为符号位,它的“权重”为 -2w-1,是无符号表示中权重的负数。符号 位被设置为 1 时,表示值为负,而当设置为 0 时,值为非负。这里来看一个示例,图 2-12 展示 的是下面几种情况下 B2T 给出的从位向量到整数的映射。   (2-4) 在这个图中,我们用向左指的条表示符号位具有负权重。于是,与一个位向量相关联的数值 是由可能的向左指的灰色条和向右指的蓝色条加起来决定的。 我们可以看到,图 2-11 和图 2-12 中的位模式都是一样的,对等式(2-2)和等式(2-4)来 说也是一样,但是当最高有效位是 1 时,数值是不同的,这是因为在一种情况中,最高有效位的 权重是 +8,而在另一种情况中,它的权重是 -8。 让我们来考虑一下 w 位补码所能表示的值的范围。它能表示的最小值是位向量 [10…0](也就 =4 =4 正文.indd 40 2010-10-19 14:18:15 第 2 章 信息的表示和处理  41  是设置这个位为负权,但是清除其他所有的位),其整数值为 Section 2.2 Integer Representations 95 We see that the bit patterns are identical for Figures 2.11 and 2.12 (as well as for Equations 2.2 and 2.4), but the values differ when the most significant bit is 1, since in one case it has weight +8, and in the other case it has weight −8. Let us consider the range of values that can be represented as a w-bit two’s- complement number. The least representable value is given by bit vector [10 ...0] (set the bit with negative weight, but clear all others), having integer value TMinw . = − 2w−1. The greatest value is given by bit vector [01 ...1] (clear the bit with negative weight, but set all others), having integer value TMaxw . = � w−2 i=0 2i =2w−1 − 1. Using the 4-bit case as an example, we have TMin4 = B2T4([1000]) = − 23 =−8, and TMax4 = B2T4([0111]) = 22 + 21 + 20 = 4 + 2 + 1 = 7. We can see that B2Tw is a mapping of bit patterns of length w to numbers be- tween TMinw and TMaxw, written as B2Tw: {0, 1} w → {− 2w−1,...,2w−1 − 1}. As we saw with the unsigned representation, every number within the representable range has a unique encoding as a w-bit two’s-complement number. In mathemat- ical terms, we say that the function B2Tw is a bijection—it associates a unique value to each bit vector of length w; conversely, each integer between − 2w−1 and 2w−1 − 1 has a unique binary representation as a bit vector of length w. Practice Problem 2.17 Assuming w = 4, we can assign a numeric value to each possible hexadecimal digit, assuming either an unsigned or a two’s-complement interpretation. Fill in the following table according to these interpretations by writing out the nonzero powers of two in the summations shown in Equations 2.1 and 2.3: �x Hexadecimal Binary B2U4(�x) B2T4(�x) 0xE [1110] 23 + 22 + 21 = 14 −23 + 22 + 21 =−2 0x0 0x5 0x8 0xD 0xF Figure 2.13 shows the bit patterns and numeric values for several important numbers for different word sizes. The first three give the ranges of representable integers in terms of the values of UMaxw, TMinw, and TMaxw. We will refer to these three special values often in the ensuing discussion. We will drop the subscript w and refer to the values UMax, TMin, and TMax when w can be inferred from context or is not central to the discussion. A few points are worth highlighting about these numbers. First, as observed in Figures 2.8 and 2.9, the two’s-complement range is asymmetric: |TMin|= |TMax|+1, that is, there is no positive counterpart to TMin. As we shall see, this leads to some peculiar properties of two’s-complement arithmetic and can be 。而最大值是位向量 [01…1](清除具有负权的位,而设置其他所有的位),其整数值为 Section 2.2 Integer Representations 95 We see that the bit patterns are identical for Figures 2.11 and 2.12 (as well as for Equations 2.2 and 2.4), but the values differ when the most significant bit is 1, since in one case it has weight +8, and in the other case it has weight −8. Let us consider the range of values that can be represented as a w-bit two’s- complement number. The least representable value is given by bit vector [10 ...0] (set the bit with negative weight, but clear all others), having integer value TMinw . = − 2w−1. The greatest value is given by bit vector [01 ...1] (clear the bit with negative weight, but set all others), having integer value TMaxw . = � w−2 i=0 2i =2w−1 − 1. Using the 4-bit case as an example, we have TMin4 = B2T4([1000]) = − 23 =−8, and TMax4 = B2T4([0111]) = 22 + 21 + 20 = 4 + 2 + 1 = 7. We can see that B2Tw is a mapping of bit patterns of length w to numbers be- tween TMinw and TMaxw, written as B2Tw: {0, 1} w → {− 2w−1,...,2w−1 − 1}. As we saw with the unsigned representation, every number within the representable range has a unique encoding as a w-bit two’s-complement number. In mathemat- ical terms, we say that the function B2Tw is a bijection—it associates a unique value to each bit vector of length w; conversely, each integer between − 2w−1 and 2w−1 − 1 has a unique binary representation as a bit vector of length w. Practice Problem 2.17 Assuming w = 4, we can assign a numeric value to each possible hexadecimal digit, assuming either an unsigned or a two’s-complement interpretation. Fill in the following table according to these interpretations by writing out the nonzero powers of two in the summations shown in Equations 2.1 and 2.3: �x Hexadecimal Binary B2U4(�x) B2T4(�x) 0xE [1110] 23 + 22 + 21 = 14 −23 + 22 + 21 =−2 0x0 0x5 0x8 0xD 0xF Figure 2.13 shows the bit patterns and numeric values for several important numbers for different word sizes. The first three give the ranges of representable integers in terms of the values of UMaxw, TMinw, and TMaxw. We will refer to these three special values often in the ensuing discussion. We will drop the subscript w and refer to the values UMax, TMin, and TMax when w can be inferred from context or is not central to the discussion. A few points are worth highlighting about these numbers. First, as observed in Figures 2.8 and 2.9, the two’s-complement range is asymmetric: |TMin|= |TMax|+1, that is, there is no positive counterpart to TMin. As we shall see, this leads to some peculiar properties of two’s-complement arithmetic and can be Section 2.2 Integer Representations 95 We see that the bit patterns are identical for Figures 2.11 and 2.12 (as well as for Equations 2.2 and 2.4), but the values differ when the most significant bit is 1, since in one case it has weight +8, and in the other case it has weight −8. Let us consider the range of values that can be represented as a w-bit two’s- complement number. The least representable value is given by bit vector [10 ...0] (set the bit with negative weight, but clear all others), having integer value TMinw . = − 2w−1. The greatest value is given by bit vector [01 ...1] (clear the bit with negative weight, but set all others), having integer value TMaxw . = � w−2 i=0 2i =2w−1 − 1. Using the 4-bit case as an example, we have TMin4 = B2T4([1000]) = − 23 =−8, and TMax4 = B2T4([0111]) = 22 + 21 + 20 = 4 + 2 + 1 = 7. We can see that B2Tw is a mapping of bit patterns of length w to numbers be- tween TMinw and TMaxw, written as B2Tw: {0, 1} w → {− 2w−1,...,2w−1 − 1}. As we saw with the unsigned representation, every number within the representable range has a unique encoding as a w-bit two’s-complement number. In mathemat- ical terms, we say that the function B2Tw is a bijection—it associates a unique value to each bit vector of length w; conversely, each integer between − 2w−1 and 2w−1 − 1 has a unique binary representation as a bit vector of length w. Practice Problem 2.17 Assuming w = 4, we can assign a numeric value to each possible hexadecimal digit, assuming either an unsigned or a two’s-complement interpretation. Fill in the following table according to these interpretations by writing out the nonzero powers of two in the summations shown in Equations 2.1 and 2.3: �x Hexadecimal Binary B2U4(�x) B2T4(�x) 0xE [1110] 23 + 22 + 21 = 14 −23 + 22 + 21 =−2 0x0 0x5 0x8 0xD 0xF Figure 2.13 shows the bit patterns and numeric values for several important numbers for different word sizes. The first three give the ranges of representable integers in terms of the values of UMaxw, TMinw, and TMaxw. We will refer to these three special values often in the ensuing discussion. We will drop the subscript w and refer to the values UMax, TMin, and TMax when w can be inferred from context or is not central to the discussion. A few points are worth highlighting about these numbers. First, as observed in Figures 2.8 and 2.9, the two’s-complement range is asymmetric: |TMin|= |TMax|+1, that is, there is no positive counterpart to TMin. As we shall see, this leads to some peculiar properties of two’s-complement arithmetic and can be 。 以长度为 4 为例,我们有 Section 2.2 Integer Representations 95 We see that the bit patterns are identical for Figures 2.11 and 2.12 (as well as for Equations 2.2 and 2.4), but the values differ when the most significant bit is 1, since in one case it has weight +8, and in the other case it has weight −8. Let us consider the range of values that can be represented as a w-bit two’s- complement number. The least representable value is given by bit vector [10 ...0] (set the bit with negative weight, but clear all others), having integer value TMinw . = − 2w−1. The greatest value is given by bit vector [01 ...1] (clear the bit with negative weight, but set all others), having integer value TMaxw . = � w−2 i=0 2i =2w−1 − 1. Using the 4-bit case as an example, we have TMin4 = B2T4([1000]) = − 23 =−8, and TMax4 = B2T4([0111]) = 22 + 21 + 20 = 4 + 2 + 1 = 7. We can see that B2Tw is a mapping of bit patterns of length w to numbers be- tween TMinw and TMaxw, written as B2Tw: {0, 1} w → {− 2w−1,...,2w−1 − 1}. As we saw with the unsigned representation, every number within the representable range has a unique encoding as a w-bit two’s-complement number. In mathemat- ical terms, we say that the function B2Tw is a bijection—it associates a unique value to each bit vector of length w; conversely, each integer between − 2w−1 and 2w−1 − 1 has a unique binary representation as a bit vector of length w. Practice Problem 2.17 Assuming w = 4, we can assign a numeric value to each possible hexadecimal digit, assuming either an unsigned or a two’s-complement interpretation. Fill in the following table according to these interpretations by writing out the nonzero powers of two in the summations shown in Equations 2.1 and 2.3: �x Hexadecimal Binary B2U4(�x) B2T4(�x) 0xE [1110] 23 + 22 + 21 = 14 −23 + 22 + 21 =−2 0x0 0x5 0x8 0xD 0xF Figure 2.13 shows the bit patterns and numeric values for several important numbers for different word sizes. The first three give the ranges of representable integers in terms of the values of UMaxw, TMinw, and TMaxw. We will refer to these three special values often in the ensuing discussion. We will drop the subscript w and refer to the values UMax, TMin, and TMax when w can be inferred from context or is not central to the discussion. A few points are worth highlighting about these numbers. First, as observed in Figures 2.8 and 2.9, the two’s-complement range is asymmetric: |TMin|= |TMax|+1, that is, there is no positive counterpart to TMin. As we shall see, this leads to some peculiar properties of two’s-complement arithmetic and can be Section 2.2 Integer Representations 95 We see that the bit patterns are identical for Figures 2.11 and 2.12 (as well as for Equations 2.2 and 2.4), but the values differ when the most significant bit is 1, since in one case it has weight +8, and in the other case it has weight −8. Let us consider the range of values that can be represented as a w-bit two’s- complement number. The least representable value is given by bit vector [10 ...0] (set the bit with negative weight, but clear all others), having integer value TMinw . = − 2w−1. The greatest value is given by bit vector [01 ...1] (clear the bit with negative weight, but set all others), having integer value TMaxw . = � w−2 i=0 2i =2w−1 − 1. Using the 4-bit case as an example, we have TMin4 = B2T4([1000]) = − 23 =−8, and TMax4 = B2T4([0111]) = 22 + 21 + 20 = 4 + 2 + 1 = 7. We can see that B2Tw is a mapping of bit patterns of length w to numbers be- tween TMinw and TMaxw, written as B2Tw: {0, 1} w → {− 2w−1,...,2w−1 − 1}. As we saw with the unsigned representation, every number within the representable range has a unique encoding as a w-bit two’s-complement number. In mathemat- ical terms, we say that the function B2Tw is a bijection—it associates a unique value to each bit vector of length w; conversely, each integer between − 2w−1 and 2w−1 − 1 has a unique binary representation as a bit vector of length w. Practice Problem 2.17 Assuming w = 4, we can assign a numeric value to each possible hexadecimal digit, assuming either an unsigned or a two’s-complement interpretation. Fill in the following table according to these interpretations by writing out the nonzero powers of two in the summations shown in Equations 2.1 and 2.3: �x Hexadecimal Binary B2U4(�x) B2T4(�x) 0xE [1110] 23 + 22 + 21 = 14 −23 + 22 + 21 =−2 0x0 0x5 0x8 0xD 0xF Figure 2.13 shows the bit patterns and numeric values for several important numbers for different word sizes. The first three give the ranges of representable integers in terms of the values of UMaxw, TMinw, and TMaxw. We will refer to these three special values often in the ensuing discussion. We will drop the subscript w and refer to the values UMax, TMin, and TMax when w can be inferred from context or is not central to the discussion. A few points are worth highlighting about these numbers. First, as observed in Figures 2.8 and 2.9, the two’s-complement range is asymmetric: |TMin|= |TMax|+1, that is, there is no positive counterpart to TMin. As we shall see, this leads to some peculiar properties of two’s-complement arithmetic and can be ,而 Section 2.2 Integer Representations 95 We see that the bit patterns are identical for Figures 2.11 and 2.12 (as well as for Equations 2.2 and 2.4), but the values differ when the most significant bit is 1, since in one case it has weight +8, and in the other case it has weight −8. Let us consider the range of values that can be represented as a w-bit two’s- complement number. The least representable value is given by bit vector [10 ...0] (set the bit with negative weight, but clear all others), having integer value TMinw . = − 2w−1. The greatest value is given by bit vector [01 ...1] (clear the bit with negative weight, but set all others), having integer value TMaxw . = � w−2 i=0 2i =2w−1 − 1. Using the 4-bit case as an example, we have TMin4 = B2T4([1000]) = − 23 =−8, and TMax4 = B2T4([0111]) = 22 + 21 + 20 = 4 + 2 + 1 = 7. We can see that B2Tw is a mapping of bit patterns of length w to numbers be- tween TMinw and TMaxw, written as B2Tw: {0, 1} w → {− 2w−1,...,2w−1 − 1}. As we saw with the unsigned representation, every number within the representable range has a unique encoding as a w-bit two’s-complement number. In mathemat- ical terms, we say that the function B2Tw is a bijection—it associates a unique value to each bit vector of length w; conversely, each integer between − 2w−1 and 2w−1 − 1 has a unique binary representation as a bit vector of length w. Practice Problem 2.17 Assuming w = 4, we can assign a numeric value to each possible hexadecimal digit, assuming either an unsigned or a two’s-complement interpretation. Fill in the following table according to these interpretations by writing out the nonzero powers of two in the summations shown in Equations 2.1 and 2.3: �x Hexadecimal Binary B2U4(�x) B2T4(�x) 0xE [1110] 23 + 22 + 21 = 14 −23 + 22 + 21 =−2 0x0 0x5 0x8 0xD 0xF Figure 2.13 shows the bit patterns and numeric values for several important numbers for different word sizes. The first three give the ranges of representable integers in terms of the values of UMaxw, TMinw, and TMaxw. We will refer to these three special values often in the ensuing discussion. We will drop the subscript w and refer to the values UMax, TMin, and TMax when w can be inferred from context or is not central to the discussion. A few points are worth highlighting about these numbers. First, as observed in Figures 2.8 and 2.9, the two’s-complement range is asymmetric: |TMin|= |TMax|+1, that is, there is no positive counterpart to TMin. As we shall see, this leads to some peculiar properties of two’s-complement arithmetic and can be Section 2.2 Integer Representations 95 We see that the bit patterns are identical for Figures 2.11 and 2.12 (as well as for Equations 2.2 and 2.4), but the values differ when the most significant bit is 1, since in one case it has weight +8, and in the other case it has weight −8. Let us consider the range of values that can be represented as a w-bit two’s- complement number. The least representable value is given by bit vector [10 ...0] (set the bit with negative weight, but clear all others), having integer value TMinw . = − 2w−1. The greatest value is given by bit vector [01 ...1] (clear the bit with negative weight, but set all others), having integer value TMaxw . = � w−2 i=0 2i =2w−1 − 1. Using the 4-bit case as an example, we have TMin4 = B2T4([1000]) = − 23 =−8, and TMax4 = B2T4([0111]) = 22 + 21 + 20 = 4 + 2 + 1 = 7. We can see that B2Tw is a mapping of bit patterns of length w to numbers be- tween TMinw and TMaxw, written as B2Tw: {0, 1} w → {− 2w−1,...,2w−1 − 1}. As we saw with the unsigned representation, every number within the representable range has a unique encoding as a w-bit two’s-complement number. In mathemat- ical terms, we say that the function B2Tw is a bijection—it associates a unique value to each bit vector of length w; conversely, each integer between − 2w−1 and 2w−1 − 1 has a unique binary representation as a bit vector of length w. Practice Problem 2.17 Assuming w = 4, we can assign a numeric value to each possible hexadecimal digit, assuming either an unsigned or a two’s-complement interpretation. Fill in the following table according to these interpretations by writing out the nonzero powers of two in the summations shown in Equations 2.1 and 2.3: �x Hexadecimal Binary B2U4(�x) B2T4(�x) 0xE [1110] 23 + 22 + 21 = 14 −23 + 22 + 21 =−2 0x0 0x5 0x8 0xD 0xF Figure 2.13 shows the bit patterns and numeric values for several important numbers for different word sizes. The first three give the ranges of representable integers in terms of the values of UMaxw, TMinw, and TMaxw. We will refer to these three special values often in the ensuing discussion. We will drop the subscript w and refer to the values UMax, TMin, and TMax when w can be inferred from context or is not central to the discussion. A few points are worth highlighting about these numbers. First, as observed in Figures 2.8 and 2.9, the two’s-complement range is asymmetric: |TMin|= |TMax|+1, that is, there is no positive counterpart to TMin. As we shall see, this leads to some peculiar properties of two’s-complement arithmetic and can be 。 图 2-12  w = 4 的补码示例。把位 3 作为符号位,因此当它为 1 时,对数值的影响是 -23 = -8。 这个权重在图中用带向左箭头的条表示 我们可以看出 B2Tw 是一个从长度为 w 的位模式到 TMinw 和 TMaxw 之间数字的映射,写做 B2Tw:{0,1}w → {-2w-1,…,2w-1-1}。同无符号表示一样,在可表示的取值范围内的每个数字都 有一个唯一的 w 位的补码编码。用数学语言来说就是 B2Tw 是一个双射—每个长度为 w 的位向 量都对应一个唯一的值 ;反过来,每个介于 -2w-1 和 2w-1-1 之间的整数都有一个唯一的长度为 w 的位向量二进制表示。 练习题 2.17 假设 w = 4,我们能给每个可能的十六进制数字赋予一个数值,假设用一个无符号或者 补码表示。请根据这些表示,通过写出等式(2-1)和等式(2-3)所示的求和公式中的 2 的非零次幂, 填写下表 : x→ B2U4( x → ) B2T4( x → )十六进制 二进制 0xE [1110] 23+22+21=14 -23+22+21=-2 0x0 0x5 0x8 0xD 0xF 图 2-13 展示了针对不同字长,几个重要数字的位模式和数值。前三个给出的是可表示的整 数的范围,用 UMaxw、TMinw 和 TMaxw 来表示。在后面的讨论中,我们还会经常引用到这三个 特殊的值。如果可以从上下文中推断出 w,或者 w 不是讨论的主要内容时,我们会省略下标 w, 直接引用 UMax、TMin 和 TMax。 关于这些数字,有几点值得注意。第一,从图 2-8 和图 2-9 可以看到,补码的范围是不对称 的 :|TMin| = |TMax| + 1,也就是说,TMin 没有与之对应的正数。正如我们将会看到的,这导致 补码运算的某些特殊的属性,并且容易造成程序中细微的错误。之所以会有这样的不对称性,是 因为一半的位模式(符号位设置为 1 的数)表示负数,而一半的数(符号位设置为 0 的数)表示 正文.indd 41 2010-10-19 14:18:17  42  第一部分 程序结构和执行  非负数。因为 0 是非负数,也就意味着能表示的正数比负数少一个。第二,最大的无符号数值刚 好比补码的最大值的两倍大一点 :UMaxw = 2 TMaxw + 1。补码表示中所有表示负数的位模式在无 符号表示中都变成了正数。图 2-13 也给出了常数 -1 和 0 的表示。注意 -1 和 UMax 有同样的位 表示—一个全 1 的串。数值 0 在两种表示方式中都是全 0 的串。 数 字长 w 8 16 32 64 UMaxw 0xFF 0xFFFF 0xFFFFFFFF 0xFFFFFFFFFFFFFFFF 255 65 535 4 294 967 295 18 446 744 073 709 551 615 TMinw 0x80 0x8000 0x80000000 0x8000000000000000 -128 -32 768 -2 147 483 648 -9 223 372 036 854 775 808 TMaxw 0x7F 0x7FFF 0x7FFFFFFF 0x7FFFFFFFFFFFFFFF 127 32 767 2 147 483 647 9 223 372 036 854 775 807 -1 0xFF 0xFFFF 0xFFFFFFFF 0xFFFFFFFFFFFFFFFF 0 0x00 0x0000 0x00000000 0x0000000000000000 图 2-13 重要的数字。图中给出了数字和十六进制表示 C 语言标准并没有要求用补码形式来表示有符号整数,但是几乎所有的机器都是这么做的。 程序员如果希望代码具有最大可移植性,能够在所有可能的机器上运行,那么除了图 2-10 所示 的那些范围之外,我们不应该假设任何可表示的数值范围,也不应该假设有符号数会使用何种 特殊的表示方式。另一方面,许多程序的书写都假设用补码来表示有符号数,并且具有图 2-8 和 图 2-9 所示的“典型的”取值范围,这些程序在大量的机器和编译器上也有可移植性。C 库中的 文件 定义了一组常量,来限定编译器运行的这台机器的不同整型数据类型的取值 范围。比如,它定义了常量 INT_MAX、INT_MIN 和 UINT_MAX,它们描述了有符号和无符号整 数的范围。对于一个补码的机器,数据类型 int 有 w 位,这些常量就对应于 TMaxw、TMinw 和 UMaxw 的值。 确定大小的整数类型 对于某些程序来说,用某个确定大小的表示来编码数据类型非常重要。例如,当编写程序, 使得机器能够按照一个标准协议在因特网上通信时,让数据类型与协议指定的数据类型兼容是非 常重要的。我们前面看到了,某些 C 数据类型,特别是 long 型,在不同的机器上有不同的取 值范围,而实际上 C 语言标准只指定了每种数据类型的最小范围,而不是确定的范围。虽然我 们可以选择与大多数机器上的标准表示兼容的数据类型,但是这也不能保证可移植性。 ISO C99 标准在文件 stdint.h 中引入了另一类整数类型。这个文件定义了一组数据类型, 它们的声明形如 intN_t 和 uintN_t,指定的是 N 位有符号和无符号整数。N 的具体值与实现 相关,但是大多数编译器允许的值为 8、16、32 和 64。因此,通过将它的类型声明为 uint16_t, 我们可以无歧义地声明一个 16 位无符号变量,而如果声明为 int32_t,就是一个 32 位有符号 变量。 这些数据类型对应着一组宏,定义了每个 N 的值对应的最小值和最大值。这些宏名字形如 INTN_MIN、INTN_MAX 和 UINTN_MAX。 关于整数数据类型的取值范围和表示,Java 标准是非常明确的。它要求采用补码表示,取值 范围与图 2-9 中 64 位的情况一样。在 Java 中,单字节数据类型称为 byte,而不是 char,而且 没有 long long 数据类型。这些非常具体的要求都是为了保证无论在什么机器上,Java 程序运 正文.indd 42 2010-10-19 14:18:17 第 2 章 信息的表示和处理  43  行的表现都能完全一样。 有符号数的其他表示方法 有符号数还有两种标准的表示方法 : 反码(Ones’ Complement):除了最高有效位的权是 -(2w-1-1) 而不是 -2w-1,它和补码是一 样的 : 原码(Sign-Magnitude):最高有效位是符号位,用来确定剩下的位应该取负权还是正权 : 这两种表示方法都有一个奇怪的属性,那就是对于数字 0 有两种不同的编码方式。这两种表 示方法,把 [00…0] 都解释为 +0。而值 -0 在原码中表示为 [10…0],在反码中表示为 [11…1]。 虽然过去生产过基于反码表示的机器,但是几乎所有的现代机器都使用补码。我们将看到在浮点 数中有使用原码编码。 请注意补码(Two’s complement)和反码(Ones’complement)中撇号的位置是不同的。术语 补码来源于这样一个情况,对于非负数 x,我们用 2w-x(这里只有一个 2)来计算 -x 的 w 位表示。 术语反码来源于这样一个属性,我们用 [111...1] -x(这里有很多个 1)来计算 -x 的反码表示。 作为一个示例,考虑下面的代码 : 98 Chapter 2 Representing and Manipulating Information 12,345 −12,345 53,191 Weight Bit Value Bit Value Bit Value 1 1 1 1 1 1 1 2 0 0 1 2 1 2 4 0 0 1 4 1 4 8 1 8 0 0 0 0 16 1 16 0 0 0 0 32 1 32 0 0 0 0 64 0 0 1 64 1 64 128 0 0 1 128 1 128 256 0 0 1 256 1 256 512 0 0 1 512 1 512 1,024 0 0 1 1,024 1 1,024 2,048 0 0 1 2,048 1 2,048 4,096 1 4,096 0 0 0 0 8,192 1 8,192 0 0 0 0 16,384 0 0 1 16,384 1 16,384 ±32,768 0 0 1 −32,768 1 32,768 Total 12,345 −12,345 53,191 Figure 2.14 Two’s-complement representations of 12,345 and −12,345, and unsigned representation of 53,191. Note that the latter two have identical bit representations. As an example, consider the following code: 1 short x = 12345; 2 short mx = -x; 3 4 show_bytes((byte_pointer) &x, sizeof(short)); 5 show_bytes((byte_pointer) &mx, sizeof(short)); When run on a big-endian machine, this code prints 30 39 and cf c7, indi- cating that x has hexadecimal representation 0x3039, while mx has hexadeci- mal representation 0xCFC7. Expanding these into binary, we get bit patterns [0011000000111001] for x and [1100111111000111] for mx. As Figure 2.14 shows, Equation 2.3 yields values 12,345 and −12,345 for these two bit patterns. Practice Problem 2.18 In Chapter 3, we will look at listings generated by a disassembler, a program that converts an executable program file back to a more readable ASCII form. These files contain many hexadecimal numbers, typically representing values in two’s- complement form. Being able to recognize these numbers and understand their 权 12 345 -12 345 53 191 位 值 位 值 位 值 1 1 1 1 1 1 1 2 0 0 1 2 1 2 4 0 0 1 4 1 4 8 1 8 0 0 0 0 16 1 16 0 0 0 0 32 1 32 0 0 0 0 64 0 0 1 64 1 64 128 0 0 1 128 1 128 256 0 0 1 256 1 256 512 0 0 1 512 1 512 1 024 0 0 1 1 024 1 1 024 2 048 0 0 1 2 048 1 2 048 4 096 1 4096 0 0 0 0 8 192 1 8192 0 0 0 0 16 384 0 0 1 16 384 1 16 384 ±32 768 0 0 1 -32 768 1 32 768 总计 12 345 -12 345 53 191 图 2-14 12 345 和 -12 345 的补码表示,以及 53 191 的无符号表示。注意后面两个数有相同的位表示 正文.indd 43 2010-10-19 14:18:17  44  第一部分 程序结构和执行  当在大端法机器上运行时,这段代码的输出为 30 39 和 cf c7,指明 x 的十六进制表示 为 0x3039,而 mx 的十六进制表示为 0xCFC7。将它们展开为二进制,我们得到 x 的位模式为 [0011000000111001],而 mx 的位模式为 [1100111111000111]。如图 2-14 所示,等式(2-3)对这 两个位模式生成的值为 12 345 和 -12 345。 练习题 2.18 在第 3 章,我们将看到由反汇编器生成的列表,反汇编器是一种将可执行程序文件转换 回可读性更好的 ASCII 码形式的程序。这些文件包含许多十六进制数字,都是用典型的补码形式来表示 这些值。能够认识这些数字并理解它们的意义(例如,它们是正数还是负数),是一项重要的技巧。   在下面的列表中,对于标号为 A ~ J(标记在右边)的那些行,将指令名(sub、mov 和 add) 右边显示的(用 32 位补码形式表示的)十六进制值转换为等价的十进制值。 Section 2.2 Integer Representations 99 significance (for example, whether they are negative or positive) is an important skill. For the lines labeled A–J (on the right) in the following listing, convert the hexadecimal values (in 32-bit two’s-complement form) shown to the right of the instruction names (sub, mov, and add) into their decimal equivalents: 8048337: 81 ec b8 01 00 00 sub $0x1b8,%esp A. 804833d: 8b 55 08 mov 0x8(%ebp),%edx 8048340: 83 c2 14 add $0x14,%edx B. 8048343: 8b 85 58 fe ff ff mov 0xfffffe58(%ebp),%eax C. 8048349: 03 02 add (%edx),%eax 804834b: 89 85 74 fe ff ff mov %eax,0xfffffe74(%ebp) D. 8048351: 8b 55 08 mov 0x8(%ebp),%edx 8048354: 83 c2 44 add $0x44,%edx E. 8048357: 8b 85 c8 fe ff ff mov 0xfffffec8(%ebp),%eax F. 804835d: 89 02 mov %eax,(%edx) 804835f: 8b 45 10 mov 0x10(%ebp),%eax G. 8048362: 03 45 0c add 0xc(%ebp),%eax H. 8048365: 89 85 ec fe ff ff mov %eax,0xfffffeec(%ebp) I. 804836b: 8b 45 08 mov 0x8(%ebp),%eax 804836e: 83 c0 20 add $0x20,%eax J. 8048371: 8b 00 mov (%eax),%eax 2.2.4 Conversions Between Signed and Unsigned C allows casting between different numeric data types. For example, suppose variable x is declared as int and u as unsigned. The expression (unsigned) x converts the value of x to an unsigned value, and (int) u converts the value of u to a signed integer. What should be the effect of casting signed value to unsigned, or vice versa? From a mathematical perspective, one can imagine several different conventions. Clearly, we want to preserve any value that can be represented in both forms. On the other hand, converting a negative value to unsigned might yield zero. Converting an unsigned value that is too large to be represented in two’s- complement form might yield TMax. For most implementations of C, however, the answer to this question is based on a bit-level perspective, rather than on a numeric one. For example, consider the following code: 1 short int v = -12345; 2 unsigned short uv = (unsigned short) v; 3 printf("v = %d, uv = %u\n", v, uv); When run on a two’s-complement machine, it generates the following output: v = -12345, uv = 53191 What we see here is that the effect of casting is to keep the bit values identical but change how these bits are interpreted. We saw in Figure 2.14 that the 16-bit 2.2.4 有符号数和无符号数之间的转换 C 语言允许在各种不同的数字数据类型之间做强制类型转换。例如,假设变量 x 声明为 int,u 声明为 unsigned。表达式 (unsigned)x 会将 x 的值转换成一个无符号数值,而 (int)u 将 u 的值转换成一个有符号整数。将有符号数强制类型转换成无符号数,或者反过来, 会得到什么结果呢?从数学的角度来说,可以想象到几种不同的规则。很明显,对于在两种形式 中都能表示的值,我们是想要保持不变的。另一方面,将负数转换成无符号数可能会得到 0。如 果转换的无符号数太大以至于超出了补码能够表示的范围,可能会得到 TMax。不过,对于大多 数 C 语言的实现来说,对这个问题的回答都是从位级角度来看的,而不是数的角度。 比如说,考虑下面的代码 : Section 2.2 Integer Representations 99 significance (for example, whether they are negative or positive) is an important skill. For the lines labeled A–J (on the right) in the following listing, convert the hexadecimal values (in 32-bit two’s-complement form) shown to the right of the instruction names (sub, mov, and add) into their decimal equivalents: 8048337: 81 ec b8 01 00 00 sub $0x1b8,%esp A. 804833d: 8b 55 08 mov 0x8(%ebp),%edx 8048340: 83 c2 14 add $0x14,%edx B. 8048343: 8b 85 58 fe ff ff mov 0xfffffe58(%ebp),%eax C. 8048349: 03 02 add (%edx),%eax 804834b: 89 85 74 fe ff ff mov %eax,0xfffffe74(%ebp) D. 8048351: 8b 55 08 mov 0x8(%ebp),%edx 8048354: 83 c2 44 add $0x44,%edx E. 8048357: 8b 85 c8 fe ff ff mov 0xfffffec8(%ebp),%eax F. 804835d: 89 02 mov %eax,(%edx) 804835f: 8b 45 10 mov 0x10(%ebp),%eax G. 8048362: 03 45 0c add 0xc(%ebp),%eax H. 8048365: 89 85 ec fe ff ff mov %eax,0xfffffeec(%ebp) I. 804836b: 8b 45 08 mov 0x8(%ebp),%eax 804836e: 83 c0 20 add $0x20,%eax J. 8048371: 8b 00 mov (%eax),%eax 2.2.4 Conversions Between Signed and Unsigned C allows casting between different numeric data types. For example, suppose variable x is declared as int and u as unsigned. The expression (unsigned) x converts the value of x to an unsigned value, and (int) u converts the value of u to a signed integer. What should be the effect of casting signed value to unsigned, or vice versa? From a mathematical perspective, one can imagine several different conventions. Clearly, we want to preserve any value that can be represented in both forms. On the other hand, converting a negative value to unsigned might yield zero. Converting an unsigned value that is too large to be represented in two’s- complement form might yield TMax. For most implementations of C, however, the answer to this question is based on a bit-level perspective, rather than on a numeric one. For example, consider the following code: 1 short int v = -12345; 2 unsigned short uv = (unsigned short) v; 3 printf("v = %d, uv = %u\n", v, uv); When run on a two’s-complement machine, it generates the following output: v = -12345, uv = 53191 What we see here is that the effect of casting is to keep the bit values identical but change how these bits are interpreted. We saw in Figure 2.14 that the 16-bit 在一台采用补码的机器上,上述代码会产生如下输出 : Section 2.2 Integer Representations 99 significance (for example, whether they are negative or positive) is an important skill. For the lines labeled A–J (on the right) in the following listing, convert the hexadecimal values (in 32-bit two’s-complement form) shown to the right of the instruction names (sub, mov, and add) into their decimal equivalents: 8048337: 81 ec b8 01 00 00 sub $0x1b8,%esp A. 804833d: 8b 55 08 mov 0x8(%ebp),%edx 8048340: 83 c2 14 add $0x14,%edx B. 8048343: 8b 85 58 fe ff ff mov 0xfffffe58(%ebp),%eax C. 8048349: 03 02 add (%edx),%eax 804834b: 89 85 74 fe ff ff mov %eax,0xfffffe74(%ebp) D. 8048351: 8b 55 08 mov 0x8(%ebp),%edx 8048354: 83 c2 44 add $0x44,%edx E. 8048357: 8b 85 c8 fe ff ff mov 0xfffffec8(%ebp),%eax F. 804835d: 89 02 mov %eax,(%edx) 804835f: 8b 45 10 mov 0x10(%ebp),%eax G. 8048362: 03 45 0c add 0xc(%ebp),%eax H. 8048365: 89 85 ec fe ff ff mov %eax,0xfffffeec(%ebp) I. 804836b: 8b 45 08 mov 0x8(%ebp),%eax 804836e: 83 c0 20 add $0x20,%eax J. 8048371: 8b 00 mov (%eax),%eax 2.2.4 Conversions Between Signed and Unsigned C allows casting between different numeric data types. For example, suppose variable x is declared as int and u as unsigned. The expression (unsigned) x converts the value of x to an unsigned value, and (int) u converts the value of u to a signed integer. What should be the effect of casting signed value to unsigned, or vice versa? From a mathematical perspective, one can imagine several different conventions. Clearly, we want to preserve any value that can be represented in both forms. On the other hand, converting a negative value to unsigned might yield zero. Converting an unsigned value that is too large to be represented in two’s- complement form might yield TMax. For most implementations of C, however, the answer to this question is based on a bit-level perspective, rather than on a numeric one. For example, consider the following code: 1 short int v = -12345; 2 unsigned short uv = (unsigned short) v; 3 printf("v = %d, uv = %u\n", v, uv); When run on a two’s-complement machine, it generates the following output: v = -12345, uv = 53191 What we see here is that the effect of casting is to keep the bit values identical but change how these bits are interpreted. We saw in Figure 2.14 that the 16-bit 我们看到,强制类型转换的结果保持位值不变,只是改变了解释这些位的方式。在图 2-14 中 我们看到过,-12 345 的 16 位补码表示与 53 191 的 16 位无符号表示是完全一样的。将 short int 强制类型转换为 unsigned short 改变数值,但是不改变位表示。 类似地,考虑下面的代码 : 100 Chapter 2 Representing and Manipulating Information two’s-complement representation of −12,345 is identical to the 16-bit unsigned representation of 53,191. Casting from short int to unsigned short changed the numeric value, but not the bit representation. Similarly, consider the following code: 1 unsigned u = 4294967295u; /* UMax_32 */ 2 int tu = (int) u; 3 printf("u = %u, tu = %d\n", u, tu); When run on a two’s-complement machine, it generates the following output: u = 4294967295, tu = -1 We can see from Figure 2.13 that, for a 32-bit word size, the bit patterns represent- ing 4,294,967,295 (UMax32) in unsigned form and −1 in two’s-complement form are identical. In casting from unsigned int to int, the underlying bit representa- tion stays the same. This is a general rule for how most C implementations handle conversions between signed and unsigned numbers with the same word size—the numeric values might change, but the bit patterns do not. Let us capture this principle in a more mathematical form. Since both B2Uw and B2Tw are bijections, they have well-defined inverses. Define U2Bw to be B2U−1 w , and T2Bw to be B2T−1 w . These functions give the unsigned or two’s-complement bit patterns for a numeric value. That is, given an integer x in the range 0 ≤ x<2w, the function U2Bw(x) gives the unique w-bit unsigned representation of x. Similarly, when x is in the range − 2w−1 ≤ x<2w−1, the function T2Bw(x) gives the unique w-bit two’s- complement representation of x. Observe that for values in the range 0 ≤ x<2w−1, both of these functions will yield the same bit representation—the most significant bit will be 0, and hence it does not matter whether this bit has positive or negative weight. Now define the function U2Tw as U2Tw(x) . = B2Tw(U2Bw(x)). This function takes a number between 0 and 2w − 1 and yields a number between − 2w−1 and 2w−1 − 1, where the two numbers have identical bit representations, except that the argument is unsigned, while the result has a two’s-complement representa- tion. Similarly, for x between − 2w−1 and 2w−1 − 1, the function T2Uw, defined as T2Uw(x) . = B2Uw(T2Bw(x)), yields the number having the same unsigned repre- sentation as the two’s-complement representation of x. Pursuing our earlier examples, we see from Figure 2.14 that T2U16(−12,345) = 53,191, and U2T16(53,191) =−12,345. That is, the 16-bit pattern written in hexadecimal as 0xCFC7 is both the two’s-complement representation of −12,345 and the unsigned representation of 53,191. Similarly, from Figure 2.13, we see that T2U32(−1) = 4,294,967,295, and U2T32(4,294,967,295) =−1. That is, UMax has the same bit representation in unsigned form as does −1 in two’s-complement form. We see, then, that function U2T describes the conversion of an unsigned number to its 2-complement counterpart, while T2U converts in the opposite 正文.indd 44 2010-10-19 14:18:18 第 2 章 信息的表示和处理  45  在一台采用补码的机器上,上述代码会产生如下输出 : 100 Chapter 2 Representing and Manipulating Information two’s-complement representation of −12,345 is identical to the 16-bit unsigned representation of 53,191. Casting from short int to unsigned short changed the numeric value, but not the bit representation. Similarly, consider the following code: 1 unsigned u = 4294967295u; /* UMax_32 */ 2 int tu = (int) u; 3 printf("u = %u, tu = %d\n", u, tu); When run on a two’s-complement machine, it generates the following output: u = 4294967295, tu = -1 We can see from Figure 2.13 that, for a 32-bit word size, the bit patterns represent- ing 4,294,967,295 (UMax32) in unsigned form and −1 in two’s-complement form are identical. In casting from unsigned int to int, the underlying bit representa- tion stays the same. This is a general rule for how most C implementations handle conversions between signed and unsigned numbers with the same word size—the numeric values might change, but the bit patterns do not. Let us capture this principle in a more mathematical form. Since both B2Uw and B2Tw are bijections, they have well-defined inverses. Define U2Bw to be B2U−1 w , and T2Bw to be B2T−1 w . These functions give the unsigned or two’s-complement bit patterns for a numeric value. That is, given an integer x in the range 0 ≤ x<2w, the function U2Bw(x) gives the unique w-bit unsigned representation of x. Similarly, when x is in the range − 2w−1 ≤ x<2w−1, the function T2Bw(x) gives the unique w-bit two’s- complement representation of x. Observe that for values in the range 0 ≤ x<2w−1, both of these functions will yield the same bit representation—the most significant bit will be 0, and hence it does not matter whether this bit has positive or negative weight. Now define the function U2Tw as U2Tw(x) . = B2Tw(U2Bw(x)). This function takes a number between 0 and 2w − 1 and yields a number between − 2w−1 and 2w−1 − 1, where the two numbers have identical bit representations, except that the argument is unsigned, while the result has a two’s-complement representa- tion. Similarly, for x between − 2w−1 and 2w−1 − 1, the function T2Uw, defined as T2Uw(x) . = B2Uw(T2Bw(x)), yields the number having the same unsigned repre- sentation as the two’s-complement representation of x. Pursuing our earlier examples, we see from Figure 2.14 that T2U16(−12,345) = 53,191, and U2T16(53,191) =−12,345. That is, the 16-bit pattern written in hexadecimal as 0xCFC7 is both the two’s-complement representation of −12,345 and the unsigned representation of 53,191. Similarly, from Figure 2.13, we see that T2U32(−1) = 4,294,967,295, and U2T32(4,294,967,295) =−1. That is, UMax has the same bit representation in unsigned form as does −1 in two’s-complement form. We see, then, that function U2T describes the conversion of an unsigned number to its 2-complement counterpart, while T2U converts in the opposite 从图 2-13 我们可以看到,对于 32 位字长来说,无符号形式的 4 294 967 295(UMax32)和补 码形式的 -1 的位模式是完全一样的。将 unsigned int 强制类型转换成 int,底层的位表示 保持不变。 对大多数 C 语言的实现而言,处理同样字长的有符号数和无符号数之间相互转换的一般规 则是 :数值可能会改变,但是位模式不变。下面我们用更数学化的形式来描述这个规则。既然 B2Uw 和 B2Tw 都是双射,它们就有定义明确的逆映射。将 U2Bw 定义为 B2Uw -1,而将 T2Bw 定义 为 B2Tw -1。这些函数给出了一个数值的无符号或者补码的位模式。也就是说,给定 0 ≤ x < 2w 范围 内的一个整数 x,函数 U2Bw(x) 会给出 x 的唯一的 w 位无符号表示。相似地,当 x 满足 -2w-1 ≤ x < 2w-1,函数 T2Bw(x) 会给出 x 的唯一的 w 位补码表示。可以观察到,对于在范围 0 ≤ x < 2w-1 内的值,这两个函数将生成同样的位模式—最高位是 0,因此这个位是正权还是负权就没有关 系了。 现在,将函数 U2Tw 定义为 U2Tw(x) = 4 B2Tw(U2Bw(x))。这个函数的输入是一个 0 ~ 2w­­-1 之 间的数,结果得到一个 -2w-1 ~ 2w-1-1 之间的值,这里两个数有相同的位模式,除了参数是无 符号的,而结果是以补码表示的。类似地,对于 -2w-1 ~ 2w-1-1 之间的值 x,函数 T2Uw 定义为 T2Uw(x) = 4 B2Uw(T2Bw(x)),生成一个数的无符号表示和 x 的补码表示相同。 继续我们前面的例子,从图 2-14 中,我们看到 T2U16(-12 345) = 53 191,并且 U2T16(53 191) = -12 345。 也就是说,十六进制表示写做 0xCFC7 的 16 位位模式既是 -12 345 的补码表示,又是 53 191 的 无符号表示。类似地,从图 2-13 我们看到 T2U32(-1) = 4 294 967 295,并且 U2T32(4 294 967 295) = -1。也就是说,无符号表示中的 UMax 有着和补码表示的 -1 相同的位模式。 接下来,我们看到函数 U2T 描述了从无符号数到补码的转换,而 T2U 描述的是补码到无符 号的转换。这两个函数描述了在大多数 C 语言实现中这两种数据类型之间的强制类型转换效果。 练习题 2.19 利用你解答练习题 2.17 时填写的表格,填写下列描述函数 T2U4 的表格。 x T2U4(x) -8 -3 -2 -1 0 5 为了更好地理解一个有符号数字 x 和与之对应的无符号数 T2Uw(x) 之间的关系,我们可以利 用它们有相同的位表示这一事实,推导出一个数字关系。比较等式(2-1)和等式(2-3),可以 发现对于位模式 x→,如果我们计算 B2Uw(x→ → ) - B2Tw( x → ) 之差,从 0 到 w-2 的位的加权和将互相抵 消掉,剩下一个值 :B2Uw( x → )-B2Tw( x → ) = xw-1(2w-1--2w-1) = xw-12w。这就得到一个关系 :B2Uw( x → ) = xw-12w + B2Tw( x → )。如果令 x→ = T2Bw(x),我们就得到以下公式 B2Uw(T2Bw (x)) = T2Uw (x) = xw-12w + x (2-5) 这个关系对于证明无符号和补码运算之间的关系是很有用的。在 x 的补码表示中,位 xw-1 决定了 x 是否为负,得到 T2Uw(x) = x+2w,x < 0 x, x ≥0                 (2-6) 正文.indd 45 2010-10-19 14:18:19  46  第一部分 程序结构和执行  比如说,图 2-15 比较了当 w = 4 时,函数 B2U 和 B2T 是如何将数值变成位模式的。对补码来 说,最高有效位是符号位,我们用带向左箭头的条来表示。对于无符号数来说,最高有效位有正权 重,我们用带向右箭头的条来表示。从补码变为无符号数,最高有效位的权重从 -8 变为 +8。因此, 补码表示的负数如果看成无符号数,值会增加 24 = 16。因而,-5 变成了 +11,而 -1 变成了 +15。 图 2-15 比较当 w = 4 时无符号表示和补码表示(对补码和无符号数来说, 最高有效位的权重分别是 -8 和 +8,因而产生一个差为 16) 图 2-16 说明了函数 T2U 的一般行为。如图所示,当将一个有符号数映射为它相应的无符号 数时,负数就被转换成了大的正数,而非负数会保持不变。 图 2-16 从补码到无符号数的转换。函数 T2U 将负数转换为大的正数 练习题 2.20 请说明在解答练习题 2.19 时生成的表格中,你是如何将等式(2-6)应用到其中各项的。 反过来看,我们希望推导出一个无符号数 u 和与之对应的有符号数 U2Tw(u) 之间的关系。如 果设 u→ = U2Bw(u),我们得到以下公式 B2Tw(U2Bw(u)) = U2Tw(u) = -uw-12w + u (2-7) 在 u 的无符号表示中,位 uw-1 决定了 u 是否大于或者等于 2w-1,得到 U2Tw(u) = u, x < 2w-1 u-2w, u ≥ 2w-1 (2-8) 图 2-17 说明了这个行为。对于小的数(<2w-1),从无符号到有符号的转换将保留数字的原 值。对于大的数(≥ 2w-1),数字将被转换为一个负数值。 总结一下,我们考虑无符号与补码表示之间互相转换的结果。对于在 0 ≤ x < 2w-1 范围之内 的值 x 而言,我们得到 T2Uw (x) = x 和 U2Tw (x) = x。也就是说,在这个范围内的数字有相同的 无符号和补码表示。对于这个范围以外的数值,转换需要加上或者减去 2w。例如,我们有 T2Uw (-1) = -1 + 2w = UMaxw—最靠近 0 的负数映射为最大的无符号数。在另一个极端,我们 正文.indd 46 2010-10-19 14:18:19 第 2 章 信息的表示和处理  47  可以看到 T2Uw (TMinw) = -2w-1 + 2w = 2w-1 = TMaxw + 1—最小的负数映射为一个刚好在补码的正数 范围之外的无符号数。使用图 2-14 的示例,我们能得到 T2U16(-12 345) = 65 563 + -12 345 = 53 191。 图 2-17 从无符号数到补码的转换。函数 U2T 把大于 2w-1-1 的数字转换为负值 2.2.5 C 语言中的有符号数与无符号数 如图 2-8 和图 2-9 所示,C 语言支持所有整型数据类型的有符号和无符号运算。尽管 C 语言 标准没有指定有符号数要采用某种表示,但是几乎所有的机器都使用补码。通常,大多数数字都 默认为是有符号的。例如,当声明一个像 12345 或者 0x1A2B 这样的常量时,这个值就被认为 是有符号的。要创建一个无符号常量,必须加上后缀字符‘U’或者‘u’。例如,12345U 或者 0x1A2Bu。 C 语言允许无符号数和有符号数之间的转换。转换的原则是底层的位表示保持不变。因此, 在一台采用补码的机器上,当从无符号数转换为有符号数时,效果就是应用函数 U2Tw,而从有 符号数转换为无符号数时,就是应用函数 T2Uw,其中 w 表示数据类型的位数。 显式的强制类型转换就会导致转换发生,就像下面的代码 : Section 2.2 Integer Representations 103 Figure 2.17 Conversion from un- signed to two’s com- plement. Function U2T converts numbers greater than 2w−1 − 1 to negative values. �2w�1 0 �2w�1 2w 0 2w�1 Two’s complement Unsigned This behavior is illustrated in Figure 2.17. For small (< 2w−1) numbers, the conver- sion from unsigned to signed preserves the numeric value. Large (≥ 2w−1) numbers are converted to negative values. To summarize, we considered the effects of converting in both directions be- tween unsigned and two’s-complement representations. For values x in the range 0 ≤ x<2w−1, we have T2Uw(x) = x and U2Tw(x) = x. That is, numbers in this range have identical unsigned and two’s-complement representations. For val- ues outside of this range, the conversions either add or subtract 2w. For exam- ple, we have T2Uw(−1) =−1 + 2w = UMaxw—the negative number closest to 0 maps to the largest unsigned number. At the other extreme, one can see that T2Uw(TMinw) =−2w−1 + 2w = 2w−1 = TMaxw + 1—the most negative number maps to an unsigned number just outside the range of positive, two’s-complement numbers. Using the example of Figure 2.14, we can see that T2U16(−12,345) =65,536 +−12,345 = 53,191. 2.2.5 Signed vs. Unsigned in C As indicated in Figures 2.8 and 2.9, C supports both signed and unsigned arithmetic for all of its integer data types. Although the C standard does not specify a particu- lar representation of signed numbers, almost all machines use two’s complement. Generally, most numbers are signed by default. For example, when declaring a constant such as 12345 or 0x1A2B, the value is considered signed. Adding charac- ter ‘U’or‘u’ as a suffix creates an unsigned constant, e.g., 12345U or 0x1A2Bu. C allows conversion between unsigned and signed. The rule is that the under- lying bit representation is not changed. Thus, on a two’s-complement machine, the effect is to apply the function U2Tw when converting from unsigned to signed, and T2Uw when converting from signed to unsigned, where w is the number of bits for the data type. Conversions can happen due to explicit casting, such as in the following code: 1 int tx, ty; 2 unsigned ux, uy; 3 4 tx = (int) ux; 5 uy = (unsigned) ty; 另外,当一种类型的表达式被赋值给另外一种类型的变量时,转换是隐式发生的,就像下面 的代码 : 104 Chapter 2 Representing and Manipulating Information Alternatively, they can happen implicitly when an expression of one type is as- signed to a variable of another, as in the following code: 1 int tx, ty; 2 unsigned ux, uy; 3 4 tx = ux; /* Cast to signed */ 5 uy = ty; /* Cast to unsigned */ When printing numeric values with printf, the directives %d, %u, and %x are used to print a number as a signed decimal, an unsigned decimal, and in hexadecimal format, respectively. Note that printf does not make use of any type information, and so it is possible to print a value of type int with directive %u and a value of type unsigned with directive %d. For example, consider the following code: 1 int x = -1; 2 unsigned u = 2147483648; /* 2 to the 31st */ 3 4 printf("x = %u = %d\n", x, x); 5 printf("u = %u = %d\n", u, u); When run on a 32-bit machine, it prints the following: x = 4294967295 = -1 u = 2147483648 = -2147483648 In both cases, printf prints the word first as if it represented an unsigned number, and second as if it represented a signed number. We can see the conversion routines in action: T2U32(−1) = UMax32 = 232 − 1 and U2T32(231) = 231 − 232 = − 231 = TMin32. Some of the peculiar behavior arises due to C’s handling of expressions con- taining combinations of signed and unsigned quantities. When an operation is performed where one operand is signed and the other is unsigned, C implicitly casts the signed argument to unsigned and performs the operations assuming the numbers are nonnegative. As we will see, this convention makes little dif- ference for standard arithmetic operations, but it leads to nonintuitive results for relational operators such as < and >. Figure 2.18 shows some sample rela- tional expressions and their resulting evaluations, assuming a 32-bit machine us- ing two’s-complement representation. Consider the comparison -1 < 0U. Since the second operand is unsigned, the first one is implicitly cast to unsigned, and hence the expression is equivalent to the comparison 4294967295U < 0U (recall that T2Uw(−1) = UMaxw), which of course is false. The other cases can be under- stood by similar analyses. Practice Problem 2.21 Assuming the expressions are evaluated on a 32-bit machine that uses two’s- complement arithmetic, fill in the following table describing the effect of casting and relational operations, in the style of Figure 2.18: 当用 printf 输出数值时,分别用指示符 %d、%u 和 %x 以有符号十进制、无符号十进制和 十六进制格式输出一个数字。注意 printf 没有使用任何类型信息,所以它可以用指示符 %u 来 输出类型为 int 的数值,也可以用指示符 %d 输出类型为 unsigned 的数值。例如,考虑下面 的代码 : 104 Chapter 2 Representing and Manipulating Information Alternatively, they can happen implicitly when an expression of one type is as- signed to a variable of another, as in the following code: 1 int tx, ty; 2 unsigned ux, uy; 3 4 tx = ux; /* Cast to signed */ 5 uy = ty; /* Cast to unsigned */ When printing numeric values with printf, the directives %d, %u, and %x are used to print a number as a signed decimal, an unsigned decimal, and in hexadecimal format, respectively. Note that printf does not make use of any type information, and so it is possible to print a value of type int with directive %u and a value of type unsigned with directive %d. For example, consider the following code: 1 int x = -1; 2 unsigned u = 2147483648; /* 2 to the 31st */ 3 4 printf("x = %u = %d\n", x, x); 5 printf("u = %u = %d\n", u, u); When run on a 32-bit machine, it prints the following: x = 4294967295 = -1 u = 2147483648 = -2147483648 In both cases, printf prints the word first as if it represented an unsigned number, and second as if it represented a signed number. We can see the conversion routines in action: T2U32(−1) = UMax32 = 232 − 1 and U2T32(231) = 231 − 232 = − 231 = TMin32. Some of the peculiar behavior arises due to C’s handling of expressions con- taining combinations of signed and unsigned quantities. When an operation is performed where one operand is signed and the other is unsigned, C implicitly casts the signed argument to unsigned and performs the operations assuming the numbers are nonnegative. As we will see, this convention makes little dif- ference for standard arithmetic operations, but it leads to nonintuitive results for relational operators such as < and >. Figure 2.18 shows some sample rela- tional expressions and their resulting evaluations, assuming a 32-bit machine us- ing two’s-complement representation. Consider the comparison -1 < 0U. Since the second operand is unsigned, the first one is implicitly cast to unsigned, and hence the expression is equivalent to the comparison 4294967295U < 0U (recall that T2Uw(−1) = UMaxw), which of course is false. The other cases can be under- stood by similar analyses. Practice Problem 2.21 Assuming the expressions are evaluated on a 32-bit machine that uses two’s- complement arithmetic, fill in the following table describing the effect of casting and relational operations, in the style of Figure 2.18: 正文.indd 47 2010-10-19 14:18:20  48  第一部分 程序结构和执行  当在一个 32 位机器上运行时,它的输出如下 : 104 Chapter 2 Representing and Manipulating Information Alternatively, they can happen implicitly when an expression of one type is as- signed to a variable of another, as in the following code: 1 int tx, ty; 2 unsigned ux, uy; 3 4 tx = ux; /* Cast to signed */ 5 uy = ty; /* Cast to unsigned */ When printing numeric values with printf, the directives %d, %u, and %x are used to print a number as a signed decimal, an unsigned decimal, and in hexadecimal format, respectively. Note that printf does not make use of any type information, and so it is possible to print a value of type int with directive %u and a value of type unsigned with directive %d. For example, consider the following code: 1 int x = -1; 2 unsigned u = 2147483648; /* 2 to the 31st */ 3 4 printf("x = %u = %d\n", x, x); 5 printf("u = %u = %d\n", u, u); When run on a 32-bit machine, it prints the following: x = 4294967295 = -1 u = 2147483648 = -2147483648 In both cases, printf prints the word first as if it represented an unsigned number, and second as if it represented a signed number. We can see the conversion routines in action: T2U32(−1) = UMax32 = 232 − 1 and U2T32(231) = 231 − 232 = − 231 = TMin32. Some of the peculiar behavior arises due to C’s handling of expressions con- taining combinations of signed and unsigned quantities. When an operation is performed where one operand is signed and the other is unsigned, C implicitly casts the signed argument to unsigned and performs the operations assuming the numbers are nonnegative. As we will see, this convention makes little dif- ference for standard arithmetic operations, but it leads to nonintuitive results for relational operators such as < and >. Figure 2.18 shows some sample rela- tional expressions and their resulting evaluations, assuming a 32-bit machine us- ing two’s-complement representation. Consider the comparison -1 < 0U. Since the second operand is unsigned, the first one is implicitly cast to unsigned, and hence the expression is equivalent to the comparison 4294967295U < 0U (recall that T2Uw(−1) = UMaxw), which of course is false. The other cases can be under- stood by similar analyses. Practice Problem 2.21 Assuming the expressions are evaluated on a 32-bit machine that uses two’s- complement arithmetic, fill in the following table describing the effect of casting and relational operations, in the style of Figure 2.18: 在这两种情况下,printf 首先将这个字当作一个无符号数输出,然后把它当作一个有符号 数输出。以下是实际运行中的转换函数 :T2U32(-1) = UMax32 = 232-1 和 U2T32(231) = 231 - 232 = - 231 = TMin32。 由于 C 语言对同时包含有符号和无符号数表达式的这种处理方式,出现了一些奇特的行 为。当执行一个运算时,如果它的一个运算数是有符号的而另一个是无符号的,那么 C 语言会 隐式地将有符号参数强制类型转换为无符号数,并假设这两个数都是非负的,来执行这个运算。 就像我们将要看到的,这种方法对于标准的算术运算来说并无多大差异,但是对于像 < 和 > 这 样的关系运算符来说,它会导致非直观的结果。图 2-18 展示了一些关系表达式的示例以及它 们得到的求值结果,这里假设使用的是一台采用补码的 32 位机器。考虑比较式 -1<0U。因为 第二个运算数是无符号的,第一个运算数就会被隐式地转换为无符号数,因此表达式就等价于 4294967295U<0U(回想 T2Uw(-1) = UMaxw),这个答案显然是错的。其他那些示例也可以通 过相似的分析来理解。 表 达 式 类  型 求  值 0 == 0U 无符号 1 -1 < 0 有符号 1 -1 < 0U 无符号 0* 2147483647 > -2147483647-1 有符号 1 2147483647U > -2147483647-1 无符号 0* 2147483647 > (int) 2147483648U 有符号 1* -1 > -2 有符号 1 (unsigned) -1 > -2 无符号 1 图 2-18 C 语言的升级规则的效果 注 : 非直观的情况标注了‘*’。当一个运算数是无符号的时候,另一个运算数也被隐式强制转换为无符号。将 TMin32 写为 -2147483647-1 的原因请参见网络旁注 DATA:TMIN。 练习题 2.21 假设在采用补码运算的 32 位机器上对这些表达式求值,按照图 2-18 的格式填写下表, 描述强制类型转换和关系运算的结果。 表达式 类  型 求  值 -2147483647-1 == 2147483648U -2147483647-1 < 2147483647 -2147483647-1U < 2147483647 -2147483647-1 < -2147483647 -2147483647-1U < -2147483647 网络旁注 DATA:TMIN C 语言中 TMin 的写法 在图 2-18 和练习题 2.21 中,我们很小心地将 TMin32 写成 -2147483647-1。为什么不简单 地写成 -2147483648 或者 0x80000000 ?看一下 C 头文件 limits.h,注意到它们使用了跟 我们写 TMin32 和 TMax32 类似的方法 : 正文.indd 48 2010-10-19 14:18:20 第 2 章 信息的表示和处理  49  不幸的是,补码表示的不对称性和 C 语言转换规则之间这种奇怪的交互,迫使我们用这种 不寻常的方式来写 TMin32。虽然理解这个问题需要我们钻研 C 语言标准的一些比较隐晦的角落, 但是它能够帮助我们充分领会整数数据类型和表示的一些细微之处。 2.2.6 扩展一个数字的位表示 一种常见的运算是在不同字长的整数之间转换,同时又保持数值不变。当然,当目标数据类 型太小以至于不能表示想要的值时,这根本就是不可能的。然而,从一个较小的数据类型转换 到一个较大的类型,这应该总是可能的。将一个无符号数转换为一个更大的数据类型,我们只 需要简单地在表示的开头添加 0,这种运算称为零扩展(zero extension)。将一个补码数字转换 为一个更大的数据类型可以执行符号扩展(sign extension),规则是在表示中添加最高有效位的 值的副本。由此可知,如果我们原始值的位表示为 [xw-1,xw-2,…,x0],那么扩展后的表示就为 [xw-1,…,xw-1,xw-1,xw-2,…,x0]。(我们用浅灰色标出符号位 xw-1 来突出它们在符号扩展中的 角色。) 例如,考虑下面的代码 : 106 Chapter 2 Representing and Manipulating Information an unsigned number to a larger data type, we can simply add leading zeros to the representation; this operation is known as zero extension. For converting a two’s- complement number to a larger data type, the rule is to perform a sign extension, adding copies of the most significant bit to the representation. Thus, if our original value has bit representation [xw−1,xw−2,...,x0], the expanded representation is [xw−1,...,xw−1, xw−1,xw−2,...,x0]. (We show the sign bit xw−1 in blue to highlight its role in sign extension.) As an example, consider the following code: 1 short sx = -12345; /* -12345 */ 2 unsigned short usx = sx; /* 53191 */ 3 int x = sx; /* -12345 */ 4 unsigned ux = usx; /* 53191 */ 5 6 printf("sx = %d:\t", sx); 7 show_bytes((byte_pointer) &sx, sizeof(short)); 8 printf("usx = %u:\t", usx); 9 show_bytes((byte_pointer) &usx, sizeof(unsigned short)); 10 printf("x = %d:\t", x); 11 show_bytes((byte_pointer) &x, sizeof(int)); 12 printf("ux = %u:\t", ux); 13 show_bytes((byte_pointer) &ux, sizeof(unsigned)); When run on a 32-bit big-endian machine using a two’s-complement representa- tion, this code prints the output sx = -12345: cf c7 usx = 53191: cf c7 x = -12345: ff ff cf c7 ux = 53191: 00 00 cf c7 We see that although the two’s-complement representation of −12,345 and the unsigned representation of 53,191 are identical for a 16-bit word size, they dif- fer for a 32-bit word size. In particular, −12,345 has hexadecimal representation 0xFFFFCFC7, while 53,191 has hexadecimal representation 0x0000CFC7. The for- mer has been sign extended—16 copies of the most significant bit 1, having hexa- decimal representation 0xFFFF, have been added as leading bits. The latter has been extended with 16 leading zeros, having hexadecimal representation 0x0000. As an illustration, Figure 2.19 shows the result of applying expanding from word size w = 3 to w = 4 by sign extension. Bit vector [101] represents the value −4 + 1 =−3. Applying sign extension gives bit vector [1101] representing the value −8 + 4 + 1 =−3. We can see that, for w = 4, the combined value of the two most significant bits is −8 + 4 =−4, matching the value of the sign bit for w = 3. Similarly, bit vectors [111] and [1111] both represent the value −1. Can we justify that sign extension works? What we want to prove is that B2Tw+k([xw−1,...,xw−1 � �� � k times , xw−1,xw−2,...,x0]) = B2Tw([xw−1,xw−2,...,x0]) 在采用补码表示的 32 位大端法机器上运行这段代码时,打印出如下输出 : 106 Chapter 2 Representing and Manipulating Information an unsigned number to a larger data type, we can simply add leading zeros to the representation; this operation is known as zero extension. For converting a two’s- complement number to a larger data type, the rule is to perform a sign extension, adding copies of the most significant bit to the representation. Thus, if our original value has bit representation [xw−1,xw−2,...,x0], the expanded representation is [xw−1,...,xw−1, xw−1,xw−2,...,x0]. (We show the sign bit xw−1 in blue to highlight its role in sign extension.) As an example, consider the following code: 1 short sx = -12345; /* -12345 */ 2 unsigned short usx = sx; /* 53191 */ 3 int x = sx; /* -12345 */ 4 unsigned ux = usx; /* 53191 */ 5 6 printf("sx = %d:\t", sx); 7 show_bytes((byte_pointer) &sx, sizeof(short)); 8 printf("usx = %u:\t", usx); 9 show_bytes((byte_pointer) &usx, sizeof(unsigned short)); 10 printf("x = %d:\t", x); 11 show_bytes((byte_pointer) &x, sizeof(int)); 12 printf("ux = %u:\t", ux); 13 show_bytes((byte_pointer) &ux, sizeof(unsigned)); When run on a 32-bit big-endian machine using a two’s-complement representa- tion, this code prints the output sx = -12345: cf c7 usx = 53191: cf c7 x = -12345: ff ff cf c7 ux = 53191: 00 00 cf c7 We see that although the two’s-complement representation of −12,345 and the unsigned representation of 53,191 are identical for a 16-bit word size, they dif- fer for a 32-bit word size. In particular, −12,345 has hexadecimal representation 0xFFFFCFC7, while 53,191 has hexadecimal representation 0x0000CFC7. The for- mer has been sign extended—16 copies of the most significant bit 1, having hexa- decimal representation 0xFFFF, have been added as leading bits. The latter has been extended with 16 leading zeros, having hexadecimal representation 0x0000. As an illustration, Figure 2.19 shows the result of applying expanding from word size w = 3 to w = 4 by sign extension. Bit vector [101] represents the value −4 + 1 =−3. Applying sign extension gives bit vector [1101] representing the value −8 + 4 + 1 =−3. We can see that, for w = 4, the combined value of the two most significant bits is −8 + 4 =−4, matching the value of the sign bit for w = 3. Similarly, bit vectors [111] and [1111] both represent the value −1. Can we justify that sign extension works? What we want to prove is that B2Tw+k([xw−1,...,xw−1 � �� � k times , xw−1,xw−2,...,x0]) = B2Tw([xw−1,xw−2,...,x0]) 我们看到,尽管 -12 345 的补码表示和 53 191 的无符号表示在 16 位字长时是相同的,但是在 32 位字长时却是不同的。特别地,-12 345 的十六进制表示为 0xFFFFCFC7,而 53 191 的十六进 制表示为 0x0000CFC7。前者使用的是符号扩展—最开头加了 16 位,都是最高有效位 1,表示 为十六进制就是 0xFFFF。后者开头使用 16 个 0 来扩展,表示为十六进制就是 0x0000。 图 2-19 给出了从字长 w = 3 到 w = 4 的符号扩展的结果。位向量 [101] 表示值 -4 + 1 = -3。对它应 用符号扩展,得到位向量 [1101],表示的值 -8 + 4 + 1 = -3。我们可以看到,对于 w = 4,最高两位的组 合值是 -8 + 4 = -4,与 w = 3 时符号位的值相同。类似地,位向量 [111] 和 [1111] 都表示值 -1。 如何证明符号扩展工作是否正确呢?我们想要证明的是 106 Chapter 2 Representing and Manipulating Information an unsigned number to a larger data type, we can simply add leading zeros to the representation; this operation is known as zero extension. For converting a two’s- complement number to a larger data type, the rule is to perform a sign extension, adding copies of the most significant bit to the representation. Thus, if our original value has bit representation [xw−1,xw−2,...,x0], the expanded representation is [xw−1,...,xw−1, xw−1,xw−2,...,x0]. (We show the sign bit xw−1 in blue to highlight its role in sign extension.) As an example, consider the following code: 1 short sx = -12345; /* -12345 */ 2 unsigned short usx = sx; /* 53191 */ 3 int x = sx; /* -12345 */ 4 unsigned ux = usx; /* 53191 */ 5 6 printf("sx = %d:\t", sx); 7 show_bytes((byte_pointer) &sx, sizeof(short)); 8 printf("usx = %u:\t", usx); 9 show_bytes((byte_pointer) &usx, sizeof(unsigned short)); 10 printf("x = %d:\t", x); 11 show_bytes((byte_pointer) &x, sizeof(int)); 12 printf("ux = %u:\t", ux); 13 show_bytes((byte_pointer) &ux, sizeof(unsigned)); When run on a 32-bit big-endian machine using a two’s-complement representa- tion, this code prints the output sx = -12345: cf c7 usx = 53191: cf c7 x = -12345: ff ff cf c7 ux = 53191: 00 00 cf c7 We see that although the two’s-complement representation of −12,345 and the unsigned representation of 53,191 are identical for a 16-bit word size, they dif- fer for a 32-bit word size. In particular, −12,345 has hexadecimal representation 0xFFFFCFC7, while 53,191 has hexadecimal representation 0x0000CFC7. The for- mer has been sign extended—16 copies of the most significant bit 1, having hexa- decimal representation 0xFFFF, have been added as leading bits. The latter has been extended with 16 leading zeros, having hexadecimal representation 0x0000. As an illustration, Figure 2.19 shows the result of applying expanding from word size w = 3 to w = 4 by sign extension. Bit vector [101] represents the value −4 + 1 =−3. Applying sign extension gives bit vector [1101] representing the value −8 + 4 + 1 =−3. We can see that, for w = 4, the combined value of the two most significant bits is −8 + 4 =−4, matching the value of the sign bit for w = 3. Similarly, bit vectors [111] and [1111] both represent the value −1. Can we justify that sign extension works? What we want to prove is that B2Tw+k([xw−1,...,xw−1 � �� � k times , xw−1,xw−2,...,x0]) = B2Tw([xw−1,xw−2,...,x0]) k 次 这里,在表达式的左边,我们增加了 k 位 xw-1 的副本。下面的证明是对 k 进行的归纳。也就是 说,如果我们能够证明符号扩展一位保持数值不变,那么符号扩展任意位都能保持这种属性。因 此,证明的任务就变为证明以下等式 : 正文.indd 49 2010-10-19 14:18:21  50  第一部分 程序结构和执行  Section 2.2 Integer Representations 107 Figure 2.19 Examples of sign exten- sion from w = 3 to w = 4. For w = 4, the combined weight of the upper 2 bits is −8 + 4 =−4, matching that of the sign bit for w = 3. 876543210–1–2–3–4–5–6–7–8 20 = 1 21 = 2 22 = 4 –23 = –8 [101] [1101] [111] [1111] –22 = –4 where, in the expression on the left-hand side, we have made k additional copies of bit xw−1. The proof follows by induction on k. That is, if we can prove that sign extending by 1 bit preserves the numeric value, then this property will hold when sign extending by an arbitrary number of bits. Thus, the task reduces to proving that B2Tw+1([xw−1, xw−1,xw−2,...,x0]) = B2Tw([xw−1,xw−2,...,x0]) Expanding the left-hand expression with Equation 2.3 gives the following: B2Tw+1([xw−1, xw−1,xw−2,...,x0]) =−xw−12w + w−1 �i=0 xi2i =−xw−12w + xw−12w−1 + w−2 �i=0 xi2i =−xw−1 � 2w − 2w−1 � + w−2 �i=0 xi2i =−xw−12w−1 + w−2 �i=0 xi2i = B2Tw([xw−1,xw−2,...,x0]) The key property we exploit is that 2w − 2w−1 = 2w−1. Thus, the combined effect of adding a bit of weight − 2w and of converting the bit having weight − 2w−1 to be one with weight 2w−1 is to preserve the original numeric value. 用等式(2-3)展开左边的表达式,得到 : Section 2.2 Integer Representations 107 Figure 2.19 Examples of sign exten- sion from w = 3 to w = 4. For w = 4, the combined weight of the upper 2 bits is −8 + 4 =−4, matching that of the sign bit for w = 3. 876543210–1–2–3–4–5–6–7–8 20 = 1 21 = 2 22 = 4 –23 = –8 [101] [1101] [111] [1111] –22 = –4 where, in the expression on the left-hand side, we have made k additional copies of bit xw−1. The proof follows by induction on k. That is, if we can prove that sign extending by 1 bit preserves the numeric value, then this property will hold when sign extending by an arbitrary number of bits. Thus, the task reduces to proving that B2Tw+1([xw−1, xw−1,xw−2,...,x0]) = B2Tw([xw−1,xw−2,...,x0]) Expanding the left-hand expression with Equation 2.3 gives the following: B2Tw+1([xw−1, xw−1,xw−2,...,x0]) =−xw−12w + w−1 �i=0 xi2i =−xw−12w + xw−12w−1 + w−2 �i=0 xi2i =−xw−1 � 2w − 2w−1 � + w−2 �i=0 xi2i =−xw−12w−1 + w−2 �i=0 xi2i = B2Tw([xw−1,xw−2,...,x0]) The key property we exploit is that 2w − 2w−1 = 2w−1. Thus, the combined effect of adding a bit of weight − 2w and of converting the bit having weight − 2w−1 to be one with weight 2w−1 is to preserve the original numeric value. 我们使用的关键属性是 2w-2w-1= 2w-1。因此,加上一个权值为 -2w 的位,和将一个权值为 -2w-1 的位转换为一个权值为 2w-1 的位,这两项运算的综合效果就会保持原始的数值。     图 2-19  从 w = 3 到 w = 4 的符号扩展示例。对于 w = 4,最高两位组合权重为 -8 + 4 = -4, 与 w = 3 时的符号位的权重一样 练习题 2.22 应用等式(2-3),证明下列每个位向量都是 -5 的补码表示。 A. [1011] B. [11011] C. [111011] 可以看到第二个和第三个位向量可以通过对第一个位向量做符号扩展得到。 值得一提的是,从一个数据大小到另一个数据大小的转换,以及无符号和有符号数字之间的 转换的相对顺序能够影响一个程序的行为。考虑下面的代码 : 108 Chapter 2 Representing and Manipulating Information Practice Problem 2.22 Show that each of the following bit vectors is a two’s-complement representation of −5 by applying Equation 2.3: A. [1011] B. [11011] C. [111011] Observe that the second and third bit vectors can be derived from the first by sign extension. One point worth making is that the relative order of conversion from one data size to another and between unsigned and signed can affect the behavior of a program. Consider the following code: 1 short sx = -12345; /* -12345 */ 2 unsigned uy = sx; /* Mystery! */ 3 4 printf("uy = %u:\t", uy); 5 show_bytes((byte_pointer) &uy, sizeof(unsigned)); When run on a big-endian machine, this code causes the following output to be printed: uy = 4294954951: ff ff cf c7 This shows that when converting from short to unsigned, we first change the size and then from signed to unsigned. That is, (unsigned) sx is equivalent to (unsigned) (int) sx, evaluating to 4,294,954,951, not (unsigned) (unsigned short) sx, which evaluates to 53,191. Indeed this convention is required by the C standards. Practice Problem 2.23 Consider the following C functions: int fun1(unsigned word) { return (int) ((word << 24) >> 24); } int fun2(unsigned word) { return ((int) word << 24) >> 24; } Assume these are executed on a machine with a 32-bit word size that uses two’s- complement arithmetic. Assume also that right shifts of signed values are per- formed arithmetically, while right shifts of unsigned values are performed logically. 正文.indd 50 2010-10-19 14:18:21 第 2 章 信息的表示和处理  51  在一台大端法机器上,这部分代码产生如下输出 : uy = 4294954951: ff ff cf c7 这表明当把 short 转换成 unsigned 时,我们先要改变大小,之后再完成从有符号到无符 号的转换。也就是说(unsigned)sx 等价于(unsigned)(int)sx,求值得到 4 294 954 951, 而不等价于(unsigned)(unsigned short)sx,后者求值得到 53 191。事实上,这个规则 是 C 语言标准要求的。 练习题 2.23 考虑下面的 C 函数 : 108 Chapter 2 Representing and Manipulating Information Practice Problem 2.22 Show that each of the following bit vectors is a two’s-complement representation of −5 by applying Equation 2.3: A. [1011] B. [11011] C. [111011] Observe that the second and third bit vectors can be derived from the first by sign extension. One point worth making is that the relative order of conversion from one data size to another and between unsigned and signed can affect the behavior of a program. Consider the following code: 1 short sx = -12345; /* -12345 */ 2 unsigned uy = sx; /* Mystery! */ 3 4 printf("uy = %u:\t", uy); 5 show_bytes((byte_pointer) &uy, sizeof(unsigned)); When run on a big-endian machine, this code causes the following output to be printed: uy = 4294954951: ff ff cf c7 This shows that when converting from short to unsigned, we first change the size and then from signed to unsigned. That is, (unsigned) sx is equivalent to (unsigned) (int) sx, evaluating to 4,294,954,951, not (unsigned) (unsigned short) sx, which evaluates to 53,191. Indeed this convention is required by the C standards. Practice Problem 2.23 Consider the following C functions: int fun1(unsigned word) { return (int) ((word << 24) >> 24); } int fun2(unsigned word) { return ((int) word << 24) >> 24; } Assume these are executed on a machine with a 32-bit word size that uses two’s- complement arithmetic. Assume also that right shifts of signed values are per- formed arithmetically, while right shifts of unsigned values are performed logically. 假设在一个采用补码运算的 32 位字长的机器上执行这些函数。还假设有符号数值的右移是算术右移, 而无符号数值的右移是逻辑右移。 A. 填写下表,说明这些函数对几个示例参数的结果。你会发现用十六进制表示来做会更方便 , 只要记 住十六进制数字 8 到 F 的最高有效位等于 1。 w fun1(w) fun2(w) 0x00000076 0x87654321 0x000000C9 0xEDCBA987 B. 用语言来描述这些函数执行的有用的计算。 2.2.7 截断数字 假设我们不用额外的位来扩展一个数值,而是减少表示一个数字的位数。例如下面代码中这 种情况 : Section 2.2 Integer Representations 109 A. Fill in the following table showing the effect of these functions for several example arguments. You will find it more convenient to work with a hexa- decimal representation. Just remember that hex digits 8 through F have their most significant bits equal to 1. w fun1(w) fun2(w) 0x00000076 0x87654321 0x000000C9 0xEDCBA987 B. Describe in words the useful computation each of these functions performs. 2.2.7 Truncating Numbers Suppose that, rather than extending a value with extra bits, we reduce the number of bits representing a number. This occurs, for example, in the code: 1 int x = 53191; 2 short sx = (short) x; /* -12345 */ 3 int y = sx; /* -12345 */ On a typical 32-bit machine, when we cast x to be short, we truncate the 32-bit int to be a 16-bit short int. As we saw before, this 16-bit pattern is the two’s-complement representation of −12,345. When we cast this back to int, sign extension will set the high-order 16 bits to ones, yielding the 32-bit two’s- complement representation of −12,345. When truncating a w-bit number �x = [xw−1,xw−2,...,x0] to a k-bit number, we drop the high-order w − k bits, giving a bit vector �x� = [xk−1,xk−2,...,x0]. Truncating a number can alter its value—a form of overflow. We now investigate what numeric value will result. For an unsigned number x, the result of truncating it to k bits is equivalent to computing x mod 2k. This can be seen by applying the modulus operation to Equation 2.1: B2Uw([xw−1,xw−2,...,x0]) mod 2k = � w−1 �i=0 xi2i � mod 2k = � k−1 �i=0 xi2i � mod 2k = k−1 �i=0 xi2i = B2Uk([xk−1,xk−2,...,x0]) 在一台典型的 32 位机器上,当把 x 强制类型转换为 short 时,我们就将 32 位的 int 截断 为 16 位的 short int。就像前面所看到的,这个 16 位的位模式就是 -12 345 的补码表示。当 我们把它强制类型转换回 int 时,符号扩展把高 16 位设置为 1,从而生成 -12 345 的 32 位补码 表示。 将一个 w 位的数 4x→=[xw-1, xw-2,…, x0] 截断为一个 k 位数字时,我们会丢弃高 w-k 位,得到一 个位向量 4x→' =[xk-1, xk-2,…, x0]。截断一个数字可能会改变它的值—溢出的一种形式。我们现在来 研究什么样的数值会产生这种情况。对于一个无符号数字 x,截断它到 k 位的结果就相当于计算 x mod 2k。通过对等式(2-1)应用取模运算就可以得到 : 正文.indd 51 2010-10-19 14:18:22  52  第一部分 程序结构和执行  在这段推导中,我们利用的属性是 :对于任何 i ≥ k,2i mod 2k = 0 和 110 Chapter 2 Representing and Manipulating Information In this derivation, we make use of the property that 2i mod 2k = 0 for any i ≥ k, and that � k−1 i=0 xi2i ≤ � k−1 i=0 2i = 2k − 1 < 2k. For a two’s-complement number x, a similar argument shows that B2Tw([xw−1,xw−2,...,x0]) mod 2k = B2Uk([xk−1,xk−2,...,x0]). That is, x mod 2k can be represented by an unsigned number having bit-level representation [xk−1,xk−2,...,x0]. In general, however, we treat the truncated number as being signed. This will have numeric value U2Tk(x mod 2k). Summarizing, the effect of truncation for unsigned numbers is B2Uk([xk−1,xk−2,...,x0]) = B2Uw([xw−1,xw−2,...,x0]) mod 2k, (2.9) while the effect for two’s-complement numbers is B2Tk([xk−1,xk−2,...,x0]) = U2Tk(B2Uw([xw−1,xw−2,...,x0]) mod 2k) (2.10) Practice Problem 2.24 Suppose we truncate a 4-bit value (represented by hex digits 0 through F)toa3- bit value (represented as hex digits 0 through 7). Fill in the table below showing the effect of this truncation for some cases, in terms of the unsigned and two’s- complement interpretations of those bit patterns. Hex Unsigned Two’s complement Original Truncated Original Truncated Original Truncated 00 0 0 22 2 2 91 9 −7 B311 −5 F715 −1 Explain how Equations 2.9 and 2.10 apply to these cases. 2.2.8 Advice on Signed vs. Unsigned As we have seen, the implicit casting of signed to unsigned leads to some non- intuitive behavior. Nonintuitive features often lead to program bugs, and ones involving the nuances of implicit casting can be especially difficult to see. Since the casting takes place without any clear indication in the code, programmers often overlook its effects. The following two practice problems illustrate some of the subtle errors that can arise due to implicit casting and the unsigned data type. 。 对于一个补码数字 x,相似的推理表明 B2Tw ([xw-1, xw-2,…, x0]) mod 2k = B2Uk[xk-1, xk-2,…, x0]。 也就是,x mod 2k 能够被一个位级表示为 [xk-1,…, x0] 的无符号数表示。不过,一般而言,我们将 被截断的数字视为有符号的。这将得到数值 U2Tk(x mod 2k)。 总而言之,无符号数的截断结果是 : 110 Chapter 2 Representing and Manipulating Information In this derivation, we make use of the property that 2i mod 2k = 0 for any i ≥ k, and that � k−1 i=0 xi2i ≤ � k−1 i=0 2i = 2k − 1 < 2k. For a two’s-complement number x, a similar argument shows that B2Tw([xw−1,xw−2,...,x0]) mod 2k = B2Uk([xk−1,xk−2,...,x0]). That is, x mod 2k can be represented by an unsigned number having bit-level representation [xk−1,xk−2,...,x0]. In general, however, we treat the truncated number as being signed. This will have numeric value U2Tk(x mod 2k). Summarizing, the effect of truncation for unsigned numbers is B2Uk([xk−1,xk−2,...,x0]) = B2Uw([xw−1,xw−2,...,x0]) mod 2k, (2.9) while the effect for two’s-complement numbers is B2Tk([xk−1,xk−2,...,x0]) = U2Tk(B2Uw([xw−1,xw−2,...,x0]) mod 2k) (2.10) Practice Problem 2.24 Suppose we truncate a 4-bit value (represented by hex digits 0 through F)toa3- bit value (represented as hex digits 0 through 7). Fill in the table below showing the effect of this truncation for some cases, in terms of the unsigned and two’s- complement interpretations of those bit patterns. Hex Unsigned Two’s complement Original Truncated Original Truncated Original Truncated 00 0 0 22 2 2 91 9 −7 B311 −5 F715 −1 Explain how Equations 2.9 and 2.10 apply to these cases. 2.2.8 Advice on Signed vs. Unsigned As we have seen, the implicit casting of signed to unsigned leads to some non- intuitive behavior. Nonintuitive features often lead to program bugs, and ones involving the nuances of implicit casting can be especially difficult to see. Since the casting takes place without any clear indication in the code, programmers often overlook its effects. The following two practice problems illustrate some of the subtle errors that can arise due to implicit casting and the unsigned data type. , (2-9) 而补码数字的截断结果是 : 110 Chapter 2 Representing and Manipulating Information In this derivation, we make use of the property that 2i mod 2k = 0 for any i ≥ k, and that � k−1 i=0 xi2i ≤ � k−1 i=0 2i = 2k − 1 < 2k. For a two’s-complement number x, a similar argument shows that B2Tw([xw−1,xw−2,...,x0]) mod 2k = B2Uk([xk−1,xk−2,...,x0]). That is, x mod 2k can be represented by an unsigned number having bit-level representation [xk−1,xk−2,...,x0]. In general, however, we treat the truncated number as being signed. This will have numeric value U2Tk(x mod 2k). Summarizing, the effect of truncation for unsigned numbers is B2Uk([xk−1,xk−2,...,x0]) = B2Uw([xw−1,xw−2,...,x0]) mod 2k, (2.9) while the effect for two’s-complement numbers is B2Tk([xk−1,xk−2,...,x0]) = U2Tk(B2Uw([xw−1,xw−2,...,x0]) mod 2k) (2.10) Practice Problem 2.24 Suppose we truncate a 4-bit value (represented by hex digits 0 through F)toa3- bit value (represented as hex digits 0 through 7). Fill in the table below showing the effect of this truncation for some cases, in terms of the unsigned and two’s- complement interpretations of those bit patterns. Hex Unsigned Two’s complement Original Truncated Original Truncated Original Truncated 00 0 0 22 2 2 91 9 −7 B311 −5 F715 −1 Explain how Equations 2.9 and 2.10 apply to these cases. 2.2.8 Advice on Signed vs. Unsigned As we have seen, the implicit casting of signed to unsigned leads to some non- intuitive behavior. Nonintuitive features often lead to program bugs, and ones involving the nuances of implicit casting can be especially difficult to see. Since the casting takes place without any clear indication in the code, programmers often overlook its effects. The following two practice problems illustrate some of the subtle errors that can arise due to implicit casting and the unsigned data type. (2-10) 练习题 2.24 假设将一个 4 位数值(用十六进制数字 0 ~ F 表示)截断到一个 3 位数值(用十六进制数 字 0 ~ 7 表示)。填写下表,根据那些位模式的无符号和补码解释,说明这种截断对某些情况的结果。 十六进制 无符号 补码 原始值 截断值 原始值 截断值 原始值 截断值 0 0 0 0 2 2 2 2 9 1 9 -7 B 3 11 -5 F 7 15 -1 解释如何将等式(2-9)和等式(2-10)应用到这些示例上。 2.2.8 关于有符号数与无符号数的建议 就像我们看到的那样,有符号数到无符号数的隐式强制类型转换导致了某些非直观的行为。而 这些非直观的特性经常导致程序错误,并且这种包含隐式强制类型转换细微差别的错误很难被发现。 因为这种强制类型转换是在代码中没有明确指示的情况下发生的,程序员经常忽视了它的影响。 下面两个练习题说明了某些由于隐式强制类型转换和无符号数据类型造成的细微错误。 练习题 2.25 考虑下列代码,这段代码试图计算数组a中所有元素的和,其中元素的数量由参数length给出。 Section 2.2 Integer Representations 111 Practice Problem 2.25 Consider the following code that attempts to sum the elements of an array a, where the number of elements is given by parameter length: 1 /* WARNING: This is buggy code */ 2 float sum_elements(float a[], unsigned length) { 3 int i; 4 float result = 0; 5 6 for (i = 0; i <= length-1; i++) 7 result += a[i]; 8 return result; 9 } When run with argument length equal to 0, this code should return 0.0. Instead it encounters a memory error. Explain why this happens. Show how this code can be corrected. Practice Problem 2.26 You are given the assignment of writing a function that determines whether one string is longer than another. You decide to make use of the string library function strlen having the following declaration: /* Prototype for library function strlen */ size_t strlen(const char *s); Here is your first attempt at the function: /* Determine whether string s is longer than string t */ /* WARNING: This function is buggy */ int strlonger(char *s, char *t) { return strlen(s) - strlen(t) > 0; } When you test this on some sample data, things do not seem to work quite right. You investigate further and determine that data type size_t is defined (via typedef) in header file stdio.h to be unsigned int. A. For what cases will this function produce an incorrect result? B. Explain how this incorrect result comes about. C. Show how to fix the code so that it will work reliably.   当参数 length 等于 0 时,运行这段代码应该返回 0.0。但实际上,运行时会遇到一个存储器错误。 请解释为什么会发生这样的情况,并且说明如何修改代码。 Section 2.2 Integer Representations 109 A. Fill in the following table showing the effect of these functions for several example arguments. You will find it more convenient to work with a hexa- decimal representation. Just remember that hex digits 8 through F have their most significant bits equal to 1. w fun1(w) fun2(w) 0x00000076 0x87654321 0x000000C9 0xEDCBA987 B. Describe in words the useful computation each of these functions performs. 2.2.7 Truncating Numbers Suppose that, rather than extending a value with extra bits, we reduce the number of bits representing a number. This occurs, for example, in the code: 1 int x = 53191; 2 short sx = (short) x; /* -12345 */ 3 int y = sx; /* -12345 */ On a typical 32-bit machine, when we cast x to be short, we truncate the 32-bit int to be a 16-bit short int. As we saw before, this 16-bit pattern is the two’s-complement representation of −12,345. When we cast this back to int, sign extension will set the high-order 16 bits to ones, yielding the 32-bit two’s- complement representation of −12,345. When truncating a w-bit number �x = [xw−1,xw−2,...,x0] to a k-bit number, we drop the high-order w − k bits, giving a bit vector �x� = [xk−1,xk−2,...,x0]. Truncating a number can alter its value—a form of overflow. We now investigate what numeric value will result. For an unsigned number x, the result of truncating it to k bits is equivalent to computing x mod 2k. This can be seen by applying the modulus operation to Equation 2.1: B2Uw([xw−1,xw−2,...,x0]) mod 2k = � w−1 �i=0 xi2i � mod 2k = � k−1 �i=0 xi2i � mod 2k = k−1 �i=0 xi2i = B2Uk([xk−1,xk−2,...,x0]) ≤ 正文.indd 52 2010-10-19 14:18:22 第 2 章 信息的表示和处理  53  练习题 2.26 现在给你一个任务,写一个函数用来判定一个字符串是否比另一个更长。前提是你要用 字符串库函数 strlen,它的声明如下 : Section 2.2 Integer Representations 111 Practice Problem 2.25 Consider the following code that attempts to sum the elements of an array a, where the number of elements is given by parameter length: 1 /* WARNING: This is buggy code */ 2 float sum_elements(float a[], unsigned length) { 3 int i; 4 float result = 0; 5 6 for (i = 0; i <= length-1; i++) 7 result += a[i]; 8 return result; 9 } When run with argument length equal to 0, this code should return 0.0. Instead it encounters a memory error. Explain why this happens. Show how this code can be corrected. Practice Problem 2.26 You are given the assignment of writing a function that determines whether one string is longer than another. You decide to make use of the string library function strlen having the following declaration: /* Prototype for library function strlen */ size_t strlen(const char *s); Here is your first attempt at the function: /* Determine whether string s is longer than string t */ /* WARNING: This function is buggy */ int strlonger(char *s, char *t) { return strlen(s) - strlen(t) > 0; } When you test this on some sample data, things do not seem to work quite right. You investigate further and determine that data type size_t is defined (via typedef) in header file stdio.h to be unsigned int. A. For what cases will this function produce an incorrect result? B. Explain how this incorrect result comes about. C. Show how to fix the code so that it will work reliably. 最开始你写的函数是这样的 : Section 2.2 Integer Representations 111 Practice Problem 2.25 Consider the following code that attempts to sum the elements of an array a, where the number of elements is given by parameter length: 1 /* WARNING: This is buggy code */ 2 float sum_elements(float a[], unsigned length) { 3 int i; 4 float result = 0; 5 6 for (i = 0; i <= length-1; i++) 7 result += a[i]; 8 return result; 9 } When run with argument length equal to 0, this code should return 0.0. Instead it encounters a memory error. Explain why this happens. Show how this code can be corrected. Practice Problem 2.26 You are given the assignment of writing a function that determines whether one string is longer than another. You decide to make use of the string library function strlen having the following declaration: /* Prototype for library function strlen */ size_t strlen(const char *s); Here is your first attempt at the function: /* Determine whether string s is longer than string t */ /* WARNING: This function is buggy */ int strlonger(char *s, char *t) { return strlen(s) - strlen(t) > 0; } When you test this on some sample data, things do not seem to work quite right. You investigate further and determine that data type size_t is defined (via typedef) in header file stdio.h to be unsigned int. A. For what cases will this function produce an incorrect result? B. Explain how this incorrect result comes about. C. Show how to fix the code so that it will work reliably. 当你在一些示例数据上测试这个函数时,一切似乎都是正确的。进一步研究发现在头文件 stdio.h 中数据类型 size_t 是定义成 unsigned int 的。 A. 在什么情况下,这个函数会产生不正确的结果? B. 解释为什么会出现这样不正确的结果。 C. 说明如何修改这段代码好让它能可靠地工作。 函数 getpeername 的安全漏洞 2002 年,从事 FreeBSD 开源操作系统项目的程序员意识到,他们对 getpeername 函数的 实现存在安全漏洞。代码的简化版本如下 : 在这段代码里,第 7 行给出的是库函数 memcpy 的原型,这个函数是要将一段指定长度为 n 的字节从存储器的一个区域复制到另一个区域。 从第 14 行开始的函数 copy_from_kernel 是要将一些操作系统内核维护的数据复制到指 定的用户可以访问的存储器区域。对用户来说,大多数内核维护的数据结构应该是不可读的,因 为这些数据结构可能包含其他用户和系统上运行的其他作业的敏感信息,但是显示为 kbuf 的区 域是用户可以读的。参数 maxlen 给出的是分配给用户的缓冲区的长度,这个缓冲区是用参数 user_dest 指示的。然后,第 16 行的计算确保复制的字节数据不会超出源或者目标缓冲区可 正文.indd 53 2010-10-19 14:18:23  54  第一部分 程序结构和执行  用的范围。 不过,假设有些怀有恶意的程序员在调用 copy_from_kernel 的代码中对 maxlen 使用 了负数值,那么,第 16 行的最小值计算会把这个值赋给 len,然后 len 会作为参数 n 被传递 给 memcpy。不过,请注意参数 n 是被声明为数据类型 size_t 的。这个数据类型是在库文件 stdio.h 中(通过 typedef)被声明的。典型地,在 32 位机器上被定义为 unsigned int。 既然参数 n 是无符号的,那么 memcpy 会把它当作一个非常大的正整数,并且试图将这样多字 节的数据从内核区域复制到用户的缓冲区。虽然复制这么多字节(至少 231 个)实际上不会完成, 因为程序会遇到进程中非法地址的错误,但是程序还是能读到没有被授权的内核存储器区域。 我们可以看到,这个问题是由于数据类型的不匹配造成的 :在一个地方,长度参数是有符号 数 ;而另一个地方,它又是无符号数。正如这个例子表明的那样,这样的不匹配会成为缺陷的原 因,甚至会导致安全漏洞。幸运的是,还没有案例报告有程序员在 FreeBSD 上利用了这个漏洞。 他们发布了一个安全建议,“FreeBSD-SA-02:38.signed-error”,建议系统管理员如何应用补丁消除这个 漏洞。要修正这个缺陷,只要将 copy_from_kernel 的参数 maxlen 声明为类型 size_t,也就是 与 memcpy 的参数 n 一致。同时,我们也应该将本地变量 len 和返回值声明为 size_t。 我们已经看到了由于许多无符号运算的细微特性,尤其是有符号数到无符号数的隐式转换, 会导致错误或者漏洞的方式。避免这类错误的一种方法就是绝不使用无符号数。实际上,除了 C 以外,很少有语言支持无符号整数。很明显,这些语言的设计者认为它们带来的麻烦要比益处多 得多。例如,Java 只支持有符号整数,并且要求用补码运算来实现。正常的右移运算符 >> 被定 义为执行算术右移。特殊的运算符 >>> 被指定为执行逻辑右移。 当我们想要把字仅仅看做是位的集合,并且没有任何数字意义时,无符号数值是非常有用 的。例如,往一个字中放入描述各种布尔条件的标记(flag)时,就是这样。地址自然地就是无 符号的,所以系统程序员发现无符号类型是很有帮助的。当实现模运算和多精度运算的数学包 时,数字是由字的数组来表示的,无符号值也会非常有用。 2.3 整数运算 许多刚入门的程序员非常惊奇地发现,两个正数相加会得出一个负数,并且比较表达式 x 0,考虑值 2w-x。我们观察到这个数字在 0 ≤ 2w-x < 2w 范围之内,并且 (x + 2w-x) mod 2w = 2w mod 2w = 0。因此,它就是 x 在+w u 下的逆元。这两种情况就导出了对于 0 ≤ x < 2w 的等式 :      116 Chapter 2 Representing and Manipulating Information 16 14 12 10 8 6 4 2 0 Overflow Normal 0 2 4 6 8 10 12 14 0 2 4 6 8 10 12 14 Unsigned addition (4–bit word) Figure 2.22 Unsigned addition. With a 4-bit word size, addition is performed modulo 16. Modular addition forms a mathematical structure known as an abelian group, named after the Danish mathematician Niels Henrik Abel (1802–1829). That is, it is commutative (that’s where the “abelian” part comes in) and associative; it has an identity element 0, and every element has an additive inverse. Let us consider the set of w-bit unsigned numbers with addition operation +u w. For every value x, there must be some value -u w x such that -u w x +u w x = 0. When x = 0, the additive inverse is clearly 0. For x>0, consider the value 2w − x. Observe that this number is in the range 0 < 2w − x<2w, and (x + 2w − x)mod 2w = 2w mod 2w = 0. Hence, it is the inverse of x under +u w. These two cases lead to the following equation for 0 ≤ x<2w: -u w x = � x, x = 0 2w − x, x > 0 (2.12) Practice Problem 2.28 We can represent a bit pattern of length w = 4 with a single hex digit. For an unsigned interpretation of these digits, use Equation 2.12 to fill in the following               (2-12) 练习题 2.28 我们能用一个十六进制数字来表示长度 w = 4 的位模式。对于这些数字的无符号解释, 使用等式(2-12)填写下表,给出所示数字的无符号加法逆元的位表示(用十六进制形式)。 正文.indd 56 2010-10-19 14:18:33 第 2 章 信息的表示和处理  57  x -4 xu 十六进制 十进制 十进制 十六进制 0 5 8 D F 2.3.2 补码加法 对于补码加法,我们必须确定当结果太大(为正)或者太小(为负)时,应该做些什么。给 定在范围 -2w-1 ≤ x, y ≤ 2w-1-1 之内的整数值 x 和 y,它们的和就在范围 -2w ≤ x + y ≤ 2w-2 之 内,要想准确表示,可能需要 w+1 位。就像以前一样,我们通过将表示截断到 w 位,来避免数 据大小的不断扩张。然而,结果却不像模数加法那样在数学上感觉很熟悉。 两个数的 w 位补码之和与无符号之和有完全相同的位级表示。实际上,大多数计算机使用 同样的机器指令来执行无符号或者有符号加法。因此,我们能够定义字长为 w 的、运算数 x 和 y 上的补码加法(x 和 y 满足 -2w-1 ≤ x, y ≤ 2w-1),表示为 + w t : Section 2.3 Integer Arithmetic 117 table giving the values and the bit representations (in hex) of the unsigned additive inverses of the digits shown. x -u 4 x Hex Decimal Decimal Hex 0 5 8 D F 2.3.2 Two’s-Complement Addition With two’s-complement addition, we must decide what to do when the result is either too large (positive) or too small (negative) to represent. Given integer values x and y in the range −2w−1 ≤ x, y ≤ 2w−1 − 1, their sum is in the range − 2w ≤ x + y ≤ 2w − 2, potentially requiring w + 1 bits to represent exactly. As before, we avoid ever-expanding data sizes by truncating the representation to w bits. The result is not as familiar mathematically as modular addition, however. The w-bit two’s-complement sum of two numbers has the exact same bit-level representation as the unsigned sum. In fact, most computers use the same machine instruction to perform either unsigned or signed addition. Thus, we can define two’s-complement addition for word size w, denoted as +t w, on operands x and y such that − 2w−1 ≤ x, y < 2w−1 as x +t w y . = U2Tw(T2Uw(x) +u w T2Uw(y)) (2.13) By Equation 2.5, we can write T2Uw(x) as xw−12w + x, and T2Uw(y) as yw−12w +y. Using the property that +u w is simply addition modulo 2w, along with the prop- erties of modular addition, we then have x +t w y = U2Tw(T2Uw(x) +u w T2Uw(y)) = U2Tw[(xw−12w + x + yw−12w + y) mod 2w] = U2Tw[(x + y) mod 2w] The terms xw−12w and yw−12w drop out since they equal 0 modulo 2w. To better understand this quantity, let us define z as the integer sum z . = x + y, z� as z� . = z mod 2w, and z�� as z�� . = U2Tw(z�). The value z�� is equal to x +t w y. We can divide the analysis into four cases, as illustrated in Figure 2.23: 1. − 2w ≤ z<− 2w−1. Then we will have z� = z + 2w. This gives 0 ≤ z� < − 2w−1 +2w = 2w−1. Examining Equation 2.8, we see that z� is in the range such that z�� = z�. This case is referred to as negative overflow. We have added two negative numbers x and y (that’s the only way we can have z<− 2w−1) and obtained a nonnegative result z�� = x + y + 2w.           (2-13) 根据等式(2-5),我们可以把 T2Uw(x) 写成 xw-12w + x,把 T2Uw(y) 写成 yw-12w + y。使用属 性,即 + w u是模 2w 的加法,以及模数加法的属性,我们就能得到 : Section 2.3 Integer Arithmetic 117 table giving the values and the bit representations (in hex) of the unsigned additive inverses of the digits shown. x -u 4 x Hex Decimal Decimal Hex 0 5 8 D F 2.3.2 Two’s-Complement Addition With two’s-complement addition, we must decide what to do when the result is either too large (positive) or too small (negative) to represent. Given integer values x and y in the range −2w−1 ≤ x, y ≤ 2w−1 − 1, their sum is in the range − 2w ≤ x + y ≤ 2w − 2, potentially requiring w + 1 bits to represent exactly. As before, we avoid ever-expanding data sizes by truncating the representation to w bits. The result is not as familiar mathematically as modular addition, however. The w-bit two’s-complement sum of two numbers has the exact same bit-level representation as the unsigned sum. In fact, most computers use the same machine instruction to perform either unsigned or signed addition. Thus, we can define two’s-complement addition for word size w, denoted as +t w, on operands x and y such that − 2w−1 ≤ x, y < 2w−1 as x +t w y . = U2Tw(T2Uw(x) +u w T2Uw(y)) (2.13) By Equation 2.5, we can write T2Uw(x) as xw−12w + x, and T2Uw(y) as yw−12w +y. Using the property that +u w is simply addition modulo 2w, along with the prop- erties of modular addition, we then have x +t w y = U2Tw(T2Uw(x) +u w T2Uw(y)) = U2Tw[(xw−12w + x + yw−12w + y) mod 2w] = U2Tw[(x + y) mod 2w] The terms xw−12w and yw−12w drop out since they equal 0 modulo 2w. To better understand this quantity, let us define z as the integer sum z . = x + y, z� as z� . = z mod 2w, and z�� as z�� . = U2Tw(z�). The value z�� is equal to x +t w y. We can divide the analysis into four cases, as illustrated in Figure 2.23: 1. − 2w ≤ z<− 2w−1. Then we will have z� = z + 2w. This gives 0 ≤ z� < − 2w−1 +2w = 2w−1. Examining Equation 2.8, we see that z� is in the range such that z�� = z�. This case is referred to as negative overflow. We have added two negative numbers x and y (that’s the only way we can have z<− 2w−1) and obtained a nonnegative result z�� = x + y + 2w. 消除了 xw-12w 和 yw-12w 这两项,因为它们模 2w 等于 0。 为了更好地理解这个数量,定义 z 为整数和 z =4 x + y,z' 为 z' = 4 z mod 2w,而 z'' 为 z'' =4 U2Tw(z')。 数值 z'' 等于 x + w t y。我们分成 4 种情况分析,如图 2-23 所示。 1) -2w ≤ z < -2w-1。然后,我们会有 z' = z + 2w。 这就得出 0 ≤ z'<-2w-1+2w=2w-1。检查等式(2-8), 我们看到 z' 在满足 z'' = z' 的范围之内。这种情况称 为负溢出(negative overflow)。我们将两个负数 x 和 y 相加(这是我们能得到 z < -2w-1 的唯一方式), 得到一个非负的结果 z'' = x + y + 2w。 2)-2w-1 ≤ z < 0。 那 么, 我 们 又 将 有 z'= z + 2w, 得 到 -2w-1+2w=2w-1 ≤ z' < 2w。 检 查 等 式 (2-8),我们看到 z' 在满足 z'' = -2w 的范围之内, 因此 z'' = z'-2w = z + 2w-2w = z。也就是说,我们的 补码和 z'' 等于整数和 x + y。 3)0 ≤ z < 2w-1。那么,我们将有 z' = z,得到 0 ≤ z' < 2w-1,因此 z'' = z' = z。补码和 z'' 又等于整 数和 x + y。 4)2w-1 ≤ z < 2w。 我 们 又 将 有 z' = z, 得 到 2w-1 ≤ z' < 2w。但是在这个范围内,我们有 z'' = z'-2w,得到 z''= x + y -2w。这种情况称为正溢出(positive overflow)。我们将正数 x 和 y 相加(这 图 2-23  整数和补码加法之间的关系。当 x+y 小于 -2w-1 时,产生负溢出。当它大 于 2w-1+1 时,产生正溢出 =4 正文.indd 57 2010-10-19 14:18:34  58  第一部分 程序结构和执行  是我们能得到 z ≥ 2w-1 的唯一方式),得到一个负数结果 z''= x + y-2w。 通过前面的分析可以证明,范围在 -2w-1 ≤ x , y ≤ 2w-1-1 之内的 x 和 y 实施运算 + w t 时,我 们有下面这样的式子 : 118 Chapter 2 Representing and Manipulating Information Figure 2.23 Relation between integer and two’s-complement addition. When x + y is less than − 2w−1, there is a negative overflow. When it is greater than 2w−1 + 1, there is a positive overflow. +2w –2w 00 +2w�1 +2w�1 –2w�1 –2w�1 Negative overflow Positive overflow Case 4 Case 3 Case 2 Case 1 Normal x �ty x �y 2. − 2w−1 ≤ z<0. Then we will again have z� = z + 2w, giving − 2w−1 + 2w =2w−1 ≤ z� < 2w. Examining Equation 2.8, we see that z� is in such a range that z�� = z� − 2w, and therefore z�� = z� − 2w = z + 2w − 2w = z. That is, our two’s- complement sum z�� equals the integer sum x + y. 3. 0 ≤ z<2w−1. Then we will have z� = z, giving 0 ≤ z� < 2w−1, and hence z�� =z� = z. Again, the two’s-complement sum z�� equals the integer sum x + y. 4. 2w−1 ≤ z<2w. We will again have z� = z, giving 2w−1 ≤ z� < 2w. But in this range we have z�� = z� − 2w, giving z�� = x + y − 2w. This case is referred to as positive overflow. We have added two positive numbers x and y (that’s the only way we can have z ≥ 2w−1) and obtained a negative result z�� = x + y − 2w. By the preceding analysis, we have shown that when operation +t w is applied to values x and y in the range −2w−1 ≤ x, y ≤ 2w−1 − 1, we have x +t w y =    x + y − 2w, 2w−1 ≤ x + y Positive overflow x + y, −2w−1 ≤ x + y<2w−1 Normal x + y + 2w,x+ y<− 2w−1 Negative overflow (2.14) As an illustration, Figure 2.24 shows some examples of 4-bit two’s-complement addition. Each example is labeled by the case to which it corresponds in the derivation of Equation 2.14. Note that 24 = 16, and hence negative overflow yields a result 16 more than the integer sum, and positive overflow yields a result 16 less. We include bit-level representations of the operands and the result. Observe that the result can be obtained by performing binary addition of the operands and truncating the result to four bits. Figure 2.25 illustrates two’s-complement addition for word size w = 4. The operands range between −8 and 7. When x + y<−8, two’s-complement addition has a negative underflow, causing the sum to be incremented by 16. When −8 ≤x + y<8, the addition yields x + y. When x + y ≥ 8, the addition has a negative overflow, causing the sum to be decremented by 16. Each of these three ranges forms a sloping plane in the figure. 正溢出 正常 负溢出 (2-14) 图 2-24 展示了一些 4 位补码加法的示例作为说明。每个示例的情况都被标号为对应于等式 (2-14)的推导过程中的情况。注意 24 = 16,因此负溢出得到的结果比整数和大 16,而正溢出得 到的结果比之小 16。我们包括了运算数和结果的位级表示。可以观察到,能够通过对运算数执 行二进制加法并将结果截断到 4 位,从而得到结果。 x y x + y x+ 4 t y 情况 -8 -5 -13 3 1 [1000] [1011] [10011] [0011] -8 -8 -16 0 1 [1000] [1000] [10000] [0000] -8 5 -3 -3 2 [1000] [0101] [11101] [1101] 2 5 7 7 3 [0010] [0101] [00111] [0111] 5 5 10 -6 4 [0101] [0101] [01010] [1010] 图 2-24  补码加法示例。通过执行运算数的二进制加法并将结果截断到 4 位, 可以获得 4 位补码和的位级表示 图 2-25 阐明了字长 w = 4 的补码加法。运算数的范围为 -8 ~ 7。当 x + y < -8 时,补码加 法就会负溢出,导致和增加了 16。当 -8 ≤ x + y < 8 时,加法就产生 x + y。当 x + y ≥ 8,加法 就会正溢出,使得和减少了 16。这三种情况中的每一种都形成了图中的一个斜面。 等式(2-14) 也 让 我 们 认 出 了 哪 些 情 况 下 会 发 生 溢 出。 当 x 和 y 都 是 负 数, 但 是 x+ w t y ≥ 0 时,我们就会得到负溢出。当 x 和 y 都是正数,但是 x+ w t y < 0 时,我们会得 到正溢出。 练习题 2.29 按照图 2-24 的形式填写下表。分别列出 5 位参数的整数值、整数和的数值、补码和的数 值、补码和的位级表示,以及属于等式(2-14)推导中的哪种情况。 x y x + y x+ 5 t y 情况 [10100] [10001] [11000] [11000] [10111] [01000] [00010] [00101] [01100] [00100] 练习题 2.30 写出一个具有如下原型的函数 : 120 Chapter 2 Representing and Manipulating Information Equation 2.14 also lets us identify the cases where overflow has occurred. When both x and y are negative but x +t w y ≥ 0, we have negative overflow. When both x and y are positive but x +t w y<0, we have positive overflow. Practice Problem 2.29 Fill in the following table in the style of Figure 2.24. Give the integer values of the 5-bit arguments, the values of both their integer and two’s-complement sums, the bit-level representation of the two’s-complement sum, and the case from the derivation of Equation 2.14. xyx+ yx+t 5 y Case [10100] [10001] [11000] [11000] [10111] [01000] [00010] [00101] [01100] [00100] Practice Problem 2.30 Write a function with the following prototype: /* Determine whether arguments can be added without overflow */ int tadd_ok(int x, int y); This function should return 1 if arguments x and y can be added without causing overflow. Practice Problem 2.31 Your coworker gets impatient with your analysis of the overflow conditions for two’s-complement addition and presents you with the following implementation of tadd_ok: /* Determine whether arguments can be added without overflow */ /* WARNING: This code is buggy. */ int tadd_ok(int x, int y) { int sum = x+y; return (sum-x == y) && (sum-y == x); } You look at the code and laugh. Explain why. ≤ ≤ 正文.indd 58 2010-10-19 14:18:35 第 2 章 信息的表示和处理  59  如果参数 x 和 y 相加不会产生溢出,这个函数就返回 1。 练习题 2.31 你的同事对你补码加法溢出条件的分析有些不耐烦了,他给出了一个函数 tadd_ok 的 实现,如下所示 : 120 Chapter 2 Representing and Manipulating Information Equation 2.14 also lets us identify the cases where overflow has occurred. When both x and y are negative but x +t w y ≥ 0, we have negative overflow. When both x and y are positive but x +t w y<0, we have positive overflow. Practice Problem 2.29 Fill in the following table in the style of Figure 2.24. Give the integer values of the 5-bit arguments, the values of both their integer and two’s-complement sums, the bit-level representation of the two’s-complement sum, and the case from the derivation of Equation 2.14. xyx+ yx+t 5 y Case [10100] [10001] [11000] [11000] [10111] [01000] [00010] [00101] [01100] [00100] Practice Problem 2.30 Write a function with the following prototype: /* Determine whether arguments can be added without overflow */ int tadd_ok(int x, int y); This function should return 1 if arguments x and y can be added without causing overflow. Practice Problem 2.31 Your coworker gets impatient with your analysis of the overflow conditions for two’s-complement addition and presents you with the following implementation of tadd_ok: /* Determine whether arguments can be added without overflow */ /* WARNING: This code is buggy. */ int tadd_ok(int x, int y) { int sum = x+y; return (sum-x == y) && (sum-y == x); } You look at the code and laugh. Explain why.你看了代码以后笑了。解释一下为什么。 练习题 2.32 你现在有个任务,编写函数 tsub_ok 的代码,函数的参数是 x 和 y,如果计算 x-y 不 产生溢出,函数就返回 1。假设你写的练习题 2.30 的代码如下所示 : Section 2.3 Integer Arithmetic 121 Practice Problem 2.32 You are assigned the task of writing code for a function tsub_ok, with arguments x and y, that will return 1 if computing x-y does not cause overflow. Having just written the code for Problem 2.30, you write the following: /* Determine whether arguments can be subtracted without overflow */ /* WARNING: This code is buggy. */ int tsub_ok(int x, int y) { return tadd_ok(x, -y); } For what values of x and y will this function give incorrect results? Writing a correct version of this function is left as an exercise (Problem 2.74). 2.3.3 Two’s-Complement Negation We can see that every number x in the range − 2w−1 ≤ x<2w−1 has an additive in- verse under +t w as follows. First, for x �=−2w−1, we can see that its additive inverse is simply −x. That is, we have − 2w−1 < −x<2w−1 and −x +t w x =−x + x = 0. For x =−2w−1 = TMinw, on the other hand, −x = 2w−1 cannot be represented as a w- bit number. We claim that this special value has itself as the additive inverse under +t w. The value of − 2w−1 +t w − 2w−1 is given by the third case of Equation 2.14, since − 2w−1 +−2w−1 =−2w. This gives − 2w−1 +t w − 2w−1 =−2w + 2w = 0. From this analysis, we can define the two’s-complement negation operation -t w for x in the range − 2w−1 ≤ x<2w−1 as -t w x = � − 2w−1,x=−2w−1 −x, x > − 2w−1 (2.15) Practice Problem 2.33 We can represent a bit pattern of length w = 4 with a single hex digit. For a two’s- complement interpretation of these digits, fill in the following table to determine the additive inverses of the digits shown: x -t 4 x Hex Decimal Decimal Hex 0 5 8 D F What do you observe about the bit patterns generated by two’s-complement and unsigned (Problem 2.28) negation? x 和 y 取什么值时,这个函数会产生错误的结果?写一个该函数的正确版本(家庭作业 2.74)。 图 2-25 补码加法(字长为 4 位的情况下,当 x+y<-8 时,产生负溢出 ;x+y ≥8 时,会产生正溢 出) 2.3.3 补码的非 我们可以看到范围在 -2w-1 ≤ x < 2w-1 中的每个数字 x 都有 +w t 下的加法逆元。首先,对于 x ≠ -2w-1,我们可以看到它的加法逆元就是 -x。也就是,我们有 -2w-1 < -x < 2w-1 和 -x+w t x = -x + x = 0。另一方面,对于 x = -2w-1 = TMinw,-x = 2w-1 不能表示为一个 w 位的数。我们声明, 这个特殊值本身就是它在 +w t 下的加法逆元。-2w-1+w t -2w-1 的值由等式(2-14)的第三种情况给 出,因为 -2w-1 + -2w-1 = -2w。最后得出 -2w-1+w t -2w-1 = -2w + 2w= 0。从这个分析中,我们可以 定义对于范围 -2w-1 ≤ x < 2w-1 内的 x,补码的非运算(negation operation)-w t 如下 : Section 2.3 Integer Arithmetic 121 Practice Problem 2.32 You are assigned the task of writing code for a function tsub_ok, with arguments x and y, that will return 1 if computing x-y does not cause overflow. Having just written the code for Problem 2.30, you write the following: /* Determine whether arguments can be subtracted without overflow */ /* WARNING: This code is buggy. */ int tsub_ok(int x, int y) { return tadd_ok(x, -y); } For what values of x and y will this function give incorrect results? Writing a correct version of this function is left as an exercise (Problem 2.74). 2.3.3 Two’s-Complement Negation We can see that every number x in the range − 2w−1 ≤ x<2w−1 has an additive in- verse under +t w as follows. First, for x �=−2w−1, we can see that its additive inverse is simply −x. That is, we have − 2w−1 < −x<2w−1 and −x +t w x =−x + x = 0. For x =−2w−1 = TMinw, on the other hand, −x = 2w−1 cannot be represented as a w- bit number. We claim that this special value has itself as the additive inverse under +t w. The value of − 2w−1 +t w − 2w−1 is given by the third case of Equation 2.14, since − 2w−1 +−2w−1 =−2w. This gives − 2w−1 +t w − 2w−1 =−2w + 2w = 0. From this analysis, we can define the two’s-complement negation operation -t w for x in the range − 2w−1 ≤ x<2w−1 as -t w x = � − 2w−1,x=−2w−1 −x, x > − 2w−1 (2.15) Practice Problem 2.33 We can represent a bit pattern of length w = 4 with a single hex digit. For a two’s- complement interpretation of these digits, fill in the following table to determine the additive inverses of the digits shown: x -t 4 x Hex Decimal Decimal Hex 0 5 8 D F What do you observe about the bit patterns generated by two’s-complement and unsigned (Problem 2.28) negation?               (2-15) 正文.indd 59 2010-10-19 14:18:38  60  第一部分 程序结构和执行  练习题 2.33 我们可以用一个十六进制数字来表示长度 w=4 的位模式。根据这些数字的补码的解释, 填写下表,确定所示数字的加法逆元。 x -4 t x 十六进制 十进制 十进制 十六进制 0 5 8 D F 对于补码和无符号(见练习题 2.28)非(negation)产生的位模式,你观察到什么? 网络旁注 DATA :TNEG :补码非的位级表示 计算一个位级表示的值的补码非有几种聪明的方法。这些技术很有用(例如当你在调试程序 的时候遇到值 0xfffffffa),同时它们也能够让你更了解补码表示的本质。 行位级补码非的第一种方法是对每一位求补,再对结果加 1。在 C 语言中,我们可以确定, 对于任意整数值 x,计算表达式 -x 和~ x + 1 得到的结果完全一样。 下面是一些字长为 4 的示例 : 从前面的例子我们知道 0xf 的补是 0x0,而 0xa 的补是 0x5,因而 0xfffffffa 是 -6 的 补码表示。 计算一个数 x 的补码非的第二种方法是建立在将位向量分为两部分的基础之上的。假设 k 是 最右边的 1 的位置,因而 x 的位级表示形如 [xw-1, xw-2,…, xk+1, 1, 0,…, 0]。(只要 x ≠ 0 就能够找 到这样的 k。)这个值的非写成二进制格式就是 [~xw-1, ~xw-2,…, ~xk+1, 1, 0,…, 0]。也就是,我们对 位位置 k 左边的所有位取反。 我们用一些 4 位数字来说明这个方法,这里用斜体来突出最右边的模式 1,0,…, 0 : 2.3.4 无符号乘法 范围在 0 ≤ x, y ≤ 2w-1 内的整数 x 和 y 可以表示为 w 位的无符号数,但是它们的乘积 x · y 的取值范围为 0 到 (2w-1)2 = 22w-2w+1+1 之间。这可能需要 2w 位来表示。不过,C 语言中的无符 号乘法被定义为产生 w 位的值,就是 2w 位的整数乘积的低 w 位表示的值。根据等式(2-9),这 可以看作等价于计算乘积模 2w。因此,w 位无符号乘法运算 * → w u 的结果为 : x * → w u y = (x · y) mod 2w (2-16) 2.3.5 补码乘法 范围在 -2w-1 ≤ x, y ≤ 2w-1-1 内的整数 x 和 y 可以表示为 w 位的补码数字,但是它们的乘积 x · y 的取值范围在 -2w-1 · (2w-1-1) = -22w-2+2w-1 和 -2w-1 · -2w-1 = -22w-2 之间。要用补码来表示这 正文.indd 60 2010-10-19 14:18:38 第 2 章 信息的表示和处理  61  个乘积,可能需要 2w 位—大多数情况下只需要 2w-1 位,但是特殊情况 22w-2 需要 2w 位(包 括一个符号位 0)。然而,C 语言中的有符号乘法是通过将 2w 位的乘积截断为 w 位的方式实现的。 根据等式(2-10),w 位的补码乘法运算 * → w t 的结果为 : x * → w t y = U2Tw((x · y) mod 2w) (2-17) 我们认为对于无符号和补码乘法来说,乘法运算的位级表示都是一样的。也就是,给定长度 为 w 的位向量 x→ 和 y→,无符号乘积 B2Uw( x →)* → w u B2Uw( y →) 的位级表示与补码乘积 B2Tw( x →)* → w t  B2Tw( y →) 的位级表示是相同的。这表明机器可以用一种乘法指令来进行有符号和无符号整数的乘法。 举例说明,图 2-26 给出了不同 3 位数字的乘法结果。对于每一对位级运算数,我们执行无 符号和补码乘法,得到 6 位的乘积,然后再把这些乘积截断到 3 位。无符号的截断后的乘积总是 等于 x · y mod 8。虽然无符号和补码两种乘法乘积的 6 位表示不同,但是截断后的乘积的位级表 示都相同。 模式 x y x · y 截断的 x · y 无符号 5 [101] 3 [011] 15 [001111] 7 [111] 补码 -3 [101] 3 [011] -9 [110111] -1 [111] 无符号 4 [100] 7 [111] 28 [011100] 4 [100] 补码 -4 [100] -1 [111] 4 [000100] -4 [100] 无符号 3 [011] 3 [011] 9 [001001] 1 [001] 补码 3 [011] 3 [011] 9 [001001] 1 [001] 图 2-26 3 位无符号和补码乘法示例(虽然完整的乘积位级表示相同, 但是截断后的乘积的位级表示都相同)       为了说明(无符号和补码)乘积的低位是相同的,设 x =B2Tw ( x → ) 和 y = B2Tw ( y → ) 是这些位模 式表示的补码值,而 x'=B2Uw ( x → ) 和 y'=B2Uw( y → ) 是这些位模式表示的无符号值。根据等式(2-5), 我们有 x' = x + xw-12w 和 y' = y + yw-12w。计算这些值的乘积模 2w 得到以下结果 : 124 Chapter 2 Representing and Manipulating Information and y� = y + yw−12w. Computing the product of these values modulo 2w gives the following: (x� . y�) mod 2w = [(x + xw−12w) . (y + yw−12w)] mod 2w (2.18) = [x . y + (xw−1y + yw−1x)2w + xw−1yw−122w] mod 2w = (x . y) mod 2w All of the terms with weight 2w drop out due to the modulus operator, and so we have shown that the low-order w bits of x . y and x� . y� are identical. Practice Problem 2.34 Fill in the following table showing the results of multiplying different 3-bit num- bers, in the style of Figure 2.26: Mode x yx. y Truncated x . y Unsigned [100] [101] Two’s comp. [100] [101] Unsigned [010] [111] Two’s comp. [010] [111] Unsigned [110] [110] Two’s comp. [110] [110] We can see that unsigned arithmetic and two’s-complement arithmetic over w-bit numbers are isomorphic—the operations +u w, -u w, and *u w have the exact same effect at the bit level as do +t w, -t w, and *t w. Practice Problem 2.35 You are given the assignment to develop code for a function tmult_ok that will determine whether two arguments can be multiplied without causing overflow. Here is your solution: /* Determine whether arguments can be multiplied without overflow */ int tmult_ok(int x, int y) { int p = x*y; /* Either x is zero, or dividing p by x gives y */ return !x || p/x == y; } You test this code for a number of values of x and y, and it seems to work properly. Your coworker challenges you, saying, “If I can’t use subtraction to test whether addition has overflowed (see Problem 2.31), then how can you use division to test whether multiplication has overflowed?” Devise a mathematical justification of your approach, along the following lines. First, argue that the case x = 0 is handled correctly. Otherwise, consider (2-18) 由于模运算符,所有带有权重 2w 的项都丢掉了,因此我们看到 x · y 和 x' · y' 的低 w 位是相同的。 练习题 2.34 按照图 2-26 的格式填写下表,说明不同的 3 位数字乘法的结果。 模式 x y x · y 截断的 x · y 无符号 [100] [101] 补码 [100] [101] 无符号 [010] [111] 补码 [010] [111] 无符号 [110] [110] 补码 [110] [110] 我们可以看出,w 位数字上的无符号运算和补码运算是同构的—运算 + → w u、- → w u、* → w u 和 + → w t、-w t、*w t 在位 级上有相同的结果。 练习题 2.35 给你一个任务,开发函数 tmult_ok 的代码,该函数会判断两个参数相乘是否会产生溢 出。下面是你的解决方案 : 正文.indd 61 2010-10-19 14:18:39  62  第一部分 程序结构和执行  124 Chapter 2 Representing and Manipulating Information and y� = y + yw−12w. Computing the product of these values modulo 2w gives the following: (x� . y�) mod 2w = [(x + xw−12w) . (y + yw−12w)] mod 2w (2.18) = [x . y + (xw−1y + yw−1x)2w + xw−1yw−122w] mod 2w = (x . y) mod 2w All of the terms with weight 2w drop out due to the modulus operator, and so we have shown that the low-order w bits of x . y and x� . y� are identical. Practice Problem 2.34 Fill in the following table showing the results of multiplying different 3-bit num- bers, in the style of Figure 2.26: Mode x yx. y Truncated x . y Unsigned [100] [101] Two’s comp. [100] [101] Unsigned [010] [111] Two’s comp. [010] [111] Unsigned [110] [110] Two’s comp. [110] [110] We can see that unsigned arithmetic and two’s-complement arithmetic over w-bit numbers are isomorphic—the operations +u w, -u w, and *u w have the exact same effect at the bit level as do +t w, -t w, and *t w. Practice Problem 2.35 You are given the assignment to develop code for a function tmult_ok that will determine whether two arguments can be multiplied without causing overflow. Here is your solution: /* Determine whether arguments can be multiplied without overflow */ int tmult_ok(int x, int y) { int p = x*y; /* Either x is zero, or dividing p by x gives y */ return !x || p/x == y; } You test this code for a number of values of x and y, and it seems to work properly. Your coworker challenges you, saying, “If I can’t use subtraction to test whether addition has overflowed (see Problem 2.31), then how can you use division to test whether multiplication has overflowed?” Devise a mathematical justification of your approach, along the following lines. First, argue that the case x = 0 is handled correctly. Otherwise, consider 你用 x 和 y 的很多值来测试这段代码,似乎都工作正常。你的同事向你挑战,说 :“如果我不能用减 法来检验加法是否溢出(参见练习题 2.31),那么你怎么能用除法来检验乘法是否溢出呢?” 按照下面的思路,用数学推导来证明你的方法是对的。首先,证明 x = 0 的情况是正确的。另外,考虑 w 位数字 x(x ≠ 0)、y、p 和 q,这里 p 是 x 和 y 补码乘法的结果,而 q 是 p 除以 x 的结果。 1. 说明 x 和 y 的整数乘积 x · y,可以写成这样的形式 :x · y = p + t2w,其中 t ≠ 0 当且仅当 p 的计算溢出。 2. 说明 p 可以写成这样的形式 :p = x · q + r,其中 |r| < |x|。 3. 说明 q = y 当且仅当 r = t = 0。 练习题 2.36 对于数据类型 int 为 32 位的情况,设计一个版本的 tmult_ok 函数(见练习题 2.35), 要使用 64 位精度的数据类型 long long,而不使用除法。 XDR 库中的安全漏洞 2002 年,人们发现 Sun Microsystems 公司提供的实现 XDR 库的代码有安全漏洞,XDR 库 是一个广泛使用的程序间共享数据结构的工具,造成这个安全漏洞的原因是程序会在毫无察觉的 情况下产生乘法溢出。 包含安全漏洞的代码与下面所示类似 : 函数 copy_elements 的设计可以将 ele_cnt 个数据结构复制到第 10 行的函数分配的缓冲区 中,每个数据结构包含 ele_size 个字节。需要的字节数是通过计算 ele_cnt*ele_size 得到的。 想象一下,一个怀有恶意的程序员用参数 ele_cnt 等于 1 048 577(220 + 1)、ele_size 等 于 4 096(212)来调用这个函数。然后第 10 行上的乘法会溢出,导致只会分配 4096 个字节,而不 是装下这些数据所需要的 4 294 971 392 个字节。从第 16 行开始的循环会试图复制所有的字节,超 越已分配的缓冲区的界限,因而破坏了其他的数据结构。这会导致程序崩溃或者行为异常。 正文.indd 62 2010-10-19 14:18:39 第 2 章 信息的表示和处理  63  几乎每个操作系统都使用了这段 Sun 的代码,像 Internet Explorer 和 Kerberos 验证系统这样 使用广泛的程序都用到了它。计算机紧急响应组(Computer Emergency Response Team,CERT), 卡内基 - 梅隆软件工程协会(Carnegie Mellon Software Engineering Institute)运行的一个追踪安 全漏洞或失效的组织,发布了建议“CA-2002-25”,于是许多公司急忙对它们的代码打补丁。幸 运的是,还没有由于这个漏洞引起的安全失效的报告。 库函数 calloc 的实现中存在着类似的漏洞。这些已经被修补过了。 练习题 2.37 现在你的任务是修补上述 XDR 代码中的漏洞。你决定将待分配字节数设置为数据类型 long long unsigned,来消除乘法溢出的可能性(至少在 32 位机器上)。你把原来对 malloc 函 数的调用(第 10 行)替换如下 : 126 Chapter 2 Representing and Manipulating Information The function copy_elements is designed to copy ele_cnt data structures, each consisting of ele_ size bytes into a buffer allocated by the function on line 10. The number of bytes required is computed as ele_cnt * ele_size. Imagine, however, that a malicious programmer calls this function with ele_cnt being 1,048,577 (220 + 1) and ele_size being 4,096 (212). Then the multiplication on line 10 will overflow, causing only 4096 bytes to be allocated, rather than the 4,294,971,392 bytes required to hold that much data. The loop starting at line 16 will attempt to copy all of those bytes, overrunning the end of the allocated buffer, and therefore corrupting other data structures. This could cause the program to crash or otherwise misbehave. The Sun code was used by almost every operating system, and in such widely used programs as Internet Explorer and the Kerberos authentication system. The Computer Emergency Response Team (CERT), an organization run by the Carnegie Mellon Software Engineering Institute to track security vulnerabilities and breaches, issued advisory “CA-2002-25,” and many companies rushed to patch their code. Fortunately, there were no reported security breaches caused by this vulnerability. A similar vulnerability existed in many implementations of the library function calloc. These have since been patched. Practice Problem 2.37 You are given the task of patching the vulnerability in the XDR code shown above. You decide to eliminate the possibility of the multiplication overflowing (on a 32- bit machine, at least) by computing the number of bytes to allocate using data type long long unsigned. You replace the original call to malloc (line 10) as follows: long long unsigned asize = ele_cnt * (long long unsigned) ele_size; void *result = malloc(asize); A. Does your code provide any improvement over the original? B. How would you change the code to eliminate the vulnerability, assuming data type size_t is the same as unsigned int, and these are 32 bits long? 2.3.6 Multiplying by Constants On most machines, the integer multiply instruction is fairly slow, requiring 10 or more clock cycles, whereas other integer operations—such as addition, subtrac- tion, bit-level operations, and shifting—require only 1 clock cycle. As a conse- quence, one important optimization used by compilers is to attempt to replace multiplications by constant factors with combinations of shift and addition oper- ations. We will first consider the case of multiplying by a power of 2, and then generalize this to arbitrary constants. Let x be the unsigned integer represented by bit pattern [xw−1,xw−2,...,x0]. Then for any k ≥ 0, we claim the bit-level representation of x2k is given by A. 这段代码在原始代码基础上有了哪些改进? B. 假设数据类型 size_t 和 unsigned int 是一样的,并且都是 32 位长,你该如何修改代码来消除 这个漏洞? 2.3.6 乘以常数 在大多数机器上,整数乘法指令相当慢,需要 10 个或者更多的时钟周期,然而其他整数运 算(例如加法、减法、位级运算和移位)只需要 1 个时钟周期。因此,编译器使用了一项重要的 优化,试着用移位和加法运算的组合来代替乘以常数因子的乘法。首先,我们会考虑乘以 2 的幂 的情况,然后再概括成乘以任意常数。 设 x 为位模式 [xw-1, xw-2,…, x0] 表示的无符号整数。那么,对于任何 k ≥ 0,我们都认为 [xw-1, xw-2,…, x0, 0,…, 0] 给出了 x2k 的位级表示,这里右边增加了 k 个 0。这个属性可以通过等式 (2-1)推导出来 : Section 2.3 Integer Arithmetic 127 [xw−1,xw−2,...,x0, 0,...,0], where k zeros have been added to the right. This property can be derived using Equation 2.1: B2Uw+k([xw−1,xw−2,...,x0, 0,...,0]) = w−1 �i=0 xi2i+k = � w−1 �i=0 xi2i � . 2k = x2k For k 0,结果是 x/y」,这里对于任何实数 a, a」 定 义为唯一的整数 a',使得 a' ≤ a < a'+ 1。例如, 3.14」 =3,*3.14」 = -4,而 3」 = 3。 考虑对一个无符号数执行逻辑右移 k 位的效果。我们认为这和除以 2k 有一样的效果。例如, 图 2-27 给出了在 12 340 的 16 位表示上执行逻辑右移的结果,以及对它执行除以 1、2、16 和 256 的结果。从左端移入的 0 以斜体表示。我们还给出了如果用真正的运算去做除法得到的结 果。这些示例说明,移位总是舍入到零,这一结果与整数除法的规则一样。 为了证明逻辑右移和除以 2 的幂之间的关系,设 x 为位模式 [xw-1, xw-2,…, x0] 表示的无符号 整数,而 k 的取值范围为 0 ≤ k < w。设 x' 为 w-k 位的位表示 [xw-1, xw-2,…, xk] 的无符号数,而 x'' 为 k 位的位表示 [xk-1,…, x0] 的无符号数。我们有 x' = x/2k」。证明如下 : … 正文.indd 64 2010-10-19 14:18:40 第 2 章 信息的表示和处理  65  根据等式(2-1),我们有 Section 2.3 Integer Arithmetic 129 k >> k (Binary) Decimal 12340/2k 0 0011000000110100 12340 12340.0 1 0001100000011010 6170 6170.0 4 0000001100000011 771 771.25 8 0000000000110000 48 48.203125 Figure 2.27 Dividing unsigned numbers by powers of 2. The examples illustrate how performing a logical right shift by k has the same effect as dividing by 2k and then rounding toward zero. 2.3.7 Dividing by Powers of Two Integer division on most machines is even slower than integer multiplication— requiring 30 or more clock cycles. Dividing by a power of 2 can also be performed using shift operations, but we use a right shift rather than a left shift. The two dif- ferent shifts—logical and arithmetic—serve this purpose for unsigned and two’s- complement numbers, respectively. Integer division always rounds toward zero. For x ≥ 0 and y>0, the result should be �x/y�, where for any real number a, �a� is defined to be the unique integer a� such that a� ≤ a>kis equivalent to x / pwr2k, where pwr2k equals 2k. 和 Section 2.3 Integer Arithmetic 129 k >> k (Binary) Decimal 12340/2k 0 0011000000110100 12340 12340.0 1 0001100000011010 6170 6170.0 4 0000001100000011 771 771.25 8 0000000000110000 48 48.203125 Figure 2.27 Dividing unsigned numbers by powers of 2. The examples illustrate how performing a logical right shift by k has the same effect as dividing by 2k and then rounding toward zero. 2.3.7 Dividing by Powers of Two Integer division on most machines is even slower than integer multiplication— requiring 30 or more clock cycles. Dividing by a power of 2 can also be performed using shift operations, but we use a right shift rather than a left shift. The two dif- ferent shifts—logical and arithmetic—serve this purpose for unsigned and two’s- complement numbers, respectively. Integer division always rounds toward zero. For x ≥ 0 and y>0, the result should be �x/y�, where for any real number a, �a� is defined to be the unique integer a� such that a� ≤ a>kis equivalent to x / pwr2k, where pwr2k equals 2k. 。因此,我们可 以把 x 写为 x = 2kx' + x''。可以观察到 Section 2.3 Integer Arithmetic 129 k >> k (Binary) Decimal 12340/2k 0 0011000000110100 12340 12340.0 1 0001100000011010 6170 6170.0 4 0000001100000011 771 771.25 8 0000000000110000 48 48.203125 Figure 2.27 Dividing unsigned numbers by powers of 2. The examples illustrate how performing a logical right shift by k has the same effect as dividing by 2k and then rounding toward zero. 2.3.7 Dividing by Powers of Two Integer division on most machines is even slower than integer multiplication— requiring 30 or more clock cycles. Dividing by a power of 2 can also be performed using shift operations, but we use a right shift rather than a left shift. The two dif- ferent shifts—logical and arithmetic—serve this purpose for unsigned and two’s- complement numbers, respectively. Integer division always rounds toward zero. For x ≥ 0 and y>0, the result should be �x/y�, where for any real number a, �a� is defined to be the unique integer a� such that a� ≤ a>kis equivalent to x / pwr2k, where pwr2k equals 2k. ,因此 0 ≤ x'' < 2k,这意味着 x''/2k」 = 0。 因此, x/2k」 = x'+x''/2k」 = x'+ x''/2k」 = x'。 对位向量 [xw-1, xw-2,…, x0] 逻辑右移 k 位会得到位向量 [0,… , 0, xw-1, xw-2,…, xk] 这个位向量有数值 x'。因此,对于无符号变量 x,C 表达式 x>>k 等价于 x/pwr2k,这里 pwr2k 等价于 2 k。 k >> k(二进制) 十进制 12340/2k 0 0011000000110100 12340 12340.0 1 0001100000011010 6170 6170.0 4 0000001100000011 771 771.25 8 0000000000110000 48 48.203125 图 2-27 无符号数除以 2 的幂 现在考虑对一个补码数进行算术右移的结果。对于一个正整数,最高有效位为 0,所以效果 与逻辑右移是一样的。因此,对于非负数来说,算术右移 k 位与除以 2k 是一样的。作为一个负 数的例子,图 2-28 给出了对 -12 340 的 16 位表示进行算术右移不同位数的结果。正如我们能看 到的,结果与除以 2 的幂几乎完全一样。对于不需要舍入的情况(k = 1),结果是正确的。但是 当需要进行舍入时,移位导致结果向下舍入,而不是像规则需要的那样向零舍入。例如,表达 式 -7/2 应该得到 -3,而不是 -4。 k >> k(二进制) 十进制 -12340/2k 0 1100111111001100 -12340 -12340.0 1 1110011111100110 -6170 -6170.0 4 1111110011111100 -772 -771.25 8 1111111111001111 -49 -48.203125 图 2-28 进行算术右移 让我们更好地理解算术右移的效果,以及如果利用它来执行除以 2 的幂。设 x 为位模式 [xw-1, xw-2,…, x0] 表示的补码整数,而 k 的取值范围为 0 ≤ k < w。设 x' 为 w-k 位 [xw-1,xw-2,…, xk] 表示的补码数,而 x'' 为低 k 位 [xk-1,…, x0] 表示的无符号数。与分析无符号情况类似,我们 有 x = 2kx'+x'',而 0 ≤ x''< 2k,得到 x' = x/2k」。进一步,可以观察到,算术右移位向量 [xw-1, xw-2,…, x0] k 位,得到位向量 [xw-1,…,xw-1,xw-1,xw-2,…,xk] 它刚好就是将 [xw-1,xw-2,…,xk] 从 w-k 位符号扩展到 w 位。因此,这个移位后的位向量就 是 x/2k」 的补码表示。这个分析证实了我们从图 2-28 的示例中发现的结论。 对于 x ≥ 0,或是不需要舍入的时候(x'' = 0),我们的分析表明这个移位的结果就是所期望 的值。不过,对于 x < 0 和 y > 0,整数除法的结果应该是 「x/y ,这里,对于任何实数 a,「a 被定义为使得 a'-1 < a ≤ a' 的唯一整数 a'。也就是说,整数除法应该将为负的结果向上朝零舍 入。因此,当有舍入发生时,将一个负数右移 k 位不等价于把它除以 2k。这个分析也证实了我们 从图 2-28 的示例中发现的结论。 我们可以在移位之前“偏置”(biasing)这个值,通过这种方法修正这种不合适的舍入。 这种技术利用的属性是 :对于整数 x 和任意 y > 0 的 y,有 「x/y = (x + y-1)/ y」。例如, ≤ ≤ 正文.indd 65 2010-10-19 14:18:41  66  第一部分 程序结构和执行  当 x = -30 且 y = 4,我们有 x + y - 1 = -27,而 「-30/4 = -7 = *27/4」。当 x = -32 且 y = 4 时, 我们有 x + y - 1 = -29,而 「-32/4 = -8 = *29/4」 。通用的方式表达这个关系,假设 x = ky + r, 这里 0 ≤ r < y,得到 (x + y - 1)/y = k + ( r + y - 1) / y,因此 (x + y - 1) / y」 = k + ( r + y - 1) /y」。当 r = 0 时,后面一项等于 0,而当 r > 0 时,等于 1。也就是说,通过给 x 增加一个偏量 y - 1,然后再 将除法向下舍入,当 y 整除 x 时,我们得到 k,否则,就得到 k + 1。因此,对于 x < 0,如果在 右移之前,先将 x 加上 2k-1,那么我们就会得到正确舍入的结果了。 这个分析表明对于使用算术右移的补码机器,C 表达式 (x<0 ? (x + (1<> k 等价于 x/pwr2k,这里 pwr2k 等于 2k。 图 2-29 说明在执行算术右移之前加上一个适当的偏量是如何导致结果正确舍入的。在第 3 列, 我们给出了 -12 340 加上偏量值之后的结果,低 k 位(那些会向右移出的位)以斜体表示。我们可 以看到,低 k 位左边的位可能会加 1,也可能不会加 1。对于不需要舍入的情况(k = 1),加上偏量 只影响那些被移掉的位。对于需要舍入的情况,加上偏量导致较高的位加 1,所以结果会向零舍入。 k 偏量 -12 340 + 偏量 >> k(二进制) 十进制 -12340/2k 0 0 1100111111001100 1100111111001100 -12340 -12340.0 1 1 1100111111001101 1110011111100110 -6170 -6170.0 4 15 1100111111011011 1111110011111101 -771 -771.25 8 255 1101000011001011 1111111111010000 -48 -48.203125 图 2-29 补码除以 2 的幂(右移之前加上一个偏量,结果向零舍入) 练习题 2.42 写一个函数 div16,对于整数参数 x 返回 x/16 的值。你的函数不能使用除法、模运 算、乘法、任何条件语句(if 或者 ?:)、任何比较运算符(例如 <、> 或 ==)或任何循环。你可以假 设数据类型 int 是 32 位长,使用补码表示,而右移是算术右移。 现在我们看到,除以 2 的幂可以通过逻辑或者算术右移来实现。这也正是为什么大多数机器 上提供这两种类型的右移。不幸的是,这种方法不能推广到除以任意常数。同乘法不同,我们不 能用除以 2 的幂的除法来表示除以任意常数 K 的除法。 练习题 2.43 在下面的代码中,我们省略了常数 M 和 N 的定义 : 132 Chapter 2 Representing and Manipulating Information Practice Problem 2.43 In the following code, we have omitted the definitions of constants M and N: #define M /* Mystery number 1 */ #define N /* Mystery number 2 */ int arith(int x, int y) { int result = 0; result = x*M + y/N; /* M and N are mystery numbers. */ return result; } We compiled this code for particular values of M and N. The compiler opti- mized the multiplication and division using the methods we have discussed. The following is a translation of the generated machine code back into C: /* Translation of assembly code for arith */ int optarith(int x, int y) { int t = x; x <<= 5; x -= t; if(y<0)y+=7; y >>= 3; /* Arithmetic shift */ return x+y; } What are the values of M and N? 2.3.8 Final Thoughts on Integer Arithmetic As we have seen, the “integer” arithmetic performed by computers is really a form of modular arithmetic. The finite word size used to represent numbers limits the range of possible values, and the resulting operations can overflow. We have also seen that the two’s-complement representation provides a clever way to be able to represent both negative and positive values, while using the same bit-level implementations as are used to perform unsigned arithmetic—operations such as addition, subtraction, multiplication, and even division have either identical or very similar bit-level behaviors whether the operands are in unsigned or two’s- complement form. We have seen that some of the conventions in the C language can yield some surprising results, and these can be sources of bugs that are hard to recognize or understand. We have especially seen that the unsigned data type, while concep- tually straightforward, can lead to behaviors that even experienced programmers do not expect. We have also seen that this data type can arise in unexpected ways, for example, when writing integer constants and when invoking library routines. 我们以某个 M 和 N 的值编译这段代码。编译器用我们讨论过的方法优化乘法和除法。下面是将产生出 的机器代码翻译回 C 语言的结果 : 132 Chapter 2 Representing and Manipulating Information Practice Problem 2.43 In the following code, we have omitted the definitions of constants M and N: #define M /* Mystery number 1 */ #define N /* Mystery number 2 */ int arith(int x, int y) { int result = 0; result = x*M + y/N; /* M and N are mystery numbers. */ return result; } We compiled this code for particular values of M and N. The compiler opti- mized the multiplication and division using the methods we have discussed. The following is a translation of the generated machine code back into C: /* Translation of assembly code for arith */ int optarith(int x, int y) { int t = x; x <<= 5; x -= t; if(y<0)y+=7; y >>= 3; /* Arithmetic shift */ return x+y; } What are the values of M and N? 2.3.8 Final Thoughts on Integer Arithmetic As we have seen, the “integer” arithmetic performed by computers is really a form of modular arithmetic. The finite word size used to represent numbers limits the range of possible values, and the resulting operations can overflow. We have also seen that the two’s-complement representation provides a clever way to be able to represent both negative and positive values, while using the same bit-level implementations as are used to perform unsigned arithmetic—operations such as addition, subtraction, multiplication, and even division have either identical or very similar bit-level behaviors whether the operands are in unsigned or two’s- complement form. We have seen that some of the conventions in the C language can yield some surprising results, and these can be sources of bugs that are hard to recognize or understand. We have especially seen that the unsigned data type, while concep- tually straightforward, can lead to behaviors that even experienced programmers do not expect. We have also seen that this data type can arise in unexpected ways, for example, when writing integer constants and when invoking library routines. M 和 N 的值为多少? 正文.indd 66 2010-10-19 14:18:42 第 2 章 信息的表示和处理  67  2.3.8 关于整数运算的最后思考 正如我们看到的,计算机执行的“整数”运算实际上是一种模运算形式。表示数字的有限字 长限制了可能的值的取值范围,结果运算可能溢出。我们还看到,补码表示提供了一种既能表示 负数也能表示正数的灵活方法,同时使用了与执行无符号算术相同的位级实现,这些运算包括加 法、减法、乘法,甚至除法,无论运算数是以无符号形式还是以补码形式表示的,都有完全一样 或者非常类似的位级行为。 我们看到了 C 语言中的某些规定可能会产生令人意想不到的结果,而这些可能是难以察觉 和理解的缺陷的源头。我们特别看到了 unsigned 数据类型,虽然它概念上很简单,但可能导 致即使是资深程序员都意想不到的行为。我们还看到这种数据类型会以出乎意料的方式出现,比 如,当书写整数常数和当调用库函数时。 练习题 2.44 假设我们在对有符号值使用补码运算的 32 位机器上运行代码。对于有符号值使用的是 算术右移,而对于无符号值使用的是逻辑右移。变量的声明和初始化如下 : Section 2.4 Floating Point 133 Practice Problem 2.44 Assume we are running code on a 32-bit machine using two’s-complement arith- metic for signed values. Right shifts are performed arithmetically for signed values and logically for unsigned values. The variables are declared and initialized as follows: int x = foo(); /* Arbitrary value */ int y = bar(); /* Arbitrary value */ unsigned ux = x; unsigned uy = y; For each of the following C expressions, either (1) argue that it is true (evalu- ates to 1) for all values of x and y, or (2) give values of x and y for which it is false (evaluates to 0): A. (x > 0) || (x-1 < 0) B.(x & 7) != 7 || (x<<29 < 0) C.(x*x)>=0 D. x<0||-x<=0 E. x>0||-x>=0 F. x+y == uy+ux G. x*~y + uy*ux == -x 2.4 Floating Point A floating-point representation encodes rational numbers of the form V = x × 2y. It is useful for performing computations involving very large numbers (|V |�0), numbers very close to 0 (|V |�1), and more generally as an approximation to real arithmetic. Up until the 1980s, every computer manufacturer devised its own conventions for how floating-point numbers were represented and the details of the operations performed on them. In addition, they often did not worry too much about the accuracy of the operations, viewing speed and ease of implementation as being more critical than numerical precision. All of this changed around 1985 with the advent of IEEE Standard 754, a carefully crafted standard for representing floating-point numbers and the oper- ations performed on them. This effort started in 1976 under Intel’s sponsorship with the design of the 8087, a chip that provided floating-point support for the 8086 processor. They hired William Kahan, a professor at the University of California, Berkeley, as a consultant to help design a floating-point standard for its future processors. They allowed Kahan to join forces with a committee generating an industry-wide standard under the auspices of the Institute of Electrical and Elec- tronics Engineers (IEEE). The committee ultimately adopted a standard close to   对于下面每个 C 表达式,1)证明对于所有的 x 和 y 值,它都为真(等于 1);或者 2)给出使得 它为假(等于 0)的 x 和 y 的值 : Section 2.4 Floating Point 133 Practice Problem 2.44 Assume we are running code on a 32-bit machine using two’s-complement arith- metic for signed values. Right shifts are performed arithmetically for signed values and logically for unsigned values. The variables are declared and initialized as follows: int x = foo(); /* Arbitrary value */ int y = bar(); /* Arbitrary value */ unsigned ux = x; unsigned uy = y; For each of the following C expressions, either (1) argue that it is true (evalu- ates to 1) for all values of x and y, or (2) give values of x and y for which it is false (evaluates to 0): A. (x > 0) || (x-1 < 0) B.(x & 7) != 7 || (x<<29 < 0) C.(x*x)>=0 D. x<0||-x<=0 E. x>0||-x>=0 F. x+y == uy+ux G. x*~y + uy*ux == -x 2.4 Floating Point A floating-point representation encodes rational numbers of the form V = x × 2y. It is useful for performing computations involving very large numbers (|V |�0), numbers very close to 0 (|V |�1), and more generally as an approximation to real arithmetic. Up until the 1980s, every computer manufacturer devised its own conventions for how floating-point numbers were represented and the details of the operations performed on them. In addition, they often did not worry too much about the accuracy of the operations, viewing speed and ease of implementation as being more critical than numerical precision. All of this changed around 1985 with the advent of IEEE Standard 754, a carefully crafted standard for representing floating-point numbers and the oper- ations performed on them. This effort started in 1976 under Intel’s sponsorship with the design of the 8087, a chip that provided floating-point support for the 8086 processor. They hired William Kahan, a professor at the University of California, Berkeley, as a consultant to help design a floating-point standard for its future processors. They allowed Kahan to join forces with a committee generating an industry-wide standard under the auspices of the Institute of Electrical and Elec- tronics Engineers (IEEE). The committee ultimately adopted a standard close to 2.4 浮点数 浮点表示对形如 V = x×2y 的有理数进行编码。它对执行涉及非常大的数字(|V |>>0)、非常 接近于 0(|V |<<1)的数字,以及更普遍地作为实数运算的近似值的计算,是很有用的。 直到 20 世纪 80 年代,每个计算机制造商都设计了自己的表示浮点数的规则,以及对浮点数 执行运算的细节。另外,它们常常不会太多地关注运算的精确性,而把实现的速度和简便性看得 比数字精确性更重要。 大约在 1985 年,这些情况随着 IEEE 标准 754 的推出而改变了,这是一个仔细制订的表示 浮点数及其运算的标准。这项工作是从 1976 年开始由 Intel 赞助的,在 8087 设计的同时,8087 是一种为 8086 处理器提供浮点支持的芯片。他们请 William Kahan(加州大学伯克利分校的一位 教授)作为顾问,帮助设计未来处理器浮点标准。他们支持 Kahan 加入一个 IEEE 资助的制订工 业标准的委员会。这个委员会最终采纳的标准非常接近于 Kahan 为 Intel 设计的标准。目前,实 际上所有的计算机都支持这个后来被称为 IEEE 浮点的标准。这大大提高了科学应用程序在不同 机器上的可移植性。 正文.indd 67 2010-10-19 14:18:42  68  第一部分 程序结构和执行  IEEE 电气和电子工程师协会(IEEE—读做“Eye-Triple-Eee”)是一个包括所有电子和计算机技 术的专业团体。它出版刊物,举办会议,并且建立委员会来定义标准,内容涉及范围从电力传输 到软件工程。 在本节,我们将看到在 IEEE 浮点格式中是如何表示数字的。我们还将探讨舍入(rounding) 的问题,当一个数字不能被准确地表示为这种格式时,就必须向上调整或者向下调整,此时就会 出现舍入。然后,我们将探讨加法、乘法和关系运算符的数学属性。许多程序员认为浮点数没意 思,往坏了说,深奥难懂。我们将看到,因为 IEEE 格式是定义在一组小而一致的原则上的,所 以它实际上是相当优雅和容易理解的。 2.4.1 二进制小数 理解浮点数的第一步是考虑含有小数值的二进制数字。首先,让我们来看看更熟悉的十进制 表示法。十进制表示法使用的表示形式为 :dmdm-1…d1d0.d-1d-2…d-n,其中每个十进制数 di 的取 值范围是 0 ~ 9。这个表示方法描述的数值 d 定义如下 : 134 Chapter 2 Representing and Manipulating Information the one Kahan had devised for Intel. Nowadays, virtually all computers support what has become known as IEEE floating point. This has greatly improved the portability of scientific application programs across different machines. Aside The IEEE The Institute of Electrical and Electronic Engineers (IEEE—pronounced “Eye-Triple-Eee”) is a pro- fessional society that encompasses all of electronic and computer technology. It publishes journals, sponsors conferences, and sets up committees to define standards on topics ranging from power trans- mission to software engineering. In this section, we will see how numbers are represented in the IEEE floating- point format. We will also explore issues of rounding, when a number cannot be represented exactly in the format and hence must be adjusted upward or down- ward. We will then explore the mathematical properties of addition, multiplica- tion, and relational operators. Many programmers consider floating point to be at best uninteresting and at worst arcane and incomprehensible. We will see that since the IEEE format is based on a small and consistent set of principles, it is really quite elegant and understandable. 2.4.1 Fractional Binary Numbers A first step in understanding floating-point numbers is to consider binary numbers having fractional values. Let us first examine the more familiar decimal notation. Decimal notation uses a representation of the formdmdm−1 ...d1d0.d −1d −2 ...d −n, where each decimal digit di ranges between 0 and 9. This notation represents a value d defined as d = m �i=−n 10i × di The weighting of the digits is defined relative to the decimal point symbol (‘.’), meaning that digits to the left are weighted by positive powers of 10, giving integral values, while digits to the right are weighted by negative powers of 10, giving fractional values. For example, 12.3410 represents the number 1 × 101 + 2 × 100 +3 × 10−1 + 4 × 10−2 = 12 34 100 . By analogy, consider a notation of the form bmbm−1 ...b1b0.b −1b −2 ... b −n−1b −n, where each binary digit, or bit, bi ranges between 0 and 1, as is illustrated in Figure 2.30. This notation represents a number b defined as b = m �i=−n 2i × bi (2.19) The symbol ‘.’ now becomes a binary point, with bits on the left being weighted by positive powers of 2, and those on the right being weighted by negative powers of 2. For example, 101.112 represents the number 1 × 22 + 0 × 21 + 1 × 20 + 1 ×2−1 + 1 × 2−2 = 4 + 0 + 1 + 1 2 + 1 4 = 53 4 . 数字权的定义与十进制小数点符号(‘.’)相关,这意味着小数点左边的数字的权是 10 的正 幂,得到整数值,而小数点右边的数字的权是 10 的负幂,得到小数值。例如,12.3410 表示数字 134 Chapter 2 Representing and Manipulating Information the one Kahan had devised for Intel. Nowadays, virtually all computers support what has become known as IEEE floating point. This has greatly improved the portability of scientific application programs across different machines. Aside The IEEE The Institute of Electrical and Electronic Engineers (IEEE—pronounced “Eye-Triple-Eee”) is a pro- fessional society that encompasses all of electronic and computer technology. It publishes journals, sponsors conferences, and sets up committees to define standards on topics ranging from power trans- mission to software engineering. In this section, we will see how numbers are represented in the IEEE floating- point format. We will also explore issues of rounding, when a number cannot be represented exactly in the format and hence must be adjusted upward or down- ward. We will then explore the mathematical properties of addition, multiplica- tion, and relational operators. Many programmers consider floating point to be at best uninteresting and at worst arcane and incomprehensible. We will see that since the IEEE format is based on a small and consistent set of principles, it is really quite elegant and understandable. 2.4.1 Fractional Binary Numbers A first step in understanding floating-point numbers is to consider binary numbers having fractional values. Let us first examine the more familiar decimal notation. Decimal notation uses a representation of the formdmdm−1 ...d1d0.d −1d −2 ...d −n, where each decimal digit di ranges between 0 and 9. This notation represents a value d defined as d = m �i=−n 10i × di The weighting of the digits is defined relative to the decimal point symbol (‘.’), meaning that digits to the left are weighted by positive powers of 10, giving integral values, while digits to the right are weighted by negative powers of 10, giving fractional values. For example, 12.3410 represents the number 1 × 101 + 2 × 100 +3 × 10−1 + 4 × 10−2 = 12 34 100 . By analogy, consider a notation of the form bmbm−1 ...b1b0.b −1b −2 ... b −n−1b −n, where each binary digit, or bit, bi ranges between 0 and 1, as is illustrated in Figure 2.30. This notation represents a number b defined as b = m �i=−n 2i × bi (2.19) The symbol ‘.’ now becomes a binary point, with bits on the left being weighted by positive powers of 2, and those on the right being weighted by negative powers of 2. For example, 101.112 represents the number 1 × 22 + 0 × 21 + 1 × 20 + 1 ×2−1 + 1 × 2−2 = 4 + 0 + 1 + 1 2 + 1 4 = 53 4 . 134 Chapter 2 Representing and Manipulating Information the one Kahan had devised for Intel. Nowadays, virtually all computers support what has become known as IEEE floating point. This has greatly improved the portability of scientific application programs across different machines. Aside The IEEE The Institute of Electrical and Electronic Engineers (IEEE—pronounced “Eye-Triple-Eee”) is a pro- fessional society that encompasses all of electronic and computer technology. It publishes journals, sponsors conferences, and sets up committees to define standards on topics ranging from power trans- mission to software engineering. In this section, we will see how numbers are represented in the IEEE floating- point format. We will also explore issues of rounding, when a number cannot be represented exactly in the format and hence must be adjusted upward or down- ward. We will then explore the mathematical properties of addition, multiplica- tion, and relational operators. Many programmers consider floating point to be at best uninteresting and at worst arcane and incomprehensible. We will see that since the IEEE format is based on a small and consistent set of principles, it is really quite elegant and understandable. 2.4.1 Fractional Binary Numbers A first step in understanding floating-point numbers is to consider binary numbers having fractional values. Let us first examine the more familiar decimal notation. Decimal notation uses a representation of the formdmdm−1 ...d1d0.d −1d −2 ...d −n, where each decimal digit di ranges between 0 and 9. This notation represents a value d defined as d = m �i=−n 10i × di The weighting of the digits is defined relative to the decimal point symbol (‘.’), meaning that digits to the left are weighted by positive powers of 10, giving integral values, while digits to the right are weighted by negative powers of 10, giving fractional values. For example, 12.3410 represents the number 1 × 101 + 2 × 100 +3 × 10−1 + 4 × 10−2 = 12 34 100 . By analogy, consider a notation of the form bmbm−1 ...b1b0.b −1b −2 ... b −n−1b −n, where each binary digit, or bit, bi ranges between 0 and 1, as is illustrated in Figure 2.30. This notation represents a number b defined as b = m �i=−n 2i × bi (2.19) The symbol ‘.’ now becomes a binary point, with bits on the left being weighted by positive powers of 2, and those on the right being weighted by negative powers of 2. For example, 101.112 represents the number 1 × 22 + 0 × 21 + 1 × 20 + 1 ×2−1 + 1 × 2−2 = 4 + 0 + 1 + 1 2 + 1 4 = 53 4 . 。 类似地,考虑一个形如 bmbm-1…b1b0.b-1b-2…b-n-1b-n 的表示法,其中每个二进制数字,或者 称为位,bi 的取值范围是 0 和 1,如图 2-30 所示。这种表示方法表示的数 b 定义如下 : 134 Chapter 2 Representing and Manipulating Information the one Kahan had devised for Intel. Nowadays, virtually all computers support what has become known as IEEE floating point. This has greatly improved the portability of scientific application programs across different machines. Aside The IEEE The Institute of Electrical and Electronic Engineers (IEEE—pronounced “Eye-Triple-Eee”) is a pro- fessional society that encompasses all of electronic and computer technology. It publishes journals, sponsors conferences, and sets up committees to define standards on topics ranging from power trans- mission to software engineering. In this section, we will see how numbers are represented in the IEEE floating- point format. We will also explore issues of rounding, when a number cannot be represented exactly in the format and hence must be adjusted upward or down- ward. We will then explore the mathematical properties of addition, multiplica- tion, and relational operators. Many programmers consider floating point to be at best uninteresting and at worst arcane and incomprehensible. We will see that since the IEEE format is based on a small and consistent set of principles, it is really quite elegant and understandable. 2.4.1 Fractional Binary Numbers A first step in understanding floating-point numbers is to consider binary numbers having fractional values. Let us first examine the more familiar decimal notation. Decimal notation uses a representation of the formdmdm−1 ...d1d0.d −1d −2 ...d −n, where each decimal digit di ranges between 0 and 9. This notation represents a value d defined as d = m �i=−n 10i × di The weighting of the digits is defined relative to the decimal point symbol (‘.’), meaning that digits to the left are weighted by positive powers of 10, giving integral values, while digits to the right are weighted by negative powers of 10, giving fractional values. For example, 12.3410 represents the number 1 × 101 + 2 × 100 +3 × 10−1 + 4 × 10−2 = 12 34 100 . By analogy, consider a notation of the form bmbm−1 ...b1b0.b −1b −2 ... b −n−1b −n, where each binary digit, or bit, bi ranges between 0 and 1, as is illustrated in Figure 2.30. This notation represents a number b defined as b = m �i=−n 2i × bi (2.19) The symbol ‘.’ now becomes a binary point, with bits on the left being weighted by positive powers of 2, and those on the right being weighted by negative powers of 2. For example, 101.112 represents the number 1 × 22 + 0 × 21 + 1 × 20 + 1 ×2−1 + 1 × 2−2 = 4 + 0 + 1 + 1 2 + 1 4 = 53 4 .             (2-19) 符号‘.’现在变为了二进制的点,点左边的位的权是 2 的正幂,点右边的位的权是 2 的负幂。 例如,101.112 表示数字 134 Chapter 2 Representing and Manipulating Information the one Kahan had devised for Intel. Nowadays, virtually all computers support what has become known as IEEE floating point. This has greatly improved the portability of scientific application programs across different machines. Aside The IEEE The Institute of Electrical and Electronic Engineers (IEEE—pronounced “Eye-Triple-Eee”) is a pro- fessional society that encompasses all of electronic and computer technology. It publishes journals, sponsors conferences, and sets up committees to define standards on topics ranging from power trans- mission to software engineering. In this section, we will see how numbers are represented in the IEEE floating- point format. We will also explore issues of rounding, when a number cannot be represented exactly in the format and hence must be adjusted upward or down- ward. We will then explore the mathematical properties of addition, multiplica- tion, and relational operators. Many programmers consider floating point to be at best uninteresting and at worst arcane and incomprehensible. We will see that since the IEEE format is based on a small and consistent set of principles, it is really quite elegant and understandable. 2.4.1 Fractional Binary Numbers A first step in understanding floating-point numbers is to consider binary numbers having fractional values. Let us first examine the more familiar decimal notation. Decimal notation uses a representation of the formdmdm−1 ...d1d0.d −1d −2 ...d −n, where each decimal digit di ranges between 0 and 9. This notation represents a value d defined as d = m �i=−n 10i × di The weighting of the digits is defined relative to the decimal point symbol (‘.’), meaning that digits to the left are weighted by positive powers of 10, giving integral values, while digits to the right are weighted by negative powers of 10, giving fractional values. For example, 12.3410 represents the number 1 × 101 + 2 × 100 +3 × 10−1 + 4 × 10−2 = 12 34 100 . By analogy, consider a notation of the form bmbm−1 ...b1b0.b −1b −2 ... b −n−1b −n, where each binary digit, or bit, bi ranges between 0 and 1, as is illustrated in Figure 2.30. This notation represents a number b defined as b = m �i=−n 2i × bi (2.19) The symbol ‘.’ now becomes a binary point, with bits on the left being weighted by positive powers of 2, and those on the right being weighted by negative powers of 2. For example, 101.112 represents the number 1 × 22 + 0 × 21 + 1 × 20 + 1 ×2−1 + 1 × 2−2 = 4 + 0 + 1 + 1 2 + 1 4 = 53 4 . 134 Chapter 2 Representing and Manipulating Information the one Kahan had devised for Intel. Nowadays, virtually all computers support what has become known as IEEE floating point. This has greatly improved the portability of scientific application programs across different machines. Aside The IEEE The Institute of Electrical and Electronic Engineers (IEEE—pronounced “Eye-Triple-Eee”) is a pro- fessional society that encompasses all of electronic and computer technology. It publishes journals, sponsors conferences, and sets up committees to define standards on topics ranging from power trans- mission to software engineering. In this section, we will see how numbers are represented in the IEEE floating- point format. We will also explore issues of rounding, when a number cannot be represented exactly in the format and hence must be adjusted upward or down- ward. We will then explore the mathematical properties of addition, multiplica- tion, and relational operators. Many programmers consider floating point to be at best uninteresting and at worst arcane and incomprehensible. We will see that since the IEEE format is based on a small and consistent set of principles, it is really quite elegant and understandable. 2.4.1 Fractional Binary Numbers A first step in understanding floating-point numbers is to consider binary numbers having fractional values. Let us first examine the more familiar decimal notation. Decimal notation uses a representation of the formdmdm−1 ...d1d0.d −1d −2 ...d −n, where each decimal digit di ranges between 0 and 9. This notation represents a value d defined as d = m �i=−n 10i × di The weighting of the digits is defined relative to the decimal point symbol (‘.’), meaning that digits to the left are weighted by positive powers of 10, giving integral values, while digits to the right are weighted by negative powers of 10, giving fractional values. For example, 12.3410 represents the number 1 × 101 + 2 × 100 +3 × 10−1 + 4 × 10−2 = 12 34 100 . By analogy, consider a notation of the form bmbm−1 ...b1b0.b −1b −2 ... b −n−1b −n, where each binary digit, or bit, bi ranges between 0 and 1, as is illustrated in Figure 2.30. This notation represents a number b defined as b = m �i=−n 2i × bi (2.19) The symbol ‘.’ now becomes a binary point, with bits on the left being weighted by positive powers of 2, and those on the right being weighted by negative powers of 2. For example, 101.112 represents the number 1 × 22 + 0 × 21 + 1 × 20 + 1 ×2−1 + 1 × 2−2 = 4 + 0 + 1 + 1 2 + 1 4 = 53 4 .。 图 2-30 小数的二进制表示。二进制点左边的数字的权形如 2i,而右边的数字的权形如 1/2i 从等式(2-19)中可以很容易地看出,二进制小数点向左移动一位相当于这个数被 2 除。例 如,101.112 表示数 Section 2.4 Floating Point 135 Figure 2.30 Fractional binary repre- sentation. Digits to the left of the binary point have weights of the form 2i, while those to the right have weights of the form 1/2i. bm bm–1 · · · · · · b2 b1 b0 b–1 1 1/2 1/4 1/8 1/2n–1 1/2n 2 4 2m–1 2m b–2 b–3 · · ·· · · · b–n–1 b–n One can readily see from Equation 2.19 that shifting the binary point one position to the left has the effect of dividing the number by 2. For example, while 101.112 represents the number 53 4 , 10.1112 represents the number 2 + 0 + 1 2 +1 4 + 1 8 = 2 7 8 . Similarly, shifting the binary point one position to the right has the effect of multiplying the number by 2. For example, 1011.12 represents the number 8 + 0 + 2 + 1 + 1 2 = 111 2 . Note that numbers of the form 0.11 ...12 represent numbers just below 1. For example, 0.1111112 represents 63 64 . We will use the shorthand notation 1.0 − � to represent such values. Assuming we consider only finite-length encodings, decimal notation cannot represent numbers such as 1 3 and 5 7 exactly. Similarly, fractional binary notation can only represent numbers that can be written x × 2y. Other values can only be approximated. For example, the number 1 5 can be represented exactly as the frac- tional decimal number 0.20. As a fractional binary number, however, we cannot represent it exactly and instead must approximate it with increasing accuracy by lengthening the binary representation: Representation Value Decimal 0.02 0 2 0.010 0.012 1 4 0.2510 0.0102 2 8 0.2510 0.00112 3 16 0.187510 0.001102 6 32 0.187510 0.0011012 13 64 0.20312510 0.00110102 26 128 0.20312510 0.001100112 51 256 0.1992187510 ,而 10.1112 表示数 Section 2.4 Floating Point 135 Figure 2.30 Fractional binary repre- sentation. Digits to the left of the binary point have weights of the form 2i, while those to the right have weights of the form 1/2i. bm bm–1 · · · · · · b2 b1 b0 b–1 1 1/2 1/4 1/8 1/2n–1 1/2n 2 4 2m–1 2m b–2 b–3 · · ·· · · · b–n–1 b–n One can readily see from Equation 2.19 that shifting the binary point one position to the left has the effect of dividing the number by 2. For example, while 101.112 represents the number 53 4 , 10.1112 represents the number 2 + 0 + 1 2 +1 4 + 1 8 = 2 7 8 . Similarly, shifting the binary point one position to the right has the effect of multiplying the number by 2. For example, 1011.12 represents the number 8 + 0 + 2 + 1 + 1 2 = 111 2 . Note that numbers of the form 0.11 ...12 represent numbers just below 1. For example, 0.1111112 represents 63 64 . We will use the shorthand notation 1.0 − � to represent such values. Assuming we consider only finite-length encodings, decimal notation cannot represent numbers such as 1 3 and 5 7 exactly. Similarly, fractional binary notation can only represent numbers that can be written x × 2y. Other values can only be approximated. For example, the number 1 5 can be represented exactly as the frac- tional decimal number 0.20. As a fractional binary number, however, we cannot represent it exactly and instead must approximate it with increasing accuracy by lengthening the binary representation: Representation Value Decimal 0.02 0 2 0.010 0.012 1 4 0.2510 0.0102 2 8 0.2510 0.00112 3 16 0.187510 0.001102 6 32 0.187510 0.0011012 13 64 0.20312510 0.00110102 26 128 0.20312510 0.001100112 51 256 0.1992187510 Section 2.4 Floating Point 135 Figure 2.30 Fractional binary repre- sentation. Digits to the left of the binary point have weights of the form 2i, while those to the right have weights of the form 1/2i. bm bm–1 · · · · · · b2 b1 b0 b–1 1 1/2 1/4 1/8 1/2n–1 1/2n 2 4 2m–1 2m b–2 b–3 · · ·· · · · b–n–1 b–n One can readily see from Equation 2.19 that shifting the binary point one position to the left has the effect of dividing the number by 2. For example, while 101.112 represents the number 53 4 , 10.1112 represents the number 2 + 0 + 1 2 +1 4 + 1 8 = 2 7 8 . Similarly, shifting the binary point one position to the right has the effect of multiplying the number by 2. For example, 1011.12 represents the number 8 + 0 + 2 + 1 + 1 2 = 111 2 . Note that numbers of the form 0.11 ...12 represent numbers just below 1. For example, 0.1111112 represents 63 64 . We will use the shorthand notation 1.0 − � to represent such values. Assuming we consider only finite-length encodings, decimal notation cannot represent numbers such as 1 3 and 5 7 exactly. Similarly, fractional binary notation can only represent numbers that can be written x × 2y. Other values can only be approximated. For example, the number 1 5 can be represented exactly as the frac- tional decimal number 0.20. As a fractional binary number, however, we cannot represent it exactly and instead must approximate it with increasing accuracy by lengthening the binary representation: Representation Value Decimal 0.02 0 2 0.010 0.012 1 4 0.2510 0.0102 2 8 0.2510 0.00112 3 16 0.187510 0.001102 6 32 0.187510 0.0011012 13 64 0.20312510 0.00110102 26 128 0.20312510 0.001100112 51 256 0.1992187510 。类似地,二进制小数点向右 移动一位相当于将该数乘 2。例如 1011.12 表示数 Section 2.4 Floating Point 135 Figure 2.30 Fractional binary repre- sentation. Digits to the left of the binary point have weights of the form 2i, while those to the right have weights of the form 1/2i. bm bm–1 · · · · · · b2 b1 b0 b–1 1 1/2 1/4 1/8 1/2n–1 1/2n 2 4 2m–1 2m b–2 b–3 · · ·· · · · b–n–1 b–n One can readily see from Equation 2.19 that shifting the binary point one position to the left has the effect of dividing the number by 2. For example, while 101.112 represents the number 53 4 , 10.1112 represents the number 2 + 0 + 1 2 +1 4 + 1 8 = 2 7 8 . Similarly, shifting the binary point one position to the right has the effect of multiplying the number by 2. For example, 1011.12 represents the number 8 + 0 + 2 + 1 + 1 2 = 111 2 . Note that numbers of the form 0.11 ...12 represent numbers just below 1. For example, 0.1111112 represents 63 64 . We will use the shorthand notation 1.0 − � to represent such values. Assuming we consider only finite-length encodings, decimal notation cannot represent numbers such as 1 3 and 5 7 exactly. Similarly, fractional binary notation can only represent numbers that can be written x × 2y. Other values can only be approximated. For example, the number 1 5 can be represented exactly as the frac- tional decimal number 0.20. As a fractional binary number, however, we cannot represent it exactly and instead must approximate it with increasing accuracy by lengthening the binary representation: Representation Value Decimal 0.02 0 2 0.010 0.012 1 4 0.2510 0.0102 2 8 0.2510 0.00112 3 16 0.187510 0.001102 6 32 0.187510 0.0011012 13 64 0.20312510 0.00110102 26 128 0.20312510 0.001100112 51 256 0.1992187510 。 正文.indd 68 2010-10-19 14:18:44 第 2 章 信息的表示和处理  69  注意,形如 0.11…12 的数表示刚好小于 1 的数。例如,0.1111112 表示 Section 2.4 Floating Point 135 Figure 2.30 Fractional binary repre- sentation. Digits to the left of the binary point have weights of the form 2i, while those to the right have weights of the form 1/2i. bm bm–1 · · · · · · b2 b1 b0 b–1 1 1/2 1/4 1/8 1/2n–1 1/2n 2 4 2m–1 2m b–2 b–3 · · ·· · · · b–n–1 b–n One can readily see from Equation 2.19 that shifting the binary point one position to the left has the effect of dividing the number by 2. For example, while 101.112 represents the number 53 4 , 10.1112 represents the number 2 + 0 + 1 2 +1 4 + 1 8 = 2 7 8 . Similarly, shifting the binary point one position to the right has the effect of multiplying the number by 2. For example, 1011.12 represents the number 8 + 0 + 2 + 1 + 1 2 = 111 2 . Note that numbers of the form 0.11 ...12 represent numbers just below 1. For example, 0.1111112 represents 63 64 . We will use the shorthand notation 1.0 − � to represent such values. Assuming we consider only finite-length encodings, decimal notation cannot represent numbers such as 1 3 and 5 7 exactly. Similarly, fractional binary notation can only represent numbers that can be written x × 2y. Other values can only be approximated. For example, the number 1 5 can be represented exactly as the frac- tional decimal number 0.20. As a fractional binary number, however, we cannot represent it exactly and instead must approximate it with increasing accuracy by lengthening the binary representation: Representation Value Decimal 0.02 0 2 0.010 0.012 1 4 0.2510 0.0102 2 8 0.2510 0.00112 3 16 0.187510 0.001102 6 32 0.187510 0.0011012 13 64 0.20312510 0.00110102 26 128 0.20312510 0.001100112 51 256 0.1992187510 ,我们将用简单的 表达法 1.0-ε 来表示这样的数值。 假定我们仅考虑有限长度的编码,那么十进制表示法不能准确地表达像 Section 2.4 Floating Point 135 Figure 2.30 Fractional binary repre- sentation. Digits to the left of the binary point have weights of the form 2i, while those to the right have weights of the form 1/2i. bm bm–1 · · · · · · b2 b1 b0 b–1 1 1/2 1/4 1/8 1/2n–1 1/2n 2 4 2m–1 2m b–2 b–3 · · ·· · · · b–n–1 b–n One can readily see from Equation 2.19 that shifting the binary point one position to the left has the effect of dividing the number by 2. For example, while 101.112 represents the number 53 4 , 10.1112 represents the number 2 + 0 + 1 2 +1 4 + 1 8 = 2 7 8 . Similarly, shifting the binary point one position to the right has the effect of multiplying the number by 2. For example, 1011.12 represents the number 8 + 0 + 2 + 1 + 1 2 = 111 2 . Note that numbers of the form 0.11 ...12 represent numbers just below 1. For example, 0.1111112 represents 63 64 . We will use the shorthand notation 1.0 − � to represent such values. Assuming we consider only finite-length encodings, decimal notation cannot represent numbers such as 1 3 and 5 7 exactly. Similarly, fractional binary notation can only represent numbers that can be written x × 2y. Other values can only be approximated. For example, the number 1 5 can be represented exactly as the frac- tional decimal number 0.20. As a fractional binary number, however, we cannot represent it exactly and instead must approximate it with increasing accuracy by lengthening the binary representation: Representation Value Decimal 0.02 0 2 0.010 0.012 1 4 0.2510 0.0102 2 8 0.2510 0.00112 3 16 0.187510 0.001102 6 32 0.187510 0.0011012 13 64 0.20312510 0.00110102 26 128 0.20312510 0.001100112 51 256 0.1992187510 和 Section 2.4 Floating Point 135 Figure 2.30 Fractional binary repre- sentation. Digits to the left of the binary point have weights of the form 2i, while those to the right have weights of the form 1/2i. bm bm–1 · · · · · · b2 b1 b0 b–1 1 1/2 1/4 1/8 1/2n–1 1/2n 2 4 2m–1 2m b–2 b–3 · · ·· · · · b–n–1 b–n One can readily see from Equation 2.19 that shifting the binary point one position to the left has the effect of dividing the number by 2. For example, while 101.112 represents the number 53 4 , 10.1112 represents the number 2 + 0 + 1 2 +1 4 + 1 8 = 2 7 8 . Similarly, shifting the binary point one position to the right has the effect of multiplying the number by 2. For example, 1011.12 represents the number 8 + 0 + 2 + 1 + 1 2 = 111 2 . Note that numbers of the form 0.11 ...12 represent numbers just below 1. For example, 0.1111112 represents 63 64 . We will use the shorthand notation 1.0 − � to represent such values. Assuming we consider only finite-length encodings, decimal notation cannot represent numbers such as 1 3 and 5 7 exactly. Similarly, fractional binary notation can only represent numbers that can be written x × 2y. Other values can only be approximated. For example, the number 1 5 can be represented exactly as the frac- tional decimal number 0.20. As a fractional binary number, however, we cannot represent it exactly and instead must approximate it with increasing accuracy by lengthening the binary representation: Representation Value Decimal 0.02 0 2 0.010 0.012 1 4 0.2510 0.0102 2 8 0.2510 0.00112 3 16 0.187510 0.001102 6 32 0.187510 0.0011012 13 64 0.20312510 0.00110102 26 128 0.20312510 0.001100112 51 256 0.1992187510 这样的数。类似 地,小数的二进制表示法只能表示那些能够被写成 x×2y 的数。其他的值只能够被近似地表示。 例如,数字 Section 2.4 Floating Point 135 Figure 2.30 Fractional binary repre- sentation. Digits to the left of the binary point have weights of the form 2i, while those to the right have weights of the form 1/2i. bm bm–1 · · · · · · b2 b1 b0 b–1 1 1/2 1/4 1/8 1/2n–1 1/2n 2 4 2m–1 2m b–2 b–3 · · ·· · · · b–n–1 b–n One can readily see from Equation 2.19 that shifting the binary point one position to the left has the effect of dividing the number by 2. For example, while 101.112 represents the number 53 4 , 10.1112 represents the number 2 + 0 + 1 2 +1 4 + 1 8 = 2 7 8 . Similarly, shifting the binary point one position to the right has the effect of multiplying the number by 2. For example, 1011.12 represents the number 8 + 0 + 2 + 1 + 1 2 = 111 2 . Note that numbers of the form 0.11 ...12 represent numbers just below 1. For example, 0.1111112 represents 63 64 . We will use the shorthand notation 1.0 − � to represent such values. Assuming we consider only finite-length encodings, decimal notation cannot represent numbers such as 1 3 and 5 7 exactly. Similarly, fractional binary notation can only represent numbers that can be written x × 2y. Other values can only be approximated. For example, the number 1 5 can be represented exactly as the frac- tional decimal number 0.20. As a fractional binary number, however, we cannot represent it exactly and instead must approximate it with increasing accuracy by lengthening the binary representation: Representation Value Decimal 0.02 0 2 0.010 0.012 1 4 0.2510 0.0102 2 8 0.2510 0.00112 3 16 0.187510 0.001102 6 32 0.187510 0.0011012 13 64 0.20312510 0.00110102 26 128 0.20312510 0.001100112 51 256 0.1992187510 可以用十进制小数 0.20 精确表示。不过,我们并不能把它准确地表示为一个二进制 小数,我们只能近似地表示它,增加二进制表示的长度可以提高表示的精度 : 表示 值 十进制 Section 2.4 Floating Point 135 Figure 2.30 Fractional binary repre- sentation. Digits to the left of the binary point have weights of the form 2i, while those to the right have weights of the form 1/2i. bm bm–1 · · · · · · b2 b1 b0 b–1 1 1/2 1/4 1/8 1/2n–1 1/2n 2 4 2m–1 2m b–2 b–3 · · ·· · · · b–n–1 b–n One can readily see from Equation 2.19 that shifting the binary point one position to the left has the effect of dividing the number by 2. For example, while 101.112 represents the number 53 4 , 10.1112 represents the number 2 + 0 + 1 2 +1 4 + 1 8 = 2 7 8 . Similarly, shifting the binary point one position to the right has the effect of multiplying the number by 2. For example, 1011.12 represents the number 8 + 0 + 2 + 1 + 1 2 = 111 2 . Note that numbers of the form 0.11 ...12 represent numbers just below 1. For example, 0.1111112 represents 63 64 . We will use the shorthand notation 1.0 − � to represent such values. Assuming we consider only finite-length encodings, decimal notation cannot represent numbers such as 1 3 and 5 7 exactly. Similarly, fractional binary notation can only represent numbers that can be written x × 2y. Other values can only be approximated. For example, the number 1 5 can be represented exactly as the frac- tional decimal number 0.20. As a fractional binary number, however, we cannot represent it exactly and instead must approximate it with increasing accuracy by lengthening the binary representation: Representation Value Decimal 0.02 0 2 0.010 0.012 1 4 0.2510 0.0102 2 8 0.2510 0.00112 3 16 0.187510 0.001102 6 32 0.187510 0.0011012 13 64 0.20312510 0.00110102 26 128 0.20312510 0.001100112 51 256 0.1992187510 练习题 2.45 填写下表中的缺失的信息 : 小数值 二进制表示 十进制表示 136 Chapter 2 Representing and Manipulating Information Practice Problem 2.45 Fill in the missing information in the following table: Fractional value Binary representation Decimal representation 1 8 0.001 0.125 3 4 25 16 10.1011 1.001 5.875 3.1875 Practice Problem 2.46 The imprecision of floating-point arithmetic can have disastrous effects. On Febru- ary 25, 1991, during the first Gulf War, an American Patriot Missile battery in Dharan, Saudi Arabia, failed to intercept an incoming Iraqi Scud missile. The Scud struck an American Army barracks and killed 28 soldiers. The U.S. General Accounting Office (GAO) conducted a detailed analysis of the failure [72] and de- termined that the underlying cause was an imprecision in a numeric calculation. In this exercise, you will reproduce part of the GAO’s analysis. The Patriot system contains an internal clock, implemented as a counter that is incremented every 0.1 seconds. To determine the time in seconds, the program would multiply the value of this counter by a 24-bit quantity that was a fractional binary approximation to 1 10 . In particular, the binary representation of 1 10 is the nonterminating sequence 0.000110011[0011] ...2, where the portion in brackets is repeated indefinitely. The program approximated 0.1, as a value x, by considering just the first 23 bits of the sequence to the right of the binary point: x = 0.00011001100110011001100. (See Problem 2.51 for a discussion of how they could have approximated 0.1 more precisely.) A. What is the binary representation of 0.1 − x? B. What is the approximate decimal value of 0.1 − x? C. The clock starts at 0 when the system is first powered up and keeps counting up from there. In this case, the system had been running for around 100 hours. What was the difference between the actual time and the time computed by the software? D. The system predicts where an incoming missile will appear based on its velocity and the time of the last radar detection. Given that a Scud travels at around 2000 meters per second, how far off was its prediction? Normally, a slight error in the absolute time reported by a clock reading would not affect a tracking computation. Instead, it should depend on the relative time between two successive readings. The problem was that the Patriot software had 0.001 0.125 136 Chapter 2 Representing and Manipulating Information Practice Problem 2.45 Fill in the missing information in the following table: Fractional value Binary representation Decimal representation 1 8 0.001 0.125 3 4 25 16 10.1011 1.001 5.875 3.1875 Practice Problem 2.46 The imprecision of floating-point arithmetic can have disastrous effects. On Febru- ary 25, 1991, during the first Gulf War, an American Patriot Missile battery in Dharan, Saudi Arabia, failed to intercept an incoming Iraqi Scud missile. The Scud struck an American Army barracks and killed 28 soldiers. The U.S. General Accounting Office (GAO) conducted a detailed analysis of the failure [72] and de- termined that the underlying cause was an imprecision in a numeric calculation. In this exercise, you will reproduce part of the GAO’s analysis. The Patriot system contains an internal clock, implemented as a counter that is incremented every 0.1 seconds. To determine the time in seconds, the program would multiply the value of this counter by a 24-bit quantity that was a fractional binary approximation to 1 10 . In particular, the binary representation of 1 10 is the nonterminating sequence 0.000110011[0011] ...2, where the portion in brackets is repeated indefinitely. The program approximated 0.1, as a value x, by considering just the first 23 bits of the sequence to the right of the binary point: x = 0.00011001100110011001100. (See Problem 2.51 for a discussion of how they could have approximated 0.1 more precisely.) A. What is the binary representation of 0.1 − x? B. What is the approximate decimal value of 0.1 − x? C. The clock starts at 0 when the system is first powered up and keeps counting up from there. In this case, the system had been running for around 100 hours. What was the difference between the actual time and the time computed by the software? D. The system predicts where an incoming missile will appear based on its velocity and the time of the last radar detection. Given that a Scud travels at around 2000 meters per second, how far off was its prediction? Normally, a slight error in the absolute time reported by a clock reading would not affect a tracking computation. Instead, it should depend on the relative time between two successive readings. The problem was that the Patriot software had 136 Chapter 2 Representing and Manipulating Information Practice Problem 2.45 Fill in the missing information in the following table: Fractional value Binary representation Decimal representation 1 8 0.001 0.125 3 4 25 16 10.1011 1.001 5.875 3.1875 Practice Problem 2.46 The imprecision of floating-point arithmetic can have disastrous effects. On Febru- ary 25, 1991, during the first Gulf War, an American Patriot Missile battery in Dharan, Saudi Arabia, failed to intercept an incoming Iraqi Scud missile. The Scud struck an American Army barracks and killed 28 soldiers. The U.S. General Accounting Office (GAO) conducted a detailed analysis of the failure [72] and de- termined that the underlying cause was an imprecision in a numeric calculation. In this exercise, you will reproduce part of the GAO’s analysis. The Patriot system contains an internal clock, implemented as a counter that is incremented every 0.1 seconds. To determine the time in seconds, the program would multiply the value of this counter by a 24-bit quantity that was a fractional binary approximation to 1 10 . In particular, the binary representation of 1 10 is the nonterminating sequence 0.000110011[0011] ...2, where the portion in brackets is repeated indefinitely. The program approximated 0.1, as a value x, by considering just the first 23 bits of the sequence to the right of the binary point: x = 0.00011001100110011001100. (See Problem 2.51 for a discussion of how they could have approximated 0.1 more precisely.) A. What is the binary representation of 0.1 − x? B. What is the approximate decimal value of 0.1 − x? C. The clock starts at 0 when the system is first powered up and keeps counting up from there. In this case, the system had been running for around 100 hours. What was the difference between the actual time and the time computed by the software? D. The system predicts where an incoming missile will appear based on its velocity and the time of the last radar detection. Given that a Scud travels at around 2000 meters per second, how far off was its prediction? Normally, a slight error in the absolute time reported by a clock reading would not affect a tracking computation. Instead, it should depend on the relative time between two successive readings. The problem was that the Patriot software had 10.1011 1.001 5.875 3.1875 练习题 2.46 浮点运算的不精确性能够产生灾难性的后果。   爱国者导弹系统中有一个内置的时钟,其实现类似一个计数器,每 0.1 秒就加 1。为了以秒为 单位来确定时间,程序将用一个 24 位的近似于 1/10 的二进制小数值来乘以这个计数器的值。特 别地,1/10 的二进制表达式是一个无穷序列 0.000110011[0011]…2,其中,方括号里的部分是无 限循环的。程序用值 x 近似地表示 0.1,x 只考虑这个序列的二进制小数点右边的前 23 位 :x = 0.00011001100110011001100。(参考练习题 2.51,里面有关于如何更精确地近似表示 0.1 的讨论。) A. 0.1 - x 的二进制表示是什么? B. 0.1 - x 的近似的十进制值是多少? C. 当系统初始启动时,时钟从 0 开始,并且一直保持计数。在这个例子中,系统已经运行了大约 100 个小时。程序计算出的时间和实际的时间之差为多少? D. 系统根据一枚来袭导弹的速率和它最后被雷达侦测到的时间,来预测它将在哪里出现。假定飞毛腿 导弹的速率大约是 2000 米每秒,对它的预测偏差了多少?   通过一次读取时钟得到的绝对时间中的一个轻微错误,通常不会影响跟踪的计算。相反,它应该 依赖于两次连续的读取之间的相对时间。问题是爱国者导弹的软件已经升级,可以使用更精确的函数 来读取时间,但不是所有的函数调用都用新的代码替换。结果就是,跟踪软件一次读取用的是精确的 时间,而另一次读取用的是不精确的时间 [100]。 正文.indd 69 2010-10-19 14:18:46  70  第一部分 程序结构和执行  2.4.2 IEEE 浮点表示 前一节中谈到的定点表示法不能很有效地表示非常大的数字。例如,表达式 5 × 2100 是用 101 后面跟随 100 个零组成的位模式来表示。相反地,我们希望通过给定 x 和 y 的值,来表示形 如 x × 2y 的数。 IEEE 浮点标准用 V = (-1)s × M × 2E 的形式来表示一个数 : • 符号(sign) s 决定这个数是负数(s=1)还是正数(s=0),而对于数值 0 的符号位解释作 为特殊情况处理。 • 尾数(significand) M 是一个二进制小数,它的范围是 1 ~ 2-ε,或者是 0 ~ 1-ε。 • 阶码(exponent) E 的作用是对浮点数加权,这个权重是 2 的 E 次幂(可能是负数)。 将浮点数的位表示划分为三个字段,分别对这些值进行编码 : • 一个单独的符号位 s 直接编码符号 s。 • k 位的阶码字段 exp = ek-1…e1e0 编码阶码 E。 • n 位小数字段 frac = fn-1…f1 f0 编码尾数 M,但是编码出来的值也依赖于阶码字段的值是否 等于 0。 图 2-31 给出了将这三个字段装进字中两种最常见的格式。在单精度浮点格式(C 语言中的 float)中,s、exp 和 frac 字段分别为 1 位、k = 8 位和 n = 23 位,得到一个 32 位的表示。 在双精度浮点格式(C 语言中的 double)中,s、exp 和 frac 字段分别为 1 位、k = 11 位和 n = 52 位,得到一个 64 位的表示。 图 2-31 标准浮点格式(浮点由 3 个字段表示,两种最常见的格式是它们 被封装到 32 位(单精度)和 64 位(双精度)的字中) 给定了位表示,根据 exp 的值,被编码的值可以分成三种不同的情况(最后一种情况有两 个变种)。图 2-32 说明了对单精度格式的情况。 图 2-32 单精度浮点数值的分类(阶码的值决定了这个数是规格化的、非规格化的、或特殊值) 情况 1 :规格化的值 这是最普遍的情况。当 exp 的位模式既不全为 0(数值 0),也不全为 1(单精度数值为 正文.indd 70 2010-10-19 14:18:47 第 2 章 信息的表示和处理  71  255,双精度数值为 2047)时,都属于这类情况。在这种情况中,阶码字段被解释为以偏置 (biased)形式表示的有符号整数。也就是说,阶码的值是 E = e-Bias,其中 e 是无符号数,其位 表示为 ek-1…e1e0,而 Bias 是一个等于 2k-1-1(单精度是 127,双精度是 1023)的偏置值。由此 产生指数的取值范围,对于单精度是 -126 ~ +127,而对于双精度是 -1022 ~ +1023。 对小数字段 frac 的解释为描述小数值 f,其中 0 ≤ f < 1,其二进制表示为 0.fn-1…f1 f0,也 就是二进制小数点在最高有效位的左边。尾数定义为 M = 1 + f。有时,这种方式也叫做隐含的以 1 开头的(implied leading 1)表示,因为我们可以把 M 看成一个二进制表达式为 1.fn-1 fn-2…f0 的 数字。既然我们总是能够调整阶码 E,使得尾数 M 在范围 1 ≤ M< 2 之中(假设没有溢出),那 么这种表示方法是一种轻松获得一个额外精度位的技巧。由于第一位总是等于 1,因此我们就不 需要显式地表示它。 情况 2 :非规格化的值 当阶码域为全 0 时,所表示的数就是非规格化形式。在这种情况下,阶码值是 E = 1 - Bias, 而尾数的值是 M = f,也就是小数字段的值,不包含隐含的开头的 1。 对于非规格化值为什么要这样设置偏置值 使阶码值为 1-Bias 而不是简单的 -Bias 似乎是违反直觉的。我们将很快看到,这种方式提 供了一种从非规格化值平滑转换到规格化值的方法。 非规格化数有两个用途。首先,它们提供了一种表示数值 0 的方法,因为使用规格化数,我 们必须总是使 M ≥ 1,因此我们就不能表示 0。实际上,+0.0 的浮点表示的位模式为全 0 :符号 位是 0,阶码字段全为 0(表明是一个非规格化值),而小数域也全为 0,这就得到 M = f = 0。令 人奇怪的是,当符号位为 1,而其他域全为 0 时,我们得到值 -0.0。根据 IEEE 的浮点格式,认 为值 +0.0 和 -0.0 在某些方面是不同的,而在其他方面是相同的。 非规格化数的另外一个功能是表示那些非常接近于 0.0 的数。它们提供了一种属性,称为逐 渐溢出(gradual underflow),其中,可能的数值分布均匀地接近于 0.0。 情况 3 :特殊值 最后一类数值是当指阶码全为 1 的时候出现的。当小数域全为 0 时,得到的值表示无穷,当 s = 0 时是 + ∞,或者当 s = 1 时是 - ∞。当我们把两个非常大的数相乘,或者除以零时,无穷能 够表示溢出的结果。当小数域为非零时,结果值被称为“NaN”,就是“不是一个数”(Not a Number)的缩写。一些运算的结果不能是实数或无穷,就会返回这样的 NaN 值,比如当计算 Section 2.4 Floating Point 139 Case 2: Denormalized Values When the exponent field is all zeros, the represented number is in denormalized form. In this case, the exponent value is E = 1 − Bias, and the significand value is M = f , that is, the value of the fraction field without an implied leading 1. Aside Why set the bias this way for denormalized values? Having the exponent value be 1 − Bias rather than simply −Bias might seem counterintuitive. We will see shortly that it provides for smooth transition from denormalized to normalized values. Denormalized numbers serve two purposes. First, they provide a way to represent numeric value 0, since with a normalized number we must always have M ≥ 1, and hence we cannot represent 0. In fact the floating-point representation of +0.0 has a bit pattern of all zeros: the sign bit is 0, the exponent field is all zeros (indicating a denormalized value), and the fraction field is all zeros, giving M = f = 0. Curiously, when the sign bit is 1, but the other fields are all zeros, we get the value −0.0. With IEEE floating-point format, the values −0.0 and +0.0 are considered different in some ways and the same in others. A second function of denormalized numbers is to represent numbers that are very close to 0.0. They provide a property known as gradual underflow in which possible numeric values are spaced evenly near 0.0. Case 3: Special Values A final category of values occurs when the exponent field is all ones. When the fraction field is all zeros, the resulting values represent infinity, either +∞ when s = 0, or −∞ when s = 1. Infinity can represent results that overflow, as when we multiply two very large numbers, or when we divide by zero. When the fraction field is nonzero, the resulting value is called a “NaN,” short for “Not a Number.” Such values are returned as the result of an operation where the result cannot be given as a real number or as infinity, as when computing √ −1 or ∞−∞. They can also be useful in some applications for representing uninitialized data. 2.4.3 Example Numbers Figure 2.33 shows the set of values that can be represented in a hypothetical 6-bit format having k = 3 exponent bits and n = 2 fraction bits. The bias is 23−1 − 1 =3. Part A of the figure shows all representable values (other than NaN). The two infinities are at the extreme ends. The normalized numbers with maximum magnitude are ±14. The denormalized numbers are clustered around 0. These can be seen more clearly in part B of the figure, where we show just the numbers between −1.0 and +1.0. The two zeros are special cases of denormalized numbers. Observe that the representable numbers are not uniformly distributed—they are denser nearer the origin. Figure 2.34 shows some examples for a hypothetical 8-bit floating-point format having k = 4 exponent bits and n = 3 fraction bits. The bias is 24−1 − 1 = 7. The 或∞ - ∞时。在某些应用中,表示未初始化的数据时,它们也很有用处。 2.4.3 数字示例 图 2-33 展示了一组数值,它们可以用假定的 6 位格式来表示,有 k = 3 的阶码位和 n = 2 的 尾数位。偏置量是 23-1-1 = 3。图中的 A 部分显示了所有可表示的值(除了 NaN)。两个无穷值 图 2-33 6 位浮点格式可表示的值(k = 3 的阶码位,n = 2 的尾数位,偏置量为 3) 正文.indd 71 2010-10-19 14:18:47  72  第一部分 程序结构和执行  在两个末端。最大数量值的规格化数是 ±14。非规格化数聚集在 0 的附近。图的 B 部分中,我 们只展示了介于 -1.0 和 +1.0 之间的数值,这样就能够看得更加清楚了。两个零是特殊的非规格 化数。可以观察到,那些可表示的数并不是均匀分布的—越靠近原点处它们越稠密。 图 2-34 展示了假定的 8 位浮点格式的示例,其中有 k = 4 的阶码位和 n = 3 的小数位。偏置 量是 24-1-1=7。图被分成了三个区域,用来描述三类数字。不同的列给出了阶码字段是如何编码 阶码 E 的,小数字段是如何编码尾数 M 的,以及它们一起是如何形成要表示的值 V = 2E × M 的。从 0 自身开始,最靠近 0 的是非规格化数。这种格式的非规格化数的 E = 1-7 = -6,得到权 2E = Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction 。小数 f 的值的范围是 Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction ,从而得到数 V 的范围是 0 ~ Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction 。 图 2-34 8 位浮点格式的非负值示例 这种形式的最小规格化数同样有 E =1-7 =-6,并且小数取值范围也为 Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction , Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction 。然而, 尾数的范围在 1 + 0 = 1 和 Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction 之间,得出数 V 的范围在 Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction 和 Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction 之间。 可以观察到最大非规格化数 Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction 和最小规格化数 Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction 之间的平滑转变。这种平滑性归功于我们 对非规格化数的 E 的定义。通过将 E 定义为 1-Bias,而不是 -Bias,我们可以补偿非规格化数 的尾数没有隐含的开头的 1 这一事实。 当增大阶码时,我们成功地得到更大的规格化值,通过 1.0 后得到最大的规格化数。这个数 具有阶码 E = 7,得到一个权 2E = 128。小数等于 Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction 得到尾数 M= Section 2.4 Floating Point 141 figure is divided into three regions representing the three classes of numbers. The different columns show how the exponent field encodes the exponent E, while the fraction field encodes the significand M, and together they form the represented value V = 2E × M. Closest to 0 are the denormalized numbers, starting with 0 itself. Denormalized numbers in this format have E = 1 − 7 =−6, giving a weight 2E = 1 64 . The fractions f and significands M range over the values 0, 1 8 ,..., 7 8 , giving numbers V in the range 0 to 1 64 × 7 8 = 7 512 . The smallest normalized numbers in this format also have E = 1 − 7 =−6, and the fractions also range over the values 0, 1 8 ,... 7 8 . However, the significands then range from 1 + 0 = 1 to 1 + 7 8 = 15 8 , giving numbers V in the range 8 512 = 1 64 to 15 512 . Observe the smooth transition between the largest denormalized number 7 512 and the smallest normalized number 8 512 . This smoothness is due to our definition of E for denormalized values. By making it 1 − Bias rather than −Bias, we com- pensate for the fact that the significand of a denormalized number does not have an implied leading 1. As we increase the exponent, we get successively larger normalized values, passing through 1.0 and then to the largest normalized number. This number has exponent E = 7, giving a weight 2E = 128. The fraction equals 7 8 , giving a signifi- cand M = 15 8 . Thus, the numeric value is V = 240. Going beyond this overflows to +∞. One interesting property of this representation is that if we interpret the bit representations of the values in Figure 2.34 as unsigned integers, they occur in ascending order, as do the values they represent as floating-point numbers. This is no accident—the IEEE format was designed so that floating-point numbers could be sorted using an integer sorting routine. A minor difficulty occurs when dealing with negative numbers, since they have a leading 1, and they occur in descending order, but this can be overcome without requiring floating-point operations to perform comparisons (see Problem 2.83). Practice Problem 2.47 Consider a 5-bit floating-point representation based on the IEEE floating-point format, with one sign bit, two exponent bits (k = 2), and two fraction bits (n = 2). The exponent bias is 22−1 − 1 = 1. The table that follows enumerates the entire nonnegative range for this 5-bit floating-point representation. Fill in the blank table entries using the following directions: e: The value represented by considering the exponent field to be an unsigned integer E: The value of the exponent after biasing 2E: The numeric weight of the exponent f : The value of the fraction 。因此,数值是 V = 240。超出 这个值就会溢出到 + ∞。 这种表示具有一个有趣的属性,假如我们将图 2-34 中的值的位表达式解释为无符号整数, 它们就是按升序排列的,就像它们表示的浮点数一样。这不是偶然的—IEEE 如此设计格式就 正文.indd 72 2010-10-19 14:18:50 第 2 章 信息的表示和处理  73  是为了浮点数能够使用整数排序函数来进行排序。当处理负数时,有一个小的难点,因为它们有 开头的 1,并且它们是按照降序出现的,但是不需要浮点运算来进行比较也能解决这个问题(参 见家庭作业 2.83)。 练习题 2.47 假设一个基于 IEEE 浮点格式的 5 位浮点表示,有 1 个符号位、2 个阶码位(k = 2)和 两个小数位(n = 2)。阶码偏置量是 22-1-1 = 1。 下表中列举了这个 5 位浮点表示的全部非负取值范围。使用下面的条件,填写表格中的空白项 : e :假定阶码字段是一个无符号整数表示的值。 E :偏置之后的阶码值。 2E :阶码的权重数。 f :小数值。 M :尾数的值。 2E × M :该数(未归约的)小数值。 V :该数归约后的小数值。 十进制 :该数的十进制表示。 写出 2E、f、M、2E × M 和 V 的值,要么是整数(如果可能的话),要么是形如 142 Chapter 2 Representing and Manipulating Information M: The value of the significand 2E × M: The (unreduced) fractional value of the number V : The reduced fractional value of the number Decimal: The decimal representation of the number Express the values of 2E, f , M,2E × M, and V either as integers (when possible) or as fractions of the form x y , where y is a power of 2. You need not fill in entries marked “—”. Bits eE2E fM 2E × MVDecimal 0 00 00 0 00 01 0 00 10 0 00 11 0 01 00 0 01 01 101 1 4 5 4 5 4 5 4 1.25 0 01 10 0 01 11 0 10 00 0 10 01 0 10 10 0 10 11 0 11 00 ——— —— — — 0 11 01 ——— —— — — 0 11 10 ——— —— — — 0 11 11 ——— —— — — Figure 2.35 shows the representations and numeric values of some important single- and double-precision floating-point numbers. As with the 8-bit format shown in Figure 2.34, we can see some general properties for a floating-point representation with a k-bit exponent and an n-bit fraction: . The value +0.0 always has a bit representation of all zeros. . The smallest positive denormalized value has a bit representation consisting of a 1 in the least significant bit position and otherwise all zeros. It has a fraction (and significand) value M = f = 2−n and an exponent value E =−2k−1 + 2. The numeric value is therefore V = 2−n− 2k−1 +2. . The largest denormalized value has a bit representation consisting of an exponent field of all zeros and a fraction field of all ones. It has a fraction (and significand) value M = f = 1 − 2−n (which we have written 1 − �) and an exponent value E =−2k−1 + 2. The numeric value is therefore V = (1 −2−n) × 2− 2k−1 +2, which is just slightly smaller than the smallest normalized value. 的小数,这里 y 是 2 的幂。标注“-”的条目不用填。 图 2-35 展示了一些重要的单精度和双精度浮点数的表示和数字值。根据图 2-34 中展示的 8 位格式,我们能够看出有 k 位阶码和 n 位小数的浮点表示的一般属性。 • 值 +0.0 总有一个全为 0 的位表示。 • 最小的正非规格化值的位表示,是由最低有效位为 1 而其他所有位为 0 构成的。它具有小 数(和尾数)值 M = f = 2-n 和阶码值 E = -2k-1 + 2。因此它的数字值是 V = 2-n-2k-1+2。 • 最大的非规格化值的位模式是由全为 0 的阶码字段和全为 1 的小数字段组成的。它有小 数(和尾数)值 M = f = 1-2-n(我们写成 1-ε)和阶码值 E = -2k-1 + 2。因此,数值 V = (1-2-n)× 2-n-2k-1+2,这仅比最小的规格化值小一点。 • 最小的正规格化值的位模式的阶码字段的最低有效位为 1,其他位全为 0。它的尾数值 M = 1,而阶码值 E = -2k-1 + 2。因此,数值 V = 2-2k-1+2。 正文.indd 73 2010-10-19 14:18:50  74  第一部分 程序结构和执行  • 值 1.0 的位表示的阶码字段除了最高有效位等于 1 以外,其他位都等于 0。它的尾数值是 M = 1,而它的阶码值是 E= 0。 • 最大的规格化值的位表示的符号位为 0,阶码的最低有效位等于 0,其他位等于 1。它的小 数值 f = 1-2-n,尾数 M = 2-2-n(我们写作 2- ε)。它的阶码值 E = 2k-1-1,得到数值 V = (2-2-n)×22k-1-1 = (1-2-n-1)×22k-1。 图 2-35 非负浮点数的示例 ε ε εε 练习把一些整数值转换成浮点形式对理解浮点表示很有用。例如,在图 2-14 中我们看 到 12 345 具 有 二 进 制 表 示 [11000000111001]。 通 过 将 二 进 制 小 数 点 左 移 13 位, 我 们 创 建 这 个 数 的 一 个 规 格 化 表 示, 得 到 12345 = 1.10000001110012×213。 为 了 用 IEEE 单 精 度 形 式 来编码,我们丢弃开头的 1,并且在末尾增加 10 个 0,来构造小数字段,得到二进制表示 [10000001110010000000000]。为了构造阶码字段,我们用 13 加上偏置量 127,得到 140,其二 进制表示为 [10001100]。加上符号位 0,我们就得到二进制的浮点表示 [010001100100000011100 10000000000]。回想 2.1.4 节,我们观察到整数值 12345(0x3039)和单精度浮点值 12345.0 (0x4640E400)在位级表示上有下列关系 : Section 2.4 Floating Point 143 Single precision Double precision Description exp frac Value Decimal Value Decimal Zero 00 ...00 0 ...00 0 0.0 00.0 Smallest denorm. 00 ...00 0 ...01 2−23 × 2−126 1.4 × 10−45 2−52 × 2−1022 4.9 × 10−324 Largest denorm. 00 ...00 1 ...11 (1 − �) × 2−126 1.2 × 10−38 (1 − �) × 2−1022 2.2 × 10−308 Smallest norm. 00 ...01 0 ...00 1 × 2−126 1.2 × 10−38 1 × 2−1022 2.2 × 10−308 One 01 ...11 0 ...00 1 × 20 1.01× 20 1.0 Largest norm. 11 ...10 1 ...11 (2 − �) × 2127 3.4 × 1038 (2 − �) × 21023 1.8 × 10308 Figure 2.35 Examples of nonnegative floating-point numbers. . The smallest positive normalized value has a bit representation witha1in the least significant bit of the exponent field and otherwise all zeros. It has a significand value M = 1 and an exponent value E =−2k−1 + 2. The numeric value is therefore V = 2− 2k−1 +2. . The value 1.0 has a bit representation with all but the most significant bit of the exponent field equal to 1 and all other bits equal to 0. Its significand value is M = 1 and its exponent value is E = 0. . The largest normalized value has a bit representation with a sign bit of 0, the least significant bit of the exponent equal to 0, and all other bits equal to 1. It has a fraction value of f = 1 − 2−n, giving a significand M = 2 − 2−n (which we have written 2 − �). It has an exponent value E = 2k−1 − 1, giving a numeric value V = (2 − 2−n) × 22k−1 −1 = (1 − 2−n−1) × 22k−1. One useful exercise for understanding floating-point representations is to con- vert sample integer values into floating-point form. For example, we saw in Figure 2.14 that 12,345 has binary representation [11000000111001]. We create a normal- ized representation of this by shifting 13 positions to the right of a binary point, giving 12345 = 1.10000001110012 × 213. To encode this in IEEE single-precision format, we construct the fraction field by dropping the leading 1 and adding 10 zeros to the end, giving binary representation [10000001110010000000000]. To construct the exponent field, we add bias 127 to 13, giving 140, which has bi- nary representation [10001100]. We combine this with a sign bit of 0 to get the floating-point representation in binary of [01000110010000001110010000000000]. Recall from Section 2.1.4 that we observed the following correlation in the bit- level representations of the integer value 12345 (0x3039) and the single-precision floating-point value 12345.0 (0x4640E400): 00003039 00000000000000000011000000111001 ************* 4640E400 01000110010000001110010000000000 现在我们可以看到,相关的区域对应于整数的低位,刚好在等于 1 的最高有效位之前停止 (这个位就是隐含的开头的位 1),和浮点表示的小数部分的高位是相匹配的。 练习题 2.48 正如在练习题 2.6 中提到的,整数 3 510 593 的十六进制表示为 0x00359141,而单精 度浮点数 3510593.0 的十六进制表示为 0x4A564504。推导出这个浮点表示,并解释整数和浮点数 表示的位之间的关系。 练习题 2.49 A. 对于一种具有 n 位小数的浮点格式,给出不能准确描述的最小正整数的公式(因为要想准确表示它 需要 n+1 位小数)。假设阶码字段长度 k 足够大,可以表示的阶码范围不会限制这个问题。 B. 对于单精度格式(n = 23),这个整数的数字值是多少? 2.4.4 舍入 因为表示方法限制了浮点数的范围和精度,浮点运算只能近似地表示实数运算。因此,对于 值 x,我们一般想用一种系统的方法,能够找到“最接近的”匹配值 x',它可以用期望的浮点形 式表示出来。这就是舍入(rounding)运算的任务。一个关键问题是在两个可能值的中间确定舍 入方向。例如,如果我有 1.50 美元,想把它舍入到最接近的美元数,应该是 1 美元还是 2 美元 正文.indd 74 2010-10-19 14:18:51 第 2 章 信息的表示和处理  75  呢?一种可选择的方法是维持实际数字的下界和上界。例如,我们可以确定可表示的值 x- 和 x+, 使得 x 的值位于它们之间 :x- ≤ x ≤ x+。IEEE 浮点格式定义了四种不同的舍入方式。默认的方 法是找到最接近的匹配,而其他三种可用于计算上界和下界。 图 2-36 举例说明了应用四种舍入方式,将一个金额数舍入到最接近的整数美元数。向偶数 舍入(round-to-even),也称为向最接近的值舍入(round-to-nearest),是默认的方式,试图找到 一个最接近的匹配值。因此,它将 1.40 美元舍入成 1 美元,而将 1.60 美元舍入成 2 美元,因为 它们是最接近的整数美元值。唯一的设计决策是确定两个可能结果中间数值的舍入效果。向偶数 舍入方式采取的方法是 :将数字向上或者向下舍入,使得结果的最低有效数字是偶数。因此,这 种方法将 1.5 美元和 2.5 美元都舍入成 2 美元。 方 式 1.40 1.60 1.50 2.50 -1.50 向偶数舍入 1 2 2 2 -2 向零舍入 1 1 1 2 -1 向下舍入 1 1 1 2 -2 向上舍入 2 2 2 3 -1 图 2-36 以美元为例说明舍入方式(单位为美元) 其他三种方式产生实际值的确界(guaranteed bound)。这些方法在一些数字应用中是很有用 的。向零舍入方式把正数向下舍入,把负数向上舍入,得到值 x^ ,使得 | x ^ | ≤ | x |。向下舍入方 式把正数和负数都向下舍入,得到值 x-,使得 x- ≤ x。向上舍入方式把正数和负数都向上舍入, 得到值 x+,满足 x ≤ x+。 向偶数舍入最初看上去好像是个相当随意的目标—有什么理由偏向取偶数呢?为什么不始 终把位于两个可表示的值中间的值都向上舍入呢?使用这种方法的一个问题就是很容易假想到这 样的情景 :这种方法舍入一组数值,会在计算这些值的平均数中引入统计偏差。我们采用这种方 式舍入得到的一组数的平均值将比这些数本身的平均值略高一些。相反,如果我们总是把两个可 表示值中间的数字向下舍入,那么舍入后的一组数的平均值将比这些数本身的平均值略低一些。 向偶数舍入在大多数现实情况中避免了这种统计偏差。在 50% 的时间里,它将向上舍入,而在 50% 的时间里,它将向下舍入。 在我们不想舍入到整数时,也可以使用向偶数舍入。我们只是简单地考虑最低有效数字是奇 数还是偶数。例如,假设我们想将十进制数舍入到最接近的百分位。不管用那种舍入方式,我们 都将把 1.2349999 舍入到 1.23,而将 1.2350001 舍入到 1.24,因为它们不是在 1.23 和 1.24 的正 中间。另一方面我们将把两个数 1.2350000 和 1.2450000 都舍入到 1.24,因为 4 是偶数。 相似地,向偶数舍入法能够运用于二进制小数。我们将最低有效位的值 0 认为是偶数,值 1 认为是奇数。一般来说,只有对形如 XX…X.YY…Y100…的二进制位模式的数,这种舍入方式才 有效,其中 X 和 Y 表示任意位值,最右边的 Y 是要舍入的位置。只有这种位模式表示在两个可 能的结果正中间的值。例如,考虑舍入值到最近的四分之一的问题(也就是二进制小数点右边 2 位)。我们将 10.000112 (2 Section 2.4 Floating Point 145 Mode $1.40 $1.60 $1.50 $2.50 $−1.50 Round-to-even $1 $2 $2 $2 $−2 Round-toward-zero $1 $1 $1 $2 $−1 Round-down $1 $1 $1 $2 $−2 Round-up $2 $2 $2 $3 $−1 Figure 2.36 Illustration of rounding modes for dollar rounding. The first rounds to a nearest value, while the other three bound the result above or below. convention that it rounds the number either upward or downward such that the least significant digit of the result is even. Thus, it rounds both $1.50 and $2.50 to $2. The other three modes produce guaranteed bounds on the actual value. These can be useful in some numerical applications. Round-toward-zero mode rounds positive numbers downward and negative numbers upward, giving a value ˆx such that |ˆx|≤|x|. Round-down mode rounds both positive and negative numbers downward, giving a value x− such that x− ≤ x. Round-up mode rounds both positive and negative numbers upward, giving a value x+ such that x ≤ x+. Round-to-even at first seems like it has a rather arbitrary goal—why is there any reason to prefer even numbers? Why not consistently round values halfway between two representable values upward? The problem with such a convention is that one can easily imagine scenarios in which rounding a set of data values would then introduce a statistical bias into the computation of an average of the values. The average of a set of numbers that we rounded by this means would be slightly higher than the average of the numbers themselves. Conversely, if we always rounded numbers halfway between downward, the average of a set of rounded numbers would be slightly lower than the average of the numbers them- selves. Rounding toward even numbers avoids this statistical bias in most real-life situations. It will round upward about 50% of the time and round downward about 50% of the time. Round-to-even rounding can be applied even when we are not rounding to a whole number. We simply consider whether the least significant digit is even or odd. For example, suppose we want to round decimal numbers to the nearest hundredth. We would round 1.2349999 to 1.23 and 1.2350001 to 1.24, regardless of rounding mode, since they are not halfway between 1.23 and 1.24. On the other hand, we would round both 1.2350000 and 1.2450000 to 1.24, since 4 is even. Similarly, round-to-even rounding can be applied to binary fractional num- bers. We consider least significant bit value 0 to be even and 1 to be odd. In general, the rounding mode is only significant when we have a bit pattern of the form XX ...X.Y Y ...Y100 ..., where X and Y denote arbitrary bit values with the rightmost Y being the position to which we wish to round. Only bit patterns of this form denote values that are halfway between two possible results. As exam- ples, consider the problem of rounding values to the nearest quarter (i.e., 2 bits to the right of the binary point). We would round 10.000112 (2 3 32 ) down to 10.002 (2),) 向下舍入到 10.002 (2),10.001102( 146 Chapter 2 Representing and Manipulating Information and 10.001102 (2 3 16 ) up to 10.012 (2 1 4 ), because these values are not halfway be- tween two possible values. We would round 10.111002 (2 7 8 ) up to 11.002 (3) and 10.101002 (2 5 8 ) down to 10.102 (2 1 2 ), since these values are halfway between two possible results, and we prefer to have the least significant bit equal to zero. Practice Problem 2.50 Show how the following binary fractional values would be rounded to the nearest half (1 bit to the right of the binary point), according to the round-to-even rule. In each case, show the numeric values, both before and after rounding. A. 10.0102 B. 10.0112 C. 10.1102 D. 11.0012 Practice Problem 2.51 We saw in Problem 2.46 that the Patriot missile software approximated 0.1 as x =0.000110011001100110011002. Suppose instead that they had used IEEE round- to-even mode to determine an approximation x� to 0.1 with 23 bits to the right of the binary point. A. What is the binary representation of x�? B. What is the approximate decimal value of x� − 0.1? C. How far off would the computed clock have been after 100 hours of opera- tion? D. How far off would the program’s prediction of the position of the Scud missile have been? Practice Problem 2.52 Consider the following two 7-bit floating-point representations based on the IEEE floating point format. Neither has a sign bit—they can only represent nonnegative numbers. 1. Format A There are k = 3 exponent bits. The exponent bias is 3. There are n = 4 fraction bits. 2. Format B There are k = 4 exponent bits. The exponent bias is 7. There are n = 3 fraction bits. Below, you are given some bit patterns in Format A, and your task is to convert them to the closest value in Format B. If necessary, you should apply the round-to- even rounding rule. In addition, give the values of numbers given by the Format A ) 向上舍入到 10.012( 146 Chapter 2 Representing and Manipulating Information and 10.001102 (2 3 16 ) up to 10.012 (2 1 4 ), because these values are not halfway be- tween two possible values. We would round 10.111002 (2 7 8 ) up to 11.002 (3) and 10.101002 (2 5 8 ) down to 10.102 (2 1 2 ), since these values are halfway between two possible results, and we prefer to have the least significant bit equal to zero. Practice Problem 2.50 Show how the following binary fractional values would be rounded to the nearest half (1 bit to the right of the binary point), according to the round-to-even rule. In each case, show the numeric values, both before and after rounding. A. 10.0102 B. 10.0112 C. 10.1102 D. 11.0012 Practice Problem 2.51 We saw in Problem 2.46 that the Patriot missile software approximated 0.1 as x =0.000110011001100110011002. Suppose instead that they had used IEEE round- to-even mode to determine an approximation x� to 0.1 with 23 bits to the right of the binary point. A. What is the binary representation of x�? B. What is the approximate decimal value of x� − 0.1? C. How far off would the computed clock have been after 100 hours of opera- tion? D. How far off would the program’s prediction of the position of the Scud missile have been? Practice Problem 2.52 Consider the following two 7-bit floating-point representations based on the IEEE floating point format. Neither has a sign bit—they can only represent nonnegative numbers. 1. Format A There are k = 3 exponent bits. The exponent bias is 3. There are n = 4 fraction bits. 2. Format B There are k = 4 exponent bits. The exponent bias is 7. There are n = 3 fraction bits. Below, you are given some bit patterns in Format A, and your task is to convert them to the closest value in Format B. If necessary, you should apply the round-to- even rounding rule. In addition, give the values of numbers given by the Format A ), 因为这些值不是两个可能值的正中间值。我们将 10.111002( 146 Chapter 2 Representing and Manipulating Information and 10.001102 (2 3 16 ) up to 10.012 (2 1 4 ), because these values are not halfway be- tween two possible values. We would round 10.111002 (2 7 8 ) up to 11.002 (3) and 10.101002 (2 5 8 ) down to 10.102 (2 1 2 ), since these values are halfway between two possible results, and we prefer to have the least significant bit equal to zero. Practice Problem 2.50 Show how the following binary fractional values would be rounded to the nearest half (1 bit to the right of the binary point), according to the round-to-even rule. In each case, show the numeric values, both before and after rounding. A. 10.0102 B. 10.0112 C. 10.1102 D. 11.0012 Practice Problem 2.51 We saw in Problem 2.46 that the Patriot missile software approximated 0.1 as x =0.000110011001100110011002. Suppose instead that they had used IEEE round- to-even mode to determine an approximation x� to 0.1 with 23 bits to the right of the binary point. A. What is the binary representation of x�? B. What is the approximate decimal value of x� − 0.1? C. How far off would the computed clock have been after 100 hours of opera- tion? D. How far off would the program’s prediction of the position of the Scud missile have been? Practice Problem 2.52 Consider the following two 7-bit floating-point representations based on the IEEE floating point format. Neither has a sign bit—they can only represent nonnegative numbers. 1. Format A There are k = 3 exponent bits. The exponent bias is 3. There are n = 4 fraction bits. 2. Format B There are k = 4 exponent bits. The exponent bias is 7. There are n = 3 fraction bits. Below, you are given some bit patterns in Format A, and your task is to convert them to the closest value in Format B. If necessary, you should apply the round-to- even rounding rule. In addition, give the values of numbers given by the Format A ) 向上舍入成 11.002(3),而 10.101002( 146 Chapter 2 Representing and Manipulating Information and 10.001102 (2 3 16 ) up to 10.012 (2 1 4 ), because these values are not halfway be- tween two possible values. We would round 10.111002 (2 7 8 ) up to 11.002 (3) and 10.101002 (2 5 8 ) down to 10.102 (2 1 2 ), since these values are halfway between two possible results, and we prefer to have the least significant bit equal to zero. Practice Problem 2.50 Show how the following binary fractional values would be rounded to the nearest half (1 bit to the right of the binary point), according to the round-to-even rule. In each case, show the numeric values, both before and after rounding. A. 10.0102 B. 10.0112 C. 10.1102 D. 11.0012 Practice Problem 2.51 We saw in Problem 2.46 that the Patriot missile software approximated 0.1 as x =0.000110011001100110011002. Suppose instead that they had used IEEE round- to-even mode to determine an approximation x� to 0.1 with 23 bits to the right of the binary point. A. What is the binary representation of x�? B. What is the approximate decimal value of x� − 0.1? C. How far off would the computed clock have been after 100 hours of opera- tion? D. How far off would the program’s prediction of the position of the Scud missile have been? Practice Problem 2.52 Consider the following two 7-bit floating-point representations based on the IEEE floating point format. Neither has a sign bit—they can only represent nonnegative numbers. 1. Format A There are k = 3 exponent bits. The exponent bias is 3. There are n = 4 fraction bits. 2. Format B There are k = 4 exponent bits. The exponent bias is 7. There are n = 3 fraction bits. Below, you are given some bit patterns in Format A, and your task is to convert them to the closest value in Format B. If necessary, you should apply the round-to- even rounding rule. In addition, give the values of numbers given by the Format A ) 向下舍入成 10.102( 146 Chapter 2 Representing and Manipulating Information and 10.001102 (2 3 16 ) up to 10.012 (2 1 4 ), because these values are not halfway be- tween two possible values. We would round 10.111002 (2 7 8 ) up to 11.002 (3) and 10.101002 (2 5 8 ) down to 10.102 (2 1 2 ), since these values are halfway between two possible results, and we prefer to have the least significant bit equal to zero. Practice Problem 2.50 Show how the following binary fractional values would be rounded to the nearest half (1 bit to the right of the binary point), according to the round-to-even rule. In each case, show the numeric values, both before and after rounding. A. 10.0102 B. 10.0112 C. 10.1102 D. 11.0012 Practice Problem 2.51 We saw in Problem 2.46 that the Patriot missile software approximated 0.1 as x =0.000110011001100110011002. Suppose instead that they had used IEEE round- to-even mode to determine an approximation x� to 0.1 with 23 bits to the right of the binary point. A. What is the binary representation of x�? B. What is the approximate decimal value of x� − 0.1? C. How far off would the computed clock have been after 100 hours of opera- tion? D. How far off would the program’s prediction of the position of the Scud missile have been? Practice Problem 2.52 Consider the following two 7-bit floating-point representations based on the IEEE floating point format. Neither has a sign bit—they can only represent nonnegative numbers. 1. Format A There are k = 3 exponent bits. The exponent bias is 3. There are n = 4 fraction bits. 2. Format B There are k = 4 exponent bits. The exponent bias is 7. There are n = 3 fraction bits. Below, you are given some bit patterns in Format A, and your task is to convert them to the closest value in Format B. If necessary, you should apply the round-to- even rounding rule. In addition, give the values of numbers given by the Format A ),因为这些值是两个可能值的中间值,并且我们倾向于使 最低有效位为零。 练习题 2.50 根据舍入到偶数规则,说明如何将下列二进制小数值舍入到最接近的二分之一(二进制 小数点右边 1 位)。对每种情况,给出舍入前后的数字值。 A. 10.0102 B. 10.0112 C. 10.1102 D. 11.0012 正文.indd 75 2010-10-19 14:18:52  76  第一部分 程序结构和执行  练习题 2.51 在练习题 2.46 中我们看到,爱国者导弹软件将 0.1 近似表示为 x = 0.000110011001100110011002。 假设使用 IEEE 舍入到偶数方式确定 0.1 的二进制小数点右边 23 位的近似表示 x'。 A. x' 的二进制表示是什么? B. x' - 0.1 的十进制表示的近似值是什么? C. 运行 100 小时后,计算时钟值会有多少偏差? D. 该程序对飞毛腿导弹位置的预测会有多少偏差? 练习题 2.52 考虑下列基于 IEEE 浮点格式的 7 位浮点表示。两个格式都没有符号位——它们只能表 示非负的数字。 1. 格式 A • 有 k = 3 个阶码位。阶码的偏置值是 3。 • 有 n = 4 个小数位。 2. 格式 B • 有 k = 4 个阶码位。阶码的偏置值是 7。 • 有 n = 3 个小数位。 下面给出了一些格式 A 表示的位模式,你的任务是将它们转换成格式 B 中最接近的值。如果有必要, 请使用舍入到偶数的原则。另外,给出由格式 A 和格式 B 表示的位模式对应的数字的值。给出整数 (例如 17)或者小数(例如 17/64)。 2.4.5 浮点运算 IEEE 标准指定了一个简单的规则,用来确定诸如加法和乘法这样的算术运算的结果。把浮 点值 x 和 y 看成实数,而某个运算⊙定义在实数上,计算将产生 Round (x ⊙ y),这是对实际运算 的精确结果进行舍入后的结果。在实际中,浮点单元的设计者使用一些聪明的小技巧来避免执行 这种精确的计算,因为计算只要精确到能够保证得到一个正确的舍入结果就可以了。当参数中有 一个是特殊值(如 -0、- ∞或 NaN)时,IEEE 标准定义了一些使之更合理的规则。例如,定义 1/-0 将产生 - ∞,而定义 1/+0 会产生 + ∞。 IEEE 标准中指定浮点运算行为方法的一个优势在于,它可以独立于任何具体的硬件或者软 件实现。因此,我们可以检查它的抽象数学属性,而不必考虑实际上它是如何实现的。 前面我们看到了整数(包括无符号和补码)加法形成了阿贝尔群。实数上的加法也形成了阿 贝尔群,但是我们必须考虑舍入对这些属性的影响。我们将 x + fy 定义为 Round(x+y)。这个运算 的定义针对 x 和 y 的所有取值,但是虽然 x 和 y 都是实数,由于溢出,该运算可能得到无穷值。 对于所有 x 和 y 的值,这个运算是可交换的,也就是说 x + fy = y + fx。另一方面,这个运算是不 可结合的。例如,使用单精度浮点,表达式 (3.14+le10)-le10 求值得到 0.0—因为舍入, 值 3.14 会丢失。另一方面,表达式 3.14+(le10-le10) 得到值 3.14。作为阿贝尔群,大多 数值的浮点加法都有逆元,也就是说 x + f-x = 0。无穷(因为 + ∞ - ∞ = NaN)和 NaN 是例外 情况,因为对于任何 x,都有 NaN + fx = NaN。 浮点加法不具有结合性,这是缺少的最重要的群属性。对于科学计算程序员和编译器编写者 来说,这具有重要的含义。例如,假设一个编译器给定了如下代码片段 : 正文.indd 76 2010-10-19 14:18:53 第 2 章 信息的表示和处理  77  x = a + b + c; y = b + c + d; 编译器可能试图产生下列代码来省去一个浮点加法 : t = b + c; x = a + t; y = t + d; 然而,对于 x 来说,这个计算可能会产生与原始值不同的值,因为它使用了加法运算的不同的 结合方式。在大多数应用中,这种差异小得无关紧要。不幸的是,编译器无法知道在效率和忠实 于原始程序的确切行为之间,使用者愿意做出什么样的选择。结果是,编译器倾向于保守,避免 任何对功能产生影响的优化,即使是很轻微的影响。 另一方面,浮点加法满足了单调性属性 :如果 a ≥ b,那么对于任何 a、b 以及 x 的值,除 了 NaN,都有 x + a ≥ x + b。无符号或补码加法不具有这个实数(和整数)加法的属性。 浮点乘法也遵循通常乘法所具有的许多属性。我们定义 x* fy 为 Round (x×y)。这个运算在 乘法中是封闭的(虽然可能产生无穷大或 NaN),它是可交换的,并且它的乘法单位元为 1.0。 另一方面,由于可能发生溢出,或者由于舍入而失去精度,它不具有可结合性。例如,在单 精度浮点情况下,表达式 (le20*le20)*le-20 的值为 + ∞,而 le20*(le20*le-20) 将 得出 le20。另外,浮点乘法在加法上不具备分配性。例如,在单精度浮点情况下,表达式 le20*(le20-le20) 的值为 0.0,而 le20*le20-le20*le20 会得出 NaN。 另一方面,对于任何 a、b 和 c,并且 a、b 和 c 都不等于 NaN,浮点乘法满足下列单调性 : a ≥ b 且 c ≥ 0 a * f c ≥ b* f c a ≥ b 且 c ≤ 0 a * f c ≤ b * f c 此外,我们还可以保证,只要 a ≠ NaN,就有 a * f a ≥ 0 。像我们先前所看到的,无符号或 补码的乘法没有这些单调性属性。 对于科学计算程序员和编译器编写者来说,缺乏结合性和分配性是很严重的问题。即使为 了在三维空间中确定两条线是否交叉而写代码这样看上去很简单的任务,也可能成为一个很大 的挑战。 2.4.6 C 语言中的浮点数 所有的 C 语言版本提供了两种不同的浮点数据类型 :float 和 double。在支持 IEEE 浮点 格式的机器上,这些数据类型就对应于单精度和双精度浮点。另外,这类机器使用向偶数舍入的 方式。不幸的是,因为 C 语言标准不要求机器使用 IEEE 浮点,所以没有标准的方法来改变舍入 方式或者得到诸如 -0、+ ∞、- ∞或者 NaN 之类的特殊值。大多数系统提供 include(‘.h’) 文件和读取这些特征的过程库,但是细节因为系统不同而不同。例如,当程序文件中出现下列句 子时,GNU 编译器 GCC 会定义程序常数 INFINITY(表示 + ∞)和 NAN(表示 NaN)。 # define _GNU_SOURCE 1 # include 较新版本的 C 语言,包括 ISO C99,包含第三种浮点数据类型 long double。对于许多机 器和编译器来说,这种数据类型等价于 double 数据类型。不过对于 Intel 兼容机来说,GCC 用 80 位“扩展精度”格式来实现这种数据类型,提供了比标准 64 位格式大得多的取值范围和精 度。家庭作业 2.85 研究了这种格式的属性。 练习题 2.53 完成下列宏定义,生成双精度值 + ∞、- ∞和 0。 #define POS_INFINITY 正文.indd 77 2010-10-19 14:18:53  78  第一部分 程序结构和执行  #define NEG_INFINITY #define NEG_ZERO 不能使用任何 include 文件(例如 math.h),但你能利用的是 :双精度能够表示的最大的有限数, 大约是 1.8×10308。 当在 int、float 和 double 格式之间进行强制类型转换时,程序改变数值和位模式的原 则如下(假设 int 是 32 位的): • 从 int 转换成 float,数字不会溢出,但是可能被舍入。 • 从 int 或 float 转换成 double,因为 double 有更大的范围(也就是可表示值的范 围),也有更高的精度(也就是有效位数),所以能够保留精确的数值。 • 从 double 转换成 float,因为范围要小一些,所以值可能溢出成为 + ∞或 - ∞。另外, 由于精确度较小,它还可能被舍入。 • 从 float 或 者 double 转 换 成 int, 值 将 会 向 零 舍 入。 例 如,1.999 将 被 转 换 成 1, 而 -1.999 将被转换成 -1。进一步来说,值可能会溢出。C 语言标准没有对这种情况指 定固定的结果。与 Intel 兼容的微处理器指定位模式 [10...00](字长为 w 时的 TMinw)为 整数不确定(integer indefinite)值。一个从浮点数到整数的转换,如果不能为该浮点数 找到一个合理的整数近似值,就会产生这样一个值。因此,表达式 (int)+1e10 会得 到 -21483648,即从一个正值变成了一个负值。 网络旁注 DATA :IA32-FP :Intel IA32 的浮点运算 在下一章,我们将深入研究 Intel IA32 处理器,这种处理器大量地应用于今天的个人计算机 中。这里,我们重点突出这种机器的一个特性,即用 GCC 编译的时候,它能够严重影响程序对 浮点数运算的行为。 像大多数其他处理器一样,IA32 处理器有特别的存储器元素,称为寄存器,当计算或者使 用浮点数时,用来保存浮点值。IA32 非同一般的属性是,浮点寄存器使用一种特殊的 80 位的扩 展精度格式。与存储器中保存值所使用的普通 32 位单精度和 64 位双精度格式相比,它提供了更 大的表示范围和更高的精度。(参见家庭作业 2.85。)所有的单精度和双精度数在从存储器加载到 浮点寄存器中时,都会转换成这种格式。运算总是以扩展精度格式进行的。当数字存储在存储器 中时,它们就从扩展精度转换成单精度或者双精度格式。 对于程序员而言,把所有寄存器数据扩展成 80 位,并把所有存储器数据收缩成更小的格式 的做法,会产生一些不太好的结果。这意味着从寄存器存储一个值到存储器中,然后再把它取回 到寄存器中,由于舍入、下溢或者上溢,可能会改变它的值。对于 C 语言程序员来说,这种存 入和取出并不总是可见的,因而会导致一些非常异常的结果。 较新版本的 Intel 处理器,包括 IA32 和较新的 64 位机器,对单精度和双精度浮点运算提 供了直接的硬件支持。随着新硬件以及基于较新的浮点指令产生代码的新编译器的使用,以前 IA32 做法导致的这些奇怪特性会逐渐消失。 Ariane 5—浮点溢出的高昂代价 将大的浮点数转换成整数是一种常见的程序错误来源。1996 年 6 月 4 日,Ariane 5 火箭初次 航行,一个错误便产生了灾难性的后果。发射后仅仅 37 秒,火箭偏离了它的飞行路径,解体并 且爆炸。火箭上载有价值 5 亿美元的通信卫星。 后来的调查 [69,39] 显示,控制惯性导航系统的计算机向控制引擎喷嘴的计算机发送了一 个无效数据。它没有发送飞行控制信息,而是送出了一个诊断位模式,表明将一个 64 位浮点数 转换成 16 位有符号整数时,产生了溢出。 正文.indd 78 2010-10-19 14:18:53 第 2 章 信息的表示和处理  79  溢出的值测量的是火箭的水平速率,这比早先的 Ariane 4 火箭所能达到的速度高出了 5 倍。 设计 Ariane 4 火箭软件的时候,他们小心地分析了这些数字值,并且确定水平速率决不会超出一 个 16 位数的表示范围。不幸的是,他们在 Ariane 5 火箭的系统中简单地重用了这一部分,而没 有检查它所基于的假设。 练习题 2.54 假定变量 x、f 和 d 的类型分别是 int、float 和 double。除了 f 和 d 都不能等于 + ∞、- ∞或者 NaN 之外,它们的值是任意的。下面每个 C 表达式,证明它总是为真(也就是求值为 1),或者给出一个使表达式不为真的值(也就是求值为 0)。 A. x==(int)(double)x B. x==(int)(float)x C. d==(double)(float)d D. f==(float)(double)f E. f==-(-f) F. 1.0/2==1/2.0 G. d*d>=0.0 H. (f+d)-f==d 2.5 小结 计算机将信息按位编码,通常组织成字节序列。用不同的编码方式表示整数、实数和字符 串。不同的计算机模型在编码数字和多字节数据中的字节排序时使用不同的约定。 C 语言的设计可以包容多种不同字长和数字编码的实现。虽然高端机器逐渐开始使用 64 位 字长,但是目前大多数机器仍使用 32 位字长。大多数机器对整数使用补码编码,而对浮点数使 用 IEEE 浮点编码。在位级上理解这些编码,并且理解算术运算的数学特性,对于想使编写的程 序能在全部数值范围上正确运算的程序员来说,是很重要的。 在相同长度的无符号和有符号整数之间进行强制类型转换时,大多数 C 语言实现遵循的原 则是底层的位模式不变。在补码机器上,对于一个 w 位的值,这种行为是由函数 T2Uw 和 U2Tw 正文.indd 79 2010-10-19 14:18:54  80  第一部分 程序结构和执行  来描述的。C 语言隐式的强制类型转换会出现许多程序员无法预计的结果,常常导致程序错误。 由于编码的长度有限,与传统整数和实数运算相比,计算机运算具有完全不同的属性。当超 出表示范围时,有限长度能够引起数值溢出。当浮点数非常接近于 0.0,从而转换成零时,也会 下溢。 和大多数其他程序语言一样,C 语言实现的有限整数运算和真实的整数运算相比,有一些特 殊的属性。例如,由于溢出,表达式 x*x 能够得出负数。但是,无符号数和补码的运算都满足 整数运算的许多其他属性,包括结合律、交换律和分配律。这就允许编译器做很多的优化。例 如,用 (x<<3)-x 取代表达式 7*x 时,我们就利用了结合律、交换律和分配律的属性,还利用 了移位和乘以 2 的幂之间的关系。 我们已经看到了几种使用位级运算和算术运算组合的聪明方法。例如,使用补码运算, ~x+1 等价于 -x 。另外一个例子,假设我们想要一个形如 [0,…,0,1,…,1] 的位模式,由 w - k 个 0 后面紧跟着 k 个 1 组成。这些位模式有助于掩码运算。这种模式能够通过 C 表达式 (1< 0x12AB5678 replace_byte(0x12345678, 0xAB, 0) --> 0x123456AB Bit-level integer coding rules In several of the following problems, we will artificially restrict what programming constructs you can use to help you gain a better understanding of the bit-level, logic, and arithmetic operations of C. In answering these problems, your code must follow these rules: . Assumptions Integers are represented in two’s-complement form. Right shifts of signed data are performed arithmetically. Data type int is w bits long. For some of the problems, you will be given a specific value for w, but otherwise your code should work as long as w is a multiple of 8. You can use the expression sizeof(int)<<3 to compute w. . Forbidden Conditionals (if or ?:), loops, switch statements, function calls, and macro invocations. Division, modulus, and multiplication. Relative comparison operators (<, >, <=, and >=). Casting, either explicit or implicit. . Allowed operations All bit-level and logic operations. Left and right shifts, but only with shift amounts between 0 and w − 1. Addition and subtraction. Equality (==) and inequality (!=) tests. (Some of the problems do not allow these.) Integer constants INT_MIN and INT_MAX. Even with these rules, you should try to make your code readable by choosing descriptive variable names and using comments to describe the logic behind your solutions. As an example, the following code extracts the most significant byte from integer argument x: 位级整数编码规则 在接下来的作业中,我们特意限制了你能使用的编程结构,来帮你更好地理解 C 语言的位级、逻辑 和算术运算。在回答这些问题时,你的代码必须遵守下面这些规则 : • 假设 ■ 整数用补码形式表示。 ■ 有符号数的右移是算术右移。 ■ 数据类型 int 是 w 位长的。对于某些题目,会给定 w 的值,但是在其他情况下,只要 w 是 8 的 整数倍,你的代码就应该能工作。你可以用表达式 sizeof(int)<<3 来计算 w。 • 禁止使用 ■ 条件语句(if 或者 ?:)、循环、分支语句、函数调用和宏调用。 ■ 除法、模运算和乘法。 ■ 相对比较运算符(<、>、<= 和 >=)。 ■ 强制类型转换,无论是显式的还是隐式的。 • 允许的运算 ■ 所有的位级和逻辑运算。 ■ 左移和右移,但是位移的数量只能在 0 和 w - 1 之间。 ■ 加法和减法。 ■ 相等(==)和不相等(!=)测试。(在有些题目中,也不允许这些运算。) ■ 整型常数 INT_MIN 和 INT_MAX。 即使有这些条件的限制,你仍然可以选择描述性的变量名,并且使用注释来描述你的解决方案的逻 辑,尽量提高代码的可读性。例如,下面这段代码从整数参数 x 中抽取出最高有效字节 : Homework Problems 155 /* Get most significant byte from x */ int get_msb(int x) { /* Shift by w-8 */ int shift_val = (sizeof(int)-1)<<3; /* Arithmetic shift */ int xright=x>>shift_val; /* Zero all but LSB */ return xright & 0xFF; } 2.61 �� Write C expressions that evaluate to 1 when the following conditions are true and to 0 when they are false. Assume x is of type int. A. Any bit of x equals 1. B. Any bit of x equals 0. C. Any bit in the most significant byte of x equals 1. D. Any bit in the least significant byte of x equals 0. Your code should follow the bit-level integer coding rules (page 154), with the additional restriction that you may not use equality (==) or inequality (!=) tests. 2.62 ��� Write a function int_shifts_are_logical() that yields 1 when run on a machine that uses logical right shifts for data type int and yields 0 otherwise. Your code should work on a machine with any word size. Test your code on several machines. 2.63 ��� Fill in code for the following C functions. Function sra performs an arithmetic right shift using a logical right shift (given by value xsrl), followed by other operations not including right shifts or division. Function srl performs a logical right shift using an arithmetic right shift (given by value xsra), followed by other operations not including right shifts or division. You may use the computation 8*sizeof(int) to determine w, the number of bits in data type int. The shift amount k can range from 0 to w − 1. int sra(int x, int k) { /* Perform shift logically */ int xsrl = (unsigned) x >> k; ... } 2.61 写一个 C 表达式,在下列描述的条件下产生 1,而在其他情况下得到 0。假设 x 是 int 类型。 A. x 的任何位都等于 1。 B. x 的任何位都等于 0。 C. x 的最高有效字节中的位都等于 1。 D. x 的最低有效字节中的位都等于 0。 代码应该遵循位级整数编码规则,另外还有一个限制,你不能使用相等(==)和不相等(!=)测试。 2.62  编写一个函数 int_shifts_are_logical(),在对 int 类型的数使用算术右移的机器上运行时 , 这个函数生成 1,而其他情况下生成 0。你的代码应该可以运行在任何字长的机器上。在几种机器上 ** ** * ** 正文.indd 81 2010-10-19 14:18:55  82  第一部分 程序结构和执行  测试你的代码。 2.63  将下面的 C 函数代码补充完整。函数 srl 用算术右移(由值 xsra 给出)来完成逻辑右移,后面的 其他操作不包括右移或者除法。函数 sra 用逻辑右移(由值 xsrl 给出)来完成算术右移,后面的 其他操作不包括右移或者除法。可以通过计算 8*sizeof(int) 来确定数据类型 int 中的位数 w。 位移量 k 的取值范围为 0 ~ w - 1。 Homework Problems 155 /* Get most significant byte from x */ int get_msb(int x) { /* Shift by w-8 */ int shift_val = (sizeof(int)-1)<<3; /* Arithmetic shift */ int xright=x>>shift_val; /* Zero all but LSB */ return xright & 0xFF; } 2.61 �� Write C expressions that evaluate to 1 when the following conditions are true and to 0 when they are false. Assume x is of type int. A. Any bit of x equals 1. B. Any bit of x equals 0. C. Any bit in the most significant byte of x equals 1. D. Any bit in the least significant byte of x equals 0. Your code should follow the bit-level integer coding rules (page 154), with the additional restriction that you may not use equality (==) or inequality (!=) tests. 2.62 ��� Write a function int_shifts_are_logical() that yields 1 when run on a machine that uses logical right shifts for data type int and yields 0 otherwise. Your code should work on a machine with any word size. Test your code on several machines. 2.63 ��� Fill in code for the following C functions. Function sra performs an arithmetic right shift using a logical right shift (given by value xsrl), followed by other operations not including right shifts or division. Function srl performs a logical right shift using an arithmetic right shift (given by value xsra), followed by other operations not including right shifts or division. You may use the computation 8*sizeof(int) to determine w, the number of bits in data type int. The shift amount k can range from 0 to w − 1. int sra(int x, int k) { /* Perform shift logically */ int xsrl = (unsigned) x >> k; ... } 156 Chapter 2 Representing and Manipulating Information unsigned srl(unsigned x, int k) { /* Perform shift arithmetically */ unsigned xsra = (int) x >> k; ... } 2.64 � Write code to implement the following function: /* Return 1 when any even bit of x equals 1; 0 otherwise. Assume w=32 */ int any_even_one(unsigned x); Your function should follow the bit-level integer coding rules, (page 154), except that you may assume that data type int has w = 32 bits. 2.65 ���� Write code to implement the following function: /* Return 1 when x contains an even number of 1s; 0 otherwise. Assume w=32 */ int even_ones(unsigned x); Your function should follow the bit-level integer coding rules, (page 154), except that you may assume that data type int has w = 32 bits. Your code should contain a total of at most 12 arithmetic, bit-wise, and logical operations. 2.66 ��� Write code to implement the following function: /* * Generate mask indicating leftmost 1 in x. Assume w=32. * For example 0xFF00 -> 0x8000, and 0x6600 --> 0x4000. *Ifx=0,then return 0. */ int leftmost_one(unsigned x); Your function should follow the bit-level integer coding rules (page 154), except that you may assume that data type int has w = 32 bits. Your code should contain a total of at most 15 arithmetic, bit-wise, and logical operations. Hint: First transform x into a bit vector of the form [0 ...011 ...1]. 2.67 �� You are given the task of writing a procedure int_size_is_32() that yields 1 when run on a machine for which an int is 32 bits, and yields 0 otherwise. You are not allowed to use the sizeof operator. Here is a first attempt: 2.64 写出代码实现如下函数 : 156 Chapter 2 Representing and Manipulating Information unsigned srl(unsigned x, int k) { /* Perform shift arithmetically */ unsigned xsra = (int) x >> k; ... } 2.64 � Write code to implement the following function: /* Return 1 when any even bit of x equals 1; 0 otherwise. Assume w=32 */ int any_even_one(unsigned x); Your function should follow the bit-level integer coding rules, (page 154), except that you may assume that data type int has w = 32 bits. 2.65 ���� Write code to implement the following function: /* Return 1 when x contains an even number of 1s; 0 otherwise. Assume w=32 */ int even_ones(unsigned x); Your function should follow the bit-level integer coding rules, (page 154), except that you may assume that data type int has w = 32 bits. Your code should contain a total of at most 12 arithmetic, bit-wise, and logical operations. 2.66 ��� Write code to implement the following function: /* * Generate mask indicating leftmost 1 in x. Assume w=32. * For example 0xFF00 -> 0x8000, and 0x6600 --> 0x4000. *Ifx=0,then return 0. */ int leftmost_one(unsigned x); Your function should follow the bit-level integer coding rules (page 154), except that you may assume that data type int has w = 32 bits. Your code should contain a total of at most 15 arithmetic, bit-wise, and logical operations. Hint: First transform x into a bit vector of the form [0 ...011 ...1]. 2.67 �� You are given the task of writing a procedure int_size_is_32() that yields 1 when run on a machine for which an int is 32 bits, and yields 0 otherwise. You are not allowed to use the sizeof operator. Here is a first attempt: 函数应该遵循位级整数编码规则,不过你可以假设数据类型 int 有 w = 32 位。 2.65 写出代码实现如下函数 : 156 Chapter 2 Representing and Manipulating Information unsigned srl(unsigned x, int k) { /* Perform shift arithmetically */ unsigned xsra = (int) x >> k; ... } 2.64 � Write code to implement the following function: /* Return 1 when any even bit of x equals 1; 0 otherwise. Assume w=32 */ int any_even_one(unsigned x); Your function should follow the bit-level integer coding rules, (page 154), except that you may assume that data type int has w = 32 bits. 2.65 ���� Write code to implement the following function: /* Return 1 when x contains an even number of 1s; 0 otherwise. Assume w=32 */ int even_ones(unsigned x); Your function should follow the bit-level integer coding rules, (page 154), except that you may assume that data type int has w = 32 bits. Your code should contain a total of at most 12 arithmetic, bit-wise, and logical operations. 2.66 ��� Write code to implement the following function: /* * Generate mask indicating leftmost 1 in x. Assume w=32. * For example 0xFF00 -> 0x8000, and 0x6600 --> 0x4000. *Ifx=0,then return 0. */ int leftmost_one(unsigned x); Your function should follow the bit-level integer coding rules (page 154), except that you may assume that data type int has w = 32 bits. Your code should contain a total of at most 15 arithmetic, bit-wise, and logical operations. Hint: First transform x into a bit vector of the form [0 ...011 ...1]. 2.67 �� You are given the task of writing a procedure int_size_is_32() that yields 1 when run on a machine for which an int is 32 bits, and yields 0 otherwise. You are not allowed to use the sizeof operator. Here is a first attempt: 函 数应该遵循位级整数编码规则,不过你可以假设数据类型 int 有 w = 32 位。 你的代码最多只能包含 12 个算术运算、位运算和逻辑运算。 2.66 写出代码实现如下函数 : 156 Chapter 2 Representing and Manipulating Information unsigned srl(unsigned x, int k) { /* Perform shift arithmetically */ unsigned xsra = (int) x >> k; ... } 2.64 � Write code to implement the following function: /* Return 1 when any even bit of x equals 1; 0 otherwise. Assume w=32 */ int any_even_one(unsigned x); Your function should follow the bit-level integer coding rules, (page 154), except that you may assume that data type int has w = 32 bits. 2.65 ���� Write code to implement the following function: /* Return 1 when x contains an even number of 1s; 0 otherwise. Assume w=32 */ int even_ones(unsigned x); Your function should follow the bit-level integer coding rules, (page 154), except that you may assume that data type int has w = 32 bits. Your code should contain a total of at most 12 arithmetic, bit-wise, and logical operations. 2.66 ��� Write code to implement the following function: /* * Generate mask indicating leftmost 1 in x. Assume w=32. * For example 0xFF00 -> 0x8000, and 0x6600 --> 0x4000. *Ifx=0,then return 0. */ int leftmost_one(unsigned x); Your function should follow the bit-level integer coding rules (page 154), except that you may assume that data type int has w = 32 bits. Your code should contain a total of at most 15 arithmetic, bit-wise, and logical operations. Hint: First transform x into a bit vector of the form [0 ...011 ...1]. 2.67 �� You are given the task of writing a procedure int_size_is_32() that yields 1 when run on a machine for which an int is 32 bits, and yields 0 otherwise. You are not allowed to use the sizeof operator. Here is a first attempt: 函数应该遵循位级整数编码规则,不过你可以假设数据类型 int 有 w = 32 位。 你的代码最多只能包含 15 个算术运算、位运算和逻辑运算。 提示 :先将 x 转换成形如 [0...011...1] 的位向量。 2.67  给你一个任务,编写一个过程 int_size_is_32(),当在一个 int 是 32 位的机器上运行时,该程 序产生 1,而其他情况则产生 0。不允许使用 sizeof 运算符。下面是开始时的尝试 :Homework Problems 157 1 /* The following code does not run properly on some machines */ 2 int bad_int_size_is_32() { 3 /* Set most significant bit (msb) of 32-bit machine */ 4 int set_msb=1<<31; 5 /* Shift past msb of 32-bit word */ 6 int beyond_msb=1<<32; 7 8 /* set_msb is nonzero when word size >= 32 9 beyond_msb is zero when word size <= 32 */ 10 return set_msb && !beyond_msb; 11 } When compiled and run on a 32-bit SUN SPARC, however, this procedure returns 0. The following compiler message gives us an indication of the problem: warning: left shift count >= width of type A. In what way does our code fail to comply with the C standard? B. Modify the code to run properly on any machine for which data type int is at least 32 bits. C. Modify the code to run properly on any machine for which data type int is at least 16 bits. 2.68 �� Write code for a function with the following prototype: /* * Clear all but least signficant n bits of x * Examples: x = 0x78ABCDEF,n=8-->0xEF,n=16-->0xCDEF * Assume 1 <= n <= w */ int lower_bits(int x, int n); Your function should follow the bit-level integer coding rules (page 154). Be careful of the case n = w. 2.69 ��� Write code for a function with the following prototype: /* * Do rotating right shift. Assume 0 <=n 0x81234567, n=20 -> 0x45678123 */ unsigned rotate_right(unsigned x, int n); Your function should follow the bit-level integer coding rules (page 154). Be careful of the case n = 0. *** * **** *** ** 正文.indd 82 2010-10-19 14:18:56 第 2 章 信息的表示和处理  83  Homework Problems 157 1 /* The following code does not run properly on some machines */ 2 int bad_int_size_is_32() { 3 /* Set most significant bit (msb) of 32-bit machine */ 4 int set_msb=1<<31; 5 /* Shift past msb of 32-bit word */ 6 int beyond_msb=1<<32; 7 8 /* set_msb is nonzero when word size >= 32 9 beyond_msb is zero when word size <= 32 */ 10 return set_msb && !beyond_msb; 11 } When compiled and run on a 32-bit SUN SPARC, however, this procedure returns 0. The following compiler message gives us an indication of the problem: warning: left shift count >= width of type A. In what way does our code fail to comply with the C standard? B. Modify the code to run properly on any machine for which data type int is at least 32 bits. C. Modify the code to run properly on any machine for which data type int is at least 16 bits. 2.68 �� Write code for a function with the following prototype: /* * Clear all but least signficant n bits of x * Examples: x = 0x78ABCDEF,n=8-->0xEF,n=16-->0xCDEF * Assume 1 <= n <= w */ int lower_bits(int x, int n); Your function should follow the bit-level integer coding rules (page 154). Be careful of the case n = w. 2.69 ��� Write code for a function with the following prototype: /* * Do rotating right shift. Assume 0 <=n 0x81234567, n=20 -> 0x45678123 */ unsigned rotate_right(unsigned x, int n); Your function should follow the bit-level integer coding rules (page 154). Be careful of the case n = 0. 当在 SUN SPARC 这样的 32 位机器上编译并运行时,这个过程返回的却是 0。下面的编译器信息给 了我们一个问题的指示 : warning: left shift count >= width of type A. 我们的代码在哪个方面没有遵守 C 语言标准? B. 修改代码,使得它在 int 至少为 32 位的任何机器上都能正确地运行。 C. 修改代码,使得它在 int 至少为 16 位的任何机器上都能正确地运行。 2.68 写出具有如下原型的函数的代码 : Homework Problems 157 1 /* The following code does not run properly on some machines */ 2 int bad_int_size_is_32() { 3 /* Set most significant bit (msb) of 32-bit machine */ 4 int set_msb=1<<31; 5 /* Shift past msb of 32-bit word */ 6 int beyond_msb=1<<32; 7 8 /* set_msb is nonzero when word size >= 32 9 beyond_msb is zero when word size <= 32 */ 10 return set_msb && !beyond_msb; 11 } When compiled and run on a 32-bit SUN SPARC, however, this procedure returns 0. The following compiler message gives us an indication of the problem: warning: left shift count >= width of type A. In what way does our code fail to comply with the C standard? B. Modify the code to run properly on any machine for which data type int is at least 32 bits. C. Modify the code to run properly on any machine for which data type int is at least 16 bits. 2.68 �� Write code for a function with the following prototype: /* * Clear all but least signficant n bits of x * Examples: x = 0x78ABCDEF,n=8-->0xEF,n=16-->0xCDEF * Assume 1 <= n <= w */ int lower_bits(int x, int n); Your function should follow the bit-level integer coding rules (page 154). Be careful of the case n = w. 2.69 ��� Write code for a function with the following prototype: /* * Do rotating right shift. Assume 0 <=n 0x81234567, n=20 -> 0x45678123 */ unsigned rotate_right(unsigned x, int n); Your function should follow the bit-level integer coding rules (page 154). Be careful of the case n = 0. 函数应该遵循位级整数编码规则。要注意 n = w 的情况。 2.69 写出具有如下原型的函数的代码 : Homework Problems 157 1 /* The following code does not run properly on some machines */ 2 int bad_int_size_is_32() { 3 /* Set most significant bit (msb) of 32-bit machine */ 4 int set_msb=1<<31; 5 /* Shift past msb of 32-bit word */ 6 int beyond_msb=1<<32; 7 8 /* set_msb is nonzero when word size >= 32 9 beyond_msb is zero when word size <= 32 */ 10 return set_msb && !beyond_msb; 11 } When compiled and run on a 32-bit SUN SPARC, however, this procedure returns 0. The following compiler message gives us an indication of the problem: warning: left shift count >= width of type A. In what way does our code fail to comply with the C standard? B. Modify the code to run properly on any machine for which data type int is at least 32 bits. C. Modify the code to run properly on any machine for which data type int is at least 16 bits. 2.68 �� Write code for a function with the following prototype: /* * Clear all but least signficant n bits of x * Examples: x = 0x78ABCDEF,n=8-->0xEF,n=16-->0xCDEF * Assume 1 <= n <= w */ int lower_bits(int x, int n); Your function should follow the bit-level integer coding rules (page 154). Be careful of the case n = w. 2.69 ��� Write code for a function with the following prototype: /* * Do rotating right shift. Assume 0 <=n 0x81234567, n=20 -> 0x45678123 */ unsigned rotate_right(unsigned x, int n); Your function should follow the bit-level integer coding rules (page 154). Be careful of the case n = 0.函数应该遵循位级整数编码规则。要注意 n = 0 的情况。 2.70 写出具有如下原型的函数的代码 : 158 Chapter 2 Representing and Manipulating Information 2.70 �� Write code for the function with the following prototype: /* * Return 1 when x can be represented as an n-bit, 2’s complement * number; 0 otherwise * Assume 1 <= n <= w */ int fits_bits(int x, int n); Your function should follow the bit-level integer coding rules (page 154). 2.71 � You just started working for a company that is implementing a set of procedures to operate on a data structure where 4 signed bytes are packed into a 32-bit unsigned. Bytes within the word are numbered from 0 (least significant) to 3 (most significant). You have been assigned the task of implementing a function for a machine using two’s-complement arithmetic and arithmetic right shifts with the following prototype: /* Declaration of data type where 4 bytes are packed into an unsigned */ typedef unsigned packed_t; /* Extract byte from word. Return as signed integer */ int xbyte(packed_t word, int bytenum); That is, the function will extract the designated byte and sign extend it to be a 32-bit int. Your predecessor (who was fired for incompetence) wrote the following code: /* Failed attempt at xbyte */ int xbyte(packed_t word, int bytenum) { return (word >> (bytenum << 3)) & 0xFF; } A. What is wrong with this code? B. Give a correct implementation of the function that uses only left and right shifts, along with one subtraction. 2.72 �� You are given the task of writing a function that will copy an integer val into a buffer buf, but it should do so only if enough space is available in the buffer. Here is the code you write: /* Copy integer into buffer if space is available */ /* WARNING: The following code is buggy */ 函数应该遵循位级整数编码规则。 2.71  你刚刚开始在一家公司工作,他们要实现一组过程来操作一个数据结构,要将 4 个有符号字节封装 成一个 32 位 unsigned。一个字中的字节从 0(最低有效字节)编号到 3(最高有效字节)。分配给 你的任务是 :为一个使用补码运算和算术右移的机器编写一个具有如下原型的函数 : 158 Chapter 2 Representing and Manipulating Information 2.70 �� Write code for the function with the following prototype: /* * Return 1 when x can be represented as an n-bit, 2’s complement * number; 0 otherwise * Assume 1 <= n <= w */ int fits_bits(int x, int n); Your function should follow the bit-level integer coding rules (page 154). 2.71 � You just started working for a company that is implementing a set of procedures to operate on a data structure where 4 signed bytes are packed into a 32-bit unsigned. Bytes within the word are numbered from 0 (least significant) to 3 (most significant). You have been assigned the task of implementing a function for a machine using two’s-complement arithmetic and arithmetic right shifts with the following prototype: /* Declaration of data type where 4 bytes are packed into an unsigned */ typedef unsigned packed_t; /* Extract byte from word. Return as signed integer */ int xbyte(packed_t word, int bytenum); That is, the function will extract the designated byte and sign extend it to be a 32-bit int. Your predecessor (who was fired for incompetence) wrote the following code: /* Failed attempt at xbyte */ int xbyte(packed_t word, int bytenum) { return (word >> (bytenum << 3)) & 0xFF; } A. What is wrong with this code? B. Give a correct implementation of the function that uses only left and right shifts, along with one subtraction. 2.72 �� You are given the task of writing a function that will copy an integer val into a buffer buf, but it should do so only if enough space is available in the buffer. Here is the code you write: /* Copy integer into buffer if space is available */ /* WARNING: The following code is buggy */ 也就是说,函数会抽取出指定的字节,再把它符号扩展为一个 32 位 int。 你的前任(因为水平不够高而被解雇了)编写了下面的代码 : ** *** ** * 正文.indd 83 2010-10-19 14:18:56  84  第一部分 程序结构和执行  158 Chapter 2 Representing and Manipulating Information 2.70 �� Write code for the function with the following prototype: /* * Return 1 when x can be represented as an n-bit, 2’s complement * number; 0 otherwise * Assume 1 <= n <= w */ int fits_bits(int x, int n); Your function should follow the bit-level integer coding rules (page 154). 2.71 � You just started working for a company that is implementing a set of procedures to operate on a data structure where 4 signed bytes are packed into a 32-bit unsigned. Bytes within the word are numbered from 0 (least significant) to 3 (most significant). You have been assigned the task of implementing a function for a machine using two’s-complement arithmetic and arithmetic right shifts with the following prototype: /* Declaration of data type where 4 bytes are packed into an unsigned */ typedef unsigned packed_t; /* Extract byte from word. Return as signed integer */ int xbyte(packed_t word, int bytenum); That is, the function will extract the designated byte and sign extend it to be a 32-bit int. Your predecessor (who was fired for incompetence) wrote the following code: /* Failed attempt at xbyte */ int xbyte(packed_t word, int bytenum) { return (word >> (bytenum << 3)) & 0xFF; } A. What is wrong with this code? B. Give a correct implementation of the function that uses only left and right shifts, along with one subtraction. 2.72 �� You are given the task of writing a function that will copy an integer val into a buffer buf, but it should do so only if enough space is available in the buffer. Here is the code you write: /* Copy integer into buffer if space is available */ /* WARNING: The following code is buggy */ A. 这段代码错在哪里? B. 给出函数的正确实现,只能使用左右移位和一个减法。 2.72  给你一个任务,写一个函数,将整数 val 复制到缓冲区 buf 中,但是只有当缓冲区中有足够可用 的空间时,才执行复制。 你写的代码如下 : 158 Chapter 2 Representing and Manipulating Information 2.70 �� Write code for the function with the following prototype: /* * Return 1 when x can be represented as an n-bit, 2’s complement * number; 0 otherwise * Assume 1 <= n <= w */ int fits_bits(int x, int n); Your function should follow the bit-level integer coding rules (page 154). 2.71 � You just started working for a company that is implementing a set of procedures to operate on a data structure where 4 signed bytes are packed into a 32-bit unsigned. Bytes within the word are numbered from 0 (least significant) to 3 (most significant). You have been assigned the task of implementing a function for a machine using two’s-complement arithmetic and arithmetic right shifts with the following prototype: /* Declaration of data type where 4 bytes are packed into an unsigned */ typedef unsigned packed_t; /* Extract byte from word. Return as signed integer */ int xbyte(packed_t word, int bytenum); That is, the function will extract the designated byte and sign extend it to be a 32-bit int. Your predecessor (who was fired for incompetence) wrote the following code: /* Failed attempt at xbyte */ int xbyte(packed_t word, int bytenum) { return (word >> (bytenum << 3)) & 0xFF; } A. What is wrong with this code? B. Give a correct implementation of the function that uses only left and right shifts, along with one subtraction. 2.72 �� You are given the task of writing a function that will copy an integer val into a buffer buf, but it should do so only if enough space is available in the buffer. Here is the code you write: /* Copy integer into buffer if space is available */ /* WARNING: The following code is buggy */ Homework Problems 159 void copy_int(int val, void *buf, int maxbytes) { if (maxbytes-sizeof(val) >= 0) memcpy(buf, (void *) &val, sizeof(val)); } This code makes use of the library function memcpy. Although its use is a bit artificial here, where we simply want to copy an int, it illustrates an approach commonly used to copy larger data structures. You carefully test the code and discover that it always copies the value to the buffer, even when maxbytes is too small. A. Explain why the conditional test in the code always succeeds. Hint: The sizeof operator returns a value of type size_t. B. Show how you can rewrite the conditional test to make it work properly. 2.73 �� Write code for a function with the following prototype: /* Addition that saturates to TMin or TMax */ int saturating_add(int x, int y); Instead of overflowing the way normal two’s-complement addition does, sat- urating addition returns TMax when there would be positive overflow, and TMin when there would be negative overflow. Saturating arithmetic is commonly used in programs that perform digital signal processing. Your function should follow the bit-level integer coding rules (page 154). 2.74 �� Write a function with the following prototype /* Determine whether subtracting arguments will cause overflow */ int tsub_ovf(int x, int y); This function should return 1 if computing x-y causes overflow. 2.75 ��� Suppose we want to compute the complete 2w-bit representation of x . y, where both x and y are in two’s-complement form on a machine for which data type int is w bits. The low-order w bits of the product can be computed with the expression x*y, so we only require a procedure with prototype int signed_high_prod(int x, int y); that computes the high-order w bits of x . y for two’s-complement variables. We have access to a library function with prototype: unsigned unsigned_high_prod(unsigned x, unsigned y); that computes the high-order w bits of x . y for the case where x and y are unsigned. Write code calling this procedure to implement the function for two’s complement arguments. Justify the correctness of your solution. 这段代码使用了库函数 memcpy。虽然在这里用这个函数有点刻意,因为我们只是想复制一个 int, 但是它说明了一种复制较大数据结构的常见方法。 你仔细地测试了这段代码后发现,哪怕 maxbytes 很小的时候,它也能把值复制到缓冲区中。 A. 解释为什么代码中的条件测试总是成功。提示 :sizeof 运算符返回类型为 size_t 的值。 B. 你该如何重写这个条件测试,使之工作正确。 2.73 写出具有如下原型的函数的代码 : Homework Problems 159 void copy_int(int val, void *buf, int maxbytes) { if (maxbytes-sizeof(val) >= 0) memcpy(buf, (void *) &val, sizeof(val)); } This code makes use of the library function memcpy. Although its use is a bit artificial here, where we simply want to copy an int, it illustrates an approach commonly used to copy larger data structures. You carefully test the code and discover that it always copies the value to the buffer, even when maxbytes is too small. A. Explain why the conditional test in the code always succeeds. Hint: The sizeof operator returns a value of type size_t. B. Show how you can rewrite the conditional test to make it work properly. 2.73 �� Write code for a function with the following prototype: /* Addition that saturates to TMin or TMax */ int saturating_add(int x, int y); Instead of overflowing the way normal two’s-complement addition does, sat- urating addition returns TMax when there would be positive overflow, and TMin when there would be negative overflow. Saturating arithmetic is commonly used in programs that perform digital signal processing. Your function should follow the bit-level integer coding rules (page 154). 2.74 �� Write a function with the following prototype /* Determine whether subtracting arguments will cause overflow */ int tsub_ovf(int x, int y); This function should return 1 if computing x-y causes overflow. 2.75 ��� Suppose we want to compute the complete 2w-bit representation of x . y, where both x and y are in two’s-complement form on a machine for which data type int is w bits. The low-order w bits of the product can be computed with the expression x*y, so we only require a procedure with prototype int signed_high_prod(int x, int y); that computes the high-order w bits of x . y for two’s-complement variables. We have access to a library function with prototype: unsigned unsigned_high_prod(unsigned x, unsigned y); that computes the high-order w bits of x . y for the case where x and y are unsigned. Write code calling this procedure to implement the function for two’s complement arguments. Justify the correctness of your solution. 同正常的补码加法溢出的方式不同,当正溢出时,saturating_add 返回 TMax,负溢出时,返回 TMin。这种运算常常用在执行数字信号处理的程序中。 你的函数应该遵循位级整数编码规则。 2.74 写出具有如下原型的函数的代码 : Homework Problems 159 void copy_int(int val, void *buf, int maxbytes) { if (maxbytes-sizeof(val) >= 0) memcpy(buf, (void *) &val, sizeof(val)); } This code makes use of the library function memcpy. Although its use is a bit artificial here, where we simply want to copy an int, it illustrates an approach commonly used to copy larger data structures. You carefully test the code and discover that it always copies the value to the buffer, even when maxbytes is too small. A. Explain why the conditional test in the code always succeeds. Hint: The sizeof operator returns a value of type size_t. B. Show how you can rewrite the conditional test to make it work properly. 2.73 �� Write code for a function with the following prototype: /* Addition that saturates to TMin or TMax */ int saturating_add(int x, int y); Instead of overflowing the way normal two’s-complement addition does, sat- urating addition returns TMax when there would be positive overflow, and TMin when there would be negative overflow. Saturating arithmetic is commonly used in programs that perform digital signal processing. Your function should follow the bit-level integer coding rules (page 154). 2.74 �� Write a function with the following prototype /* Determine whether subtracting arguments will cause overflow */ int tsub_ovf(int x, int y); This function should return 1 if computing x-y causes overflow. 2.75 ��� Suppose we want to compute the complete 2w-bit representation of x . y, where both x and y are in two’s-complement form on a machine for which data type int is w bits. The low-order w bits of the product can be computed with the expression x*y, so we only require a procedure with prototype int signed_high_prod(int x, int y); that computes the high-order w bits of x . y for two’s-complement variables. We have access to a library function with prototype: unsigned unsigned_high_prod(unsigned x, unsigned y); that computes the high-order w bits of x . y for the case where x and y are unsigned. Write code calling this procedure to implement the function for two’s complement arguments. Justify the correctness of your solution. 如果计算 x-y 导致溢出,这个函数就返回 1。 2.75  假设我们想要计算 x · y 的完整的 2w 位表示,其中,x 和 y 都是无符号数,并且运行在数据类型 unsigned 是 w 位的机器上。乘积的低 w 位能够用表达式 x*y 计算,所以,我们只需要一个具有 下列原型的函数 : int signed_high_prod(int x, int y); 这个函数计算无符号变量 x · y 的高 w 位。 我们使用一个具有下面原型的库函数 : unsigned unsigned_high_prod(unsigned x, unsigned y); 它计算在 x 和 y 采用补码形式的情况下,x · y 的高 w 位。编写代码调用这个过程,以实现用无符号 数为参数的函数。验证你的解答的正确性。 提示 :看看等式(2-18)的推导中,有符号乘积 x · y 和无符号乘积 x' · y' 之间的关系。 2.76  假设我们有一个任务 :生成一段代码,将整数变量 x 乘以不同的常数因子 K。为了提高效率,我们 想只使用 +、- 和 << 运算。对于下列 K 的值,写出执行乘法运算的 C 表达式,每个表达式中最多 ** ** ** *** ** 正文.indd 84 2010-10-19 14:18:57 第 2 章 信息的表示和处理  85  使用 3 个运算。 A. K = 5 : B. K= 9 : C. K = 30 : D. K = -56 : 2.77 写出具有如下原型的函数的代码 : 160 Chapter 2 Representing and Manipulating Information Hint: Look at the relationship between the signed product x . y and the unsigned product x� . y� in the derivation of Equation 2.18. 2.76 �� Suppose we are given the task of generating code to multiply integer variable x by various different constant factors K. To be efficient, we want to use only the operations +, -, and <<. For the following values of K, write C expressions to perform the multiplication using at most three operations per expression. A. K = 5: B. K = 9: C. K = 30: D. K =−56: 2.77 �� Write code for a function with the following prototype: /* Divide by power of two. Assume 0 <=ky) == (-x<-y) B.((x+y)<<5) + x-y == 31*y+33*x C. ~x+~y == ~(x+y) D.(int) (ux-uy) == -(y-x) E. ((x >> 1) << 1) <= x 2.82 �� Consider numbers having a binary representation consisting of an infinite string of the form 0.yyyyyy..., where y is a k-bit sequence. For example, the binary representation of 1 3 is 0.01010101 ...(y = 01), while the representation of 1 5 is 0.001100110011 ...(y = 0011). A. Let Y = B2Uk(y), that is, the number having binary representation y. Give a formula in terms of Y and k for the value represented by the infinite string. Hint: Consider the effect of shifting the binary point k positions to the right. B. What is the numeric value of the string for the following values of y? (a) 001 (b) 1001 (c) 000111 2.83 � Fill in the return value for the following procedure, which tests whether its first argument is greater than or equal to its second. Assume the function f2u returns an unsigned 32-bit number having the same bit representation as its floating-point argument. You can assume that neither argument is NaN. The two flavors of zero, +0 and −0, are considered equal. int float_ge(float x, float y) { unsigned ux = f2u(x); unsigned uy = f2u(y); 对于下列每个 C 表达式,你要指出表达式是否总是为 1。如果它总是为 1,那么请描述其中的数学 原理。否则,列举一个使它为 0 的参数示例。 Homework Problems 161 We generate arbitrary values x and y, and convert them to unsigned values as follows: /* Create some arbitrary values */ int x = random(); int y = random(); /* Convert to unsigned */ unsigned ux = (unsigned) x; unsigned uy = (unsigned) y; For each of the following C expressions, you are to indicate whether or not the expression always yields 1. If it always yields 1, describe the underlying mathematical principles. Otherwise, give an example of arguments that make it yield 0. A. (x>y) == (-x<-y) B.((x+y)<<5) + x-y == 31*y+33*x C. ~x+~y == ~(x+y) D.(int) (ux-uy) == -(y-x) E. ((x >> 1) << 1) <= x 2.82 �� Consider numbers having a binary representation consisting of an infinite string of the form 0.yyyyyy..., where y is a k-bit sequence. For example, the binary representation of 1 3 is 0.01010101 ...(y = 01), while the representation of 1 5 is 0.001100110011 ...(y = 0011). A. Let Y = B2Uk(y), that is, the number having binary representation y. Give a formula in terms of Y and k for the value represented by the infinite string. Hint: Consider the effect of shifting the binary point k positions to the right. B. What is the numeric value of the string for the following values of y? (a) 001 (b) 1001 (c) 000111 2.83 � Fill in the return value for the following procedure, which tests whether its first argument is greater than or equal to its second. Assume the function f2u returns an unsigned 32-bit number having the same bit representation as its floating-point argument. You can assume that neither argument is NaN. The two flavors of zero, +0 and −0, are considered equal. int float_ge(float x, float y) { unsigned ux = f2u(x); unsigned uy = f2u(y); 2.82  一些数字的二进制表示是由形如 0.y y y y y y…的无穷串组成的,其中 y 是一个 k 位的序列。例如, Homework Problems 161 We generate arbitrary values x and y, and convert them to unsigned values as follows: /* Create some arbitrary values */ int x = random(); int y = random(); /* Convert to unsigned */ unsigned ux = (unsigned) x; unsigned uy = (unsigned) y; For each of the following C expressions, you are to indicate whether or not the expression always yields 1. If it always yields 1, describe the underlying mathematical principles. Otherwise, give an example of arguments that make it yield 0. A. (x>y) == (-x<-y) B.((x+y)<<5) + x-y == 31*y+33*x C. ~x+~y == ~(x+y) D.(int) (ux-uy) == -(y-x) E. ((x >> 1) << 1) <= x 2.82 �� Consider numbers having a binary representation consisting of an infinite string of the form 0.yyyyyy..., where y is a k-bit sequence. For example, the binary representation of 1 3 is 0.01010101 ...(y = 01), while the representation of 1 5 is 0.001100110011 ...(y = 0011). A. Let Y = B2Uk(y), that is, the number having binary representation y. Give a formula in terms of Y and k for the value represented by the infinite string. Hint: Consider the effect of shifting the binary point k positions to the right. B. What is the numeric value of the string for the following values of y? (a) 001 (b) 1001 (c) 000111 2.83 � Fill in the return value for the following procedure, which tests whether its first argument is greater than or equal to its second. Assume the function f2u returns an unsigned 32-bit number having the same bit representation as its floating-point argument. You can assume that neither argument is NaN. The two flavors of zero, +0 and −0, are considered equal. int float_ge(float x, float y) { unsigned ux = f2u(x); unsigned uy = f2u(y); 的二进制表示是 0.01010101…( y = 01),而 Homework Problems 161 We generate arbitrary values x and y, and convert them to unsigned values as follows: /* Create some arbitrary values */ int x = random(); int y = random(); /* Convert to unsigned */ unsigned ux = (unsigned) x; unsigned uy = (unsigned) y; For each of the following C expressions, you are to indicate whether or not the expression always yields 1. If it always yields 1, describe the underlying mathematical principles. Otherwise, give an example of arguments that make it yield 0. A. (x>y) == (-x<-y) B.((x+y)<<5) + x-y == 31*y+33*x C. ~x+~y == ~(x+y) D.(int) (ux-uy) == -(y-x) E. ((x >> 1) << 1) <= x 2.82 �� Consider numbers having a binary representation consisting of an infinite string of the form 0.yyyyyy..., where y is a k-bit sequence. For example, the binary representation of 1 3 is 0.01010101 ...(y = 01), while the representation of 1 5 is 0.001100110011 ...(y = 0011). A. Let Y = B2Uk(y), that is, the number having binary representation y. Give a formula in terms of Y and k for the value represented by the infinite string. Hint: Consider the effect of shifting the binary point k positions to the right. B. What is the numeric value of the string for the following values of y? (a) 001 (b) 1001 (c) 000111 2.83 � Fill in the return value for the following procedure, which tests whether its first argument is greater than or equal to its second. Assume the function f2u returns an unsigned 32-bit number having the same bit representation as its floating-point argument. You can assume that neither argument is NaN. The two flavors of zero, +0 and −0, are considered equal. int float_ge(float x, float y) { unsigned ux = f2u(x); unsigned uy = f2u(y); 的二进制表示是 0.001100110011…( y = 0011)。 A. 设 Y = B2Uk ( y ),也就是说,这个数具有二进制表示 y。给出一个由 Y 和 k 组成的公式表示这个无 穷串的值。提示 :请考虑将二进制小数点右移 k 位的结果。 B. 对于下列 y 的值,串的数值是多少? (a)001 (b)1001 (c)000111 2.83   填写下列程序的返回值,这个程序是测试它的第一个参数是否大于或者等于第二个参数。假定函数 ** ** ** ** ** * * 正文.indd 85 2010-10-19 14:18:59  86  第一部分 程序结构和执行  f2u 返回一个无符号 32 位数字,其位表示与它的浮点参数相同。你可以假设两个参数都不是 NaN。 两种 0,+0 和 -0 被认为是相等的。 162 Chapter 2 Representing and Manipulating Information /* Get the sign bits */ unsigned sx = ux >> 31; unsigned sy = uy >> 31; /* Give an expression using only ux, uy, sx, and sy */ return ; } 2.84 � Given a floating-point format with a k-bit exponent and an n-bit fraction, write formulas for the exponent E, significand M, the fraction f , and the value V for the quantities that follow. In addition, describe the bit representation. A. The number 5.0 B. The largest odd integer that can be represented exactly C. The reciprocal of the smallest positive normalized value 2.85 � Intel-compatible processors also support an “extended precision” floating-point format with an 80-bit word divided into a sign bit, k = 15 exponent bits, a single integer bit, and n = 63 fraction bits. The integer bit is an explicit copy of the implied bit in the IEEE floating-point representation. That is, it equals 1 for normalized values and 0 for denormalized values. Fill in the following table giving the approximate values of some “interesting” numbers in this format: Extended precision Description Value Decimal Smallest positive denormalized Smallest positive normalized Largest normalized 2.86 � Consider a 16-bit floating-point representation based on the IEEE floating-point format, with one sign bit, seven exponent bits (k = 7), and eight fraction bits (n = 8). The exponent bias is 27−1 − 1 = 63. Fill in the table that follows for each of the numbers given, with the following instructions for each column: Hex: The four hexadecimal digits describing the encoded form. M: The value of the significand. This should be a number of the form x or x y , where x is an integer, and y is an integral power of 2. Examples include 0, 67 64 , and 1 256 . E: The integer value of the exponent. V : The numeric value represented. Use the notation x or x × 2z, where x and z are integers. Homework Problems 161 We generate arbitrary values x and y, and convert them to unsigned values as follows: /* Create some arbitrary values */ int x = random(); int y = random(); /* Convert to unsigned */ unsigned ux = (unsigned) x; unsigned uy = (unsigned) y; For each of the following C expressions, you are to indicate whether or not the expression always yields 1. If it always yields 1, describe the underlying mathematical principles. Otherwise, give an example of arguments that make it yield 0. A. (x>y) == (-x<-y) B. ((x+y)<<5) + x-y == 31*y+33*x C. ~x+~y == ~(x+y) D. (int) (ux-uy) == -(y-x) E. ((x >> 1) << 1) <= x 2.82 �� Consider numbers having a binary representation consisting of an infinite string of the form 0.yyyyyy..., where y is a k-bit sequence. For example, the binary representation of 1 3 is 0.01010101 ...(y = 01), while the representation of 1 5 is 0.001100110011 ...(y = 0011). A. Let Y = B2Uk(y), that is, the number having binary representation y. Give a formula in terms of Y and k for the value represented by the infinite string. Hint: Consider the effect of shifting the binary point k positions to the right. B. What is the numeric value of the string for the following values of y? (a) 001 (b) 1001 (c) 000111 2.83 � Fill in the return value for the following procedure, which tests whether its first argument is greater than or equal to its second. Assume the function f2u returns an unsigned 32-bit number having the same bit representation as its floating-point argument. You can assume that neither argument is NaN. The two flavors of zero, +0 and −0, are considered equal. int float_ge(float x, float y) { unsigned ux = f2u(x); unsigned uy = f2u(y); 2.84  给定一个浮点格式,有 k 位指数和 n 位小数,对于下列数,写出阶码 E、尾数 M、小数 f 和值 V 的 公式。另外,请描述其位表示。 A. 数 5.0。 B. 能够被准确描述的最大奇整数。 C. 最小的规格化数的倒数。 2.85  与 Intel 兼容的处理器也支持“扩展精度”浮点形式,这种格式具有 80 位字长,被分成 1 个符号位、 k = 15 个阶码位、1 个单独的整数位和 n = 63 个小数位。整数位是 IEEE 浮点表示中隐含位的显式副 本。也就是说,对于规格化的值它等于 1,对于非规格化的值它等于 0。填写下表,给出用这种格式 表示的一些“有趣的”数字的近似值。 描 述 扩展精度 值 十进制 最小的正非规格化数 最小的正规格化数 最大的规格化数 2.86  考虑一个基于 IEEE 浮点格式的 16 位浮点表示,它具有 1 个符号位、7 个阶码位(k = 7)和 8 个小 数位(n = 8)。阶码偏置量是 27-1-1 = 63。 对于每个给定的数,填写下表,其中,每一列具有如下指示说明 : Hex :描述编码形式的 4 个十六进制数字。 M :尾数的值。这应该是一个形如 x 或 162 Chapter 2 Representing and Manipulating Information /* Get the sign bits */ unsigned sx = ux >> 31; unsigned sy = uy >> 31; /* Give an expression using only ux, uy, sx, and sy */ return ; } 2.84 � Given a floating-point format with a k-bit exponent and an n-bit fraction, write formulas for the exponent E, significand M, the fraction f , and the value V for the quantities that follow. In addition, describe the bit representation. A. The number 5.0 B. The largest odd integer that can be represented exactly C. The reciprocal of the smallest positive normalized value 2.85 � Intel-compatible processors also support an “extended precision” floating-point format with an 80-bit word divided into a sign bit, k = 15 exponent bits, a single integer bit, and n = 63 fraction bits. The integer bit is an explicit copy of the implied bit in the IEEE floating-point representation. That is, it equals 1 for normalized values and 0 for denormalized values. Fill in the following table giving the approximate values of some “interesting” numbers in this format: Extended precision Description Value Decimal Smallest positive denormalized Smallest positive normalized Largest normalized 2.86 � Consider a 16-bit floating-point representation based on the IEEE floating-point format, with one sign bit, seven exponent bits (k = 7), and eight fraction bits (n = 8). The exponent bias is 27−1 − 1 = 63. Fill in the table that follows for each of the numbers given, with the following instructions for each column: Hex: The four hexadecimal digits describing the encoded form. M: The value of the significand. This should be a number of the form x or x y , where x is an integer, and y is an integral power of 2. Examples include 0, 67 64 , and 1 256 . E: The integer value of the exponent. V : The numeric value represented. Use the notation x or x × 2z, where x and z are integers. 的数,其中 x 是一个整数,而 y 是 2 的整数幂。例如 :0、 162 Chapter 2 Representing and Manipulating Information /* Get the sign bits */ unsigned sx = ux >> 31; unsigned sy = uy >> 31; /* Give an expression using only ux, uy, sx, and sy */ return ; } 2.84 � Given a floating-point format with a k-bit exponent and an n-bit fraction, write formulas for the exponent E, significand M, the fraction f , and the value V for the quantities that follow. In addition, describe the bit representation. A. The number 5.0 B. The largest odd integer that can be represented exactly C. The reciprocal of the smallest positive normalized value 2.85 � Intel-compatible processors also support an “extended precision” floating-point format with an 80-bit word divided into a sign bit, k = 15 exponent bits, a single integer bit, and n = 63 fraction bits. The integer bit is an explicit copy of the implied bit in the IEEE floating-point representation. That is, it equals 1 for normalized values and 0 for denormalized values. Fill in the following table giving the approximate values of some “interesting” numbers in this format: Extended precision Description Value Decimal Smallest positive denormalized Smallest positive normalized Largest normalized 2.86 � Consider a 16-bit floating-point representation based on the IEEE floating-point format, with one sign bit, seven exponent bits (k = 7), and eight fraction bits (n = 8). The exponent bias is 27−1 − 1 = 63. Fill in the table that follows for each of the numbers given, with the following instructions for each column: Hex: The four hexadecimal digits describing the encoded form. M: The value of the significand. This should be a number of the form x or x y , where x is an integer, and y is an integral power of 2. Examples include 0, 67 64 , and 1 256 . E: The integer value of the exponent. V : The numeric value represented. Use the notation x or x × 2z, where x and z are integers. 和 162 Chapter 2 Representing and Manipulating Information /* Get the sign bits */ unsigned sx = ux >> 31; unsigned sy = uy >> 31; /* Give an expression using only ux, uy, sx, and sy */ return ; } 2.84 � Given a floating-point format with a k-bit exponent and an n-bit fraction, write formulas for the exponent E, significand M, the fraction f , and the value V for the quantities that follow. In addition, describe the bit representation. A. The number 5.0 B. The largest odd integer that can be represented exactly C. The reciprocal of the smallest positive normalized value 2.85 � Intel-compatible processors also support an “extended precision” floating-point format with an 80-bit word divided into a sign bit, k = 15 exponent bits, a single integer bit, and n = 63 fraction bits. The integer bit is an explicit copy of the implied bit in the IEEE floating-point representation. That is, it equals 1 for normalized values and 0 for denormalized values. Fill in the following table giving the approximate values of some “interesting” numbers in this format: Extended precision Description Value Decimal Smallest positive denormalized Smallest positive normalized Largest normalized 2.86 � Consider a 16-bit floating-point representation based on the IEEE floating-point format, with one sign bit, seven exponent bits (k = 7), and eight fraction bits (n = 8). The exponent bias is 27−1 − 1 = 63. Fill in the table that follows for each of the numbers given, with the following instructions for each column: Hex: The four hexadecimal digits describing the encoded form. M: The value of the significand. This should be a number of the form x or x y , where x is an integer, and y is an integral power of 2. Examples include 0, 67 64 , and 1 256 . E: The integer value of the exponent. V : The numeric value represented. Use the notation x or x × 2z, where x and z are integers. 。 E :阶码的整数值。 V :所表示的数字值。使用 x 或者 x × 2z 表示,其中 x 和 z 都是整数。 举一个例子,为了表示数 Homework Problems 163 As an example, to represent the number 7 2 , we would have s = 0, M = 7 4 , and E = 1. Our number would therefore have an exponent field of 0x40 (decimal value 63 + 1 = 64) and a significand field 0xC0 (binary 110000002), giving a hex representation 40C0. You need not fill in entries marked “—”. Description Hex MEV −0 — Smallest value > 1 256 — Largest denormalized −∞ ——— Number with hex representation 3AA0 — 2.87 �� Consider the following two 9-bit floating-point representations based on the IEEE floating-point format. 1. Format A There is one sign bit. There are k = 5 exponent bits. The exponent bias is 15. There are n = 3 fraction bits. 2. Format B There is one sign bit. There are k = 4 exponent bits. The exponent bias is 7. There are n = 4 fraction bits. Below, you are given some bit patterns in Format A, and your task is to convert them to the closest value in Format B. If rounding is necessary, you should round toward +∞. In addition, give the values of numbers given by the Format A and Format B bit patterns. Give these as whole numbers (e.g., 17) or as fractions (e.g., 17/64 or 17/26). Format A Format B Bits Value Bits Value 1 01110 001 −9 16 1 0110 0010 −9 16 0 10110 101 1 00111 110 0 00000 101 1 11011 000 0 11000 100 2.88 � We are running programs on a machine where values of type int have a 32- bit two’s-complement representation. Values of type float use the 32-bit IEEE format, and values of type double use the 64-bit IEEE format. ,我们有 s = 0,M = Homework Problems 163 As an example, to represent the number 7 2 , we would have s = 0, M = 7 4 , and E = 1. Our number would therefore have an exponent field of 0x40 (decimal value 63 + 1 = 64) and a significand field 0xC0 (binary 110000002), giving a hex representation 40C0. You need not fill in entries marked “—”. Description Hex MEV −0 — Smallest value > 1 256 — Largest denormalized −∞ ——— Number with hex representation 3AA0 — 2.87 �� Consider the following two 9-bit floating-point representations based on the IEEE floating-point format. 1. Format A There is one sign bit. There are k = 5 exponent bits. The exponent bias is 15. There are n = 3 fraction bits. 2. Format B There is one sign bit. There are k = 4 exponent bits. The exponent bias is 7. There are n = 4 fraction bits. Below, you are given some bit patterns in Format A, and your task is to convert them to the closest value in Format B. If rounding is necessary, you should round toward +∞. In addition, give the values of numbers given by the Format A and Format B bit patterns. Give these as whole numbers (e.g., 17) or as fractions (e.g., 17/64 or 17/26). Format A Format B Bits Value Bits Value 1 01110 001 −9 16 1 0110 0010 −9 16 0 10110 101 1 00111 110 0 00000 101 1 11011 000 0 11000 100 2.88 � We are running programs on a machine where values of type int have a 32- bit two’s-complement representation. Values of type float use the 32-bit IEEE format, and values of type double use the 64-bit IEEE format. 和 E = 1。因此这个数的阶码字段为 0x40(十进制 值 63+1=64),尾数字段为 0xC0(二进制 110000002),得到一个十六进制的表示 40C0。 标记为“— ”的条目不用填写。 描 述 Hex M E V -0 — 最小的值 >1 256 — 最大的非规格化数 -∞ — — — 十六进制表示为 3AA0 的数 — 2.87 考虑下面两个基于 IEEE 浮点格式的 9 位浮点表示。 * * * ** 正文.indd 86 2010-10-19 14:19:01 第 2 章 信息的表示和处理  87  1. 格式 A ■ 有一个符号位。 ■ 有 k = 5 个阶码位。阶码偏置量是 15。 ■ 有 n = 3 个小数位。 2. 格式 B ■ 有一个符号位。 ■ 有 k = 4 个阶码位。阶码偏置量是 7。 ■ 有 n = 4 个小数位。 下面给出了一些格式 A 表示的位模式,你的任务是把它们转换成最接近的格式 B 表示的值。如果 需要舍入,你要向 + ∞舍入。另外,给出用格式 A 和格式 B 表示的位模式对应的值。要么是整数 (例如,17),要么是小数(例如,17/64 或 17/26)。 2.88  我们在一个 int 类型为 32 位补码表示的机器上运行程序。float 类型的值使用 32 位 IEEE 格式, 而 double 类型的值使用 64 位 IEEE 格式。 我们产生随机整数 x、y 和 z,并且把它们转换成 double 类型的值 : 164 Chapter 2 Representing and Manipulating Information We generate arbitrary integer values x, y, and z, and convert them to values of type double as follows: /* Create some arbitrary values */ int x = random(); int y = random(); int z = random(); /* Convert to double */ double dx = (double) x; double dy = (double) y; double dz = (double) z; For each of the following C expressions, you are to indicate whether or not the expression always yields 1. If it always yields 1, describe the underlying mathematical principles. Otherwise, give an example of arguments that make it yield 0. Note that you cannot use an IA32 machine running gcc to test your answers, since it would use the 80-bit extended-precision representation for both float and double. A. (double)(float) x == dx B. dx + dy == (double) (x+y) C. dx+dy+dz==dz+dy+dx D. dx*dy*dz==dz*dy*dx E. dx/dx==dy/dy 2.89 � You have been assigned the task of writing a C function to compute a floating- point representation of 2x. You decide that the best way to do this is to directly construct the IEEE single-precision representation of the result. When x is too small, your routine will return 0.0. When x is too large, it will return +∞. Fill in the blank portions of the code that follows to compute the correct result. Assume the function u2f returns a floating-point value having an identical bit representation as its unsigned argument. float fpwr2(int x) { /* Result exponent and fraction */ unsigned exp, frac; unsigned u; if (x < ){ /* Too small. Return 0.0 */ exp = ; frac = ; } else if (x < ){ 对于下列的每个 C 表达式,你要指出表达式是否总是为 1。如果它总是为 1,描述其中的数学原理。 否则,列举出使它为 0 的参数的例子。请注意,不能使用 IA32 机器运行 GCC 来测试你的答案,因 为对于 float 和 double,它使用的都是 80 位的扩展精度表示。 164 Chapter 2 Representing and Manipulating Information We generate arbitrary integer values x, y, and z, and convert them to values of type double as follows: /* Create some arbitrary values */ int x = random(); int y = random(); int z = random(); /* Convert to double */ double dx = (double) x; double dy = (double) y; double dz = (double) z; For each of the following C expressions, you are to indicate whether or not the expression always yields 1. If it always yields 1, describe the underlying mathematical principles. Otherwise, give an example of arguments that make it yield 0. Note that you cannot use an IA32 machine running gcc to test your answers, since it would use the 80-bit extended-precision representation for both float and double. A. (double)(float) x == dx B. dx + dy == (double) (x+y) C. dx+dy+dz==dz+dy+dx D. dx*dy*dz==dz*dy*dx E. dx/dx==dy/dy 2.89 � You have been assigned the task of writing a C function to compute a floating- point representation of 2x. You decide that the best way to do this is to directly construct the IEEE single-precision representation of the result. When x is too small, your routine will return 0.0. When x is too large, it will return +∞. Fill in the blank portions of the code that follows to compute the correct result. Assume the function u2f returns a floating-point value having an identical bit representation as its unsigned argument. float fpwr2(int x) { /* Result exponent and fraction */ unsigned exp, frac; unsigned u; if (x < ){ /* Too small. Return 0.0 */ exp = ; frac = ; } else if (x < ){ 2.89  分配给你一个任务,编写一个 C 函数来计算 2x 的浮点表示。你意识到完成这个任务的最好方法 是直接创建结果的 IEEE 单精度表示。当 x 太小时,你的程序将返回 0.0。当 x 太大时,它会返回 + ∞。填写下列代码的空白部分,以计算出正确的结果。假设函数 u2f 返回的浮点值与它的无符号 参数有相同的位表示。 * * 正文.indd 87 2010-10-19 14:19:01  88  第一部分 程序结构和执行  164 Chapter 2 Representing and Manipulating Information We generate arbitrary integer values x, y, and z, and convert them to values of type double as follows: /* Create some arbitrary values */ int x = random(); int y = random(); int z = random(); /* Convert to double */ double dx = (double) x; double dy = (double) y; double dz = (double) z; For each of the following C expressions, you are to indicate whether or not the expression always yields 1. If it always yields 1, describe the underlying mathematical principles. Otherwise, give an example of arguments that make it yield 0. Note that you cannot use an IA32 machine running gcc to test your answers, since it would use the 80-bit extended-precision representation for both float and double. A. (double)(float) x == dx B. dx + dy == (double) (x+y) C. dx+dy+dz==dz+dy+dx D. dx*dy*dz==dz*dy*dx E. dx/dx==dy/dy 2.89 � You have been assigned the task of writing a C function to compute a floating- point representation of 2x. You decide that the best way to do this is to directly construct the IEEE single-precision representation of the result. When x is too small, your routine will return 0.0. When x is too large, it will return +∞. Fill in the blank portions of the code that follows to compute the correct result. Assume the function u2f returns a floating-point value having an identical bit representation as its unsigned argument. float fpwr2(int x) { /* Result exponent and fraction */ unsigned exp, frac; unsigned u; if (x < ){ /* Too small. Return 0.0 */ exp = ; frac = ; } else if (x < ){ Homework Problems 165 /* Denormalized result */ exp = ; frac = ; } else if (x < ){ /* Normalized result. */ exp = ; frac = ; } else { /* Too big. Return +oo */ exp = ; frac = ; } /* Pack exp and frac into 32 bits */ u = exp << 23 | frac; /* Return as float */ return u2f(u); } 2.90 � Around 250 B.C., the Greek mathematician Archimedes proved that 223 71 <π<22 7 . Had he had access to a computer and the standard library , he would have been able to determine that the single-precision floating-point approximation of π has the hexadecimal representation 0x40490FDB. Of course, all of these are just approximations, since π is not rational. A. What is the fractional binary number denoted by this floating-point value? B. What is the fractional binary representation of 22 7 ? Hint: See Problem 2.82. C. At what bit position (relative to the binary point) do these two approxima- tions to π diverge? Bit-level floating-point coding rules In the following problems, you will write code to implement floating-point func- tions, operating directly on bit-level representations of floating-point numbers. Your code should exactly replicate the conventions for IEEE floating-point oper- ations, including using round-to-even mode when rounding is required. Toward this end, we define data type float_bits to be equivalent to un- signed: /* Access bit-level representation floating-point number */ typedef unsigned float_bits; Rather than using data type float in your code, you will use float_bits. You may use both int and unsigned data types, including unsigned and integer constants and operations. You may not use any unions, structs, or arrays. Most 2.90  大约在公元前 250 年,希腊数学家阿基米德证明了 Homework Problems 165 /* Denormalized result */ exp = ; frac = ; } else if (x < ){ /* Normalized result. */ exp = ; frac = ; } else { /* Too big. Return +oo */ exp = ; frac = ; } /* Pack exp and frac into 32 bits */ u = exp << 23 | frac; /* Return as float */ return u2f(u); } 2.90 � Around 250 B.C., the Greek mathematician Archimedes proved that 223 71 <π< 22 7 . Had he had access to a computer and the standard library , he would have been able to determine that the single-precision floating-point approximation of π has the hexadecimal representation 0x40490FDB. Of course, all of these are just approximations, since π is not rational. A. What is the fractional binary number denoted by this floating-point value? B. What is the fractional binary representation of 22 7 ? Hint: See Problem 2.82. C. At what bit position (relative to the binary point) do these two approxima- tions to π diverge? Bit-level floating-point coding rules In the following problems, you will write code to implement floating-point func- tions, operating directly on bit-level representations of floating-point numbers. Your code should exactly replicate the conventions for IEEE floating-point oper- ations, including using round-to-even mode when rounding is required. Toward this end, we define data type float_bits to be equivalent to un- signed: /* Access bit-level representation floating-point number */ typedef unsigned float_bits; Rather than using data type float in your code, you will use float_bits. You may use both int and unsigned data types, including unsigned and integer constants and operations. You may not use any unions, structs, or arrays. Most π Homework Problems 165 /* Denormalized result */ exp = ; frac = ; } else if (x < ){ /* Normalized result. */ exp = ; frac = ; } else { /* Too big. Return +oo */ exp = ; frac = ; } /* Pack exp and frac into 32 bits */ u = exp << 23 | frac; /* Return as float */ return u2f(u); } 2.90 � Around 250 B.C., the Greek mathematician Archimedes proved that 223 71 <π< 22 7 . Had he had access to a computer and the standard library , he would have been able to determine that the single-precision floating-point approximation of π has the hexadecimal representation 0x40490FDB. Of course, all of these are just approximations, since π is not rational. A. What is the fractional binary number denoted by this floating-point value? B. What is the fractional binary representation of 22 7 ? Hint: See Problem 2.82. C. At what bit position (relative to the binary point) do these two approxima- tions to π diverge? Bit-level floating-point coding rules In the following problems, you will write code to implement floating-point func- tions, operating directly on bit-level representations of floating-point numbers. Your code should exactly replicate the conventions for IEEE floating-point oper- ations, including using round-to-even mode when rounding is required. Toward this end, we define data type float_bits to be equivalent to un- signed: /* Access bit-level representation floating-point number */ typedef unsigned float_bits; Rather than using data type float in your code, you will use float_bits. You may use both int and unsigned data types, including unsigned and integer constants and operations. You may not use any unions, structs, or arrays. Most 。如果当时有一台计算机和标准库 ,他就能够确定 π 的单精度浮点近似值的十六进制表示为 0x40490FDB。当然,所有的 这些都只是近似值,因为 π 不是有理数。 A. 这个浮点值表示的二进制小数是多少? B. Homework Problems 165 /* Denormalized result */ exp = ; frac = ; } else if (x < ){ /* Normalized result. */ exp = ; frac = ; } else { /* Too big. Return +oo */ exp = ; frac = ; } /* Pack exp and frac into 32 bits */ u = exp << 23 | frac; /* Return as float */ return u2f(u); } 2.90 � Around 250 B.C., the Greek mathematician Archimedes proved that 223 71 <π< 22 7 . Had he had access to a computer and the standard library , he would have been able to determine that the single-precision floating-point approximation of π has the hexadecimal representation 0x40490FDB. Of course, all of these are just approximations, since π is not rational. A. What is the fractional binary number denoted by this floating-point value? B. What is the fractional binary representation of 22 7 ? Hint: See Problem 2.82. C. At what bit position (relative to the binary point) do these two approxima- tions to π diverge? Bit-level floating-point coding rules In the following problems, you will write code to implement floating-point func- tions, operating directly on bit-level representations of floating-point numbers. Your code should exactly replicate the conventions for IEEE floating-point oper- ations, including using round-to-even mode when rounding is required. Toward this end, we define data type float_bits to be equivalent to un- signed: /* Access bit-level representation floating-point number */ typedef unsigned float_bits; Rather than using data type float in your code, you will use float_bits. You may use both int and unsigned data types, including unsigned and integer constants and operations. You may not use any unions, structs, or arrays. Most 的二进制小数表示是什么?提示 :参见家庭作业 2.82。 C. 这两个 Homework Problems 165 /* Denormalized result */ exp = ; frac = ; } else if (x < ){ /* Normalized result. */ exp = ; frac = ; } else { /* Too big. Return +oo */ exp = ; frac = ; } /* Pack exp and frac into 32 bits */ u = exp << 23 | frac; /* Return as float */ return u2f(u); } 2.90 � Around 250 B.C., the Greek mathematician Archimedes proved that 223 71 <π< 22 7 . Had he had access to a computer and the standard library , he would have been able to determine that the single-precision floating-point approximation of π has the hexadecimal representation 0x40490FDB. Of course, all of these are just approximations, since π is not rational. A. What is the fractional binary number denoted by this floating-point value? B. What is the fractional binary representation of 22 7 ? Hint: See Problem 2.82. C. At what bit position (relative to the binary point) do these two approxima- tions to π diverge? Bit-level floating-point coding rules In the following problems, you will write code to implement floating-point func- tions, operating directly on bit-level representations of floating-point numbers. Your code should exactly replicate the conventions for IEEE floating-point oper- ations, including using round-to-even mode when rounding is required. Toward this end, we define data type float_bits to be equivalent to un- signed: /* Access bit-level representation floating-point number */ typedef unsigned float_bits; Rather than using data type float in your code, you will use float_bits. You may use both int and unsigned data types, including unsigned and integer constants and operations. You may not use any unions, structs, or arrays. Most 的近似值从哪一位(相对于二进制小数点)开始不同的? 位级浮点编码规则 在接下来的题目中,你要写的代码要实现浮点函数在浮点数的位级表示上直接运算。你的代码应该 完全遵循 IEEE 浮点运算的规则,包括当需要舍入时,要使用向偶数舍入的方式。 为此,我们定义数据类型 float-bits 等价于 unsigned : Homework Problems 165 /* Denormalized result */ exp = ; frac = ; } else if (x < ){ /* Normalized result. */ exp = ; frac = ; } else { /* Too big. Return +oo */ exp = ; frac = ; } /* Pack exp and frac into 32 bits */ u = exp << 23 | frac; /* Return as float */ return u2f(u); } 2.90 � Around 250 B.C., the Greek mathematician Archimedes proved that 223 71 <π<22 7 . Had he had access to a computer and the standard library , he would have been able to determine that the single-precision floating-point approximation of π has the hexadecimal representation 0x40490FDB. Of course, all of these are just approximations, since π is not rational. A. What is the fractional binary number denoted by this floating-point value? B. What is the fractional binary representation of 22 7 ? Hint: See Problem 2.82. C. At what bit position (relative to the binary point) do these two approxima- tions to π diverge? Bit-level floating-point coding rules In the following problems, you will write code to implement floating-point func- tions, operating directly on bit-level representations of floating-point numbers. Your code should exactly replicate the conventions for IEEE floating-point oper- ations, including using round-to-even mode when rounding is required. Toward this end, we define data type float_bits to be equivalent to un- signed: /* Access bit-level representation floating-point number */ typedef unsigned float_bits; Rather than using data type float in your code, you will use float_bits. You may use both int and unsigned data types, including unsigned and integer constants and operations. You may not use any unions, structs, or arrays. Most 你 的 代 码 中 不 使 用 数 据 类 型 float, 而 要 使 用 float_bits。 你 可 以 使 用 数 据 类 型 int 和 unsigned,包括无符号和整数常数和运算。你不可以使用任何联合、结构和数组。更重要的是, 你不能使用任何浮点数据类型、运算或者常数。取而代之的是,你的代码应该执行实现这些指定的 浮点运算的位操作。 下面的函数说明了对这些规则的使用。对于参数 f,如果 f 是非规格化的,该函数返回 ±0(保持 f 的符号),否则,返回 f。 166 Chapter 2 Representing and Manipulating Information significantly, you may not use any floating-point data types, operations, or con- stants. Instead, your code should perform the bit manipulations that implement the specified floating-point operations. The following function illustrates the use of these coding rules. For argument f , it returns ±0 if f is denormalized (preserving the sign of f ) and returns f otherwise. /* If f is denorm, return 0. Otherwise, return f */ float_bits float_denorm_zero(float_bits f) { /* Decompose bit representation into parts */ unsigned sign = f>>31; unsigned exp = f>>23 & 0xFF; unsigned frac = f & 0x7FFFFF; if (exp == 0) { /* Denormalized. Set fraction to 0 */ frac = 0; } /* Reassemble bits */ return (sign << 31) | (exp << 23) | frac; } 2.91 �� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute |f|. If f is NaN, then return f. */ float_bits float_absval(float_bits f); For floating-point number f , this function computes |f |. If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.92 �� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute -f. If f is NaN, then return f. */ float_bits float_negate(float_bits f); For floating-point number f , this function computes −f . If f is NaN, your func- tion should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. * 正文.indd 88 2010-10-19 14:19:03 第 2 章 信息的表示和处理  89  2.91 遵循位级浮点编码规则,实现具有如下原型的函数 : 166 Chapter 2 Representing and Manipulating Information significantly, you may not use any floating-point data types, operations, or con- stants. Instead, your code should perform the bit manipulations that implement the specified floating-point operations. The following function illustrates the use of these coding rules. For argument f , it returns ±0 if f is denormalized (preserving the sign of f ) and returns f otherwise. /* If f is denorm, return 0. Otherwise, return f */ float_bits float_denorm_zero(float_bits f) { /* Decompose bit representation into parts */ unsigned sign = f>>31; unsigned exp = f>>23 & 0xFF; unsigned frac = f & 0x7FFFFF; if (exp == 0) { /* Denormalized. Set fraction to 0 */ frac = 0; } /* Reassemble bits */ return (sign << 31) | (exp << 23) | frac; } 2.91 �� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute |f|. If f is NaN, then return f. */ float_bits float_absval(float_bits f); For floating-point number f , this function computes |f |. If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.92 �� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute -f. If f is NaN, then return f. */ float_bits float_negate(float_bits f); For floating-point number f , this function computes −f . If f is NaN, your func- tion should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 对于浮点数 f,这个函数计算 | f |。如果 f 是 NaN,你的函数应该简单地返回 f。 测试你的函数,对参数 f 可以取的所有 232 个值求值,将结果与你使用机器的浮点运算得到的结果相 比较。 2.92 遵循位级浮点编码规则,实现具有如下原型的函数 : 166 Chapter 2 Representing and Manipulating Information significantly, you may not use any floating-point data types, operations, or con- stants. Instead, your code should perform the bit manipulations that implement the specified floating-point operations. The following function illustrates the use of these coding rules. For argument f , it returns ±0 if f is denormalized (preserving the sign of f ) and returns f otherwise. /* If f is denorm, return 0. Otherwise, return f */ float_bits float_denorm_zero(float_bits f) { /* Decompose bit representation into parts */ unsigned sign = f>>31; unsigned exp = f>>23 & 0xFF; unsigned frac = f & 0x7FFFFF; if (exp == 0) { /* Denormalized. Set fraction to 0 */ frac = 0; } /* Reassemble bits */ return (sign << 31) | (exp << 23) | frac; } 2.91 �� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute |f|. If f is NaN, then return f. */ float_bits float_absval(float_bits f); For floating-point number f , this function computes |f |. If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.92 �� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute -f. If f is NaN, then return f. */ float_bits float_negate(float_bits f); For floating-point number f , this function computes −f . If f is NaN, your func- tion should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 对于浮点数 f,这个函数计算 | f |。如果 f 是 NaN,你的函数应该简单地返回 f。 测试你的函数,对参数 f 可以取的所有 232 个值求值,将结果与你使用机器的浮点运算得到的结果相 比较。 2.93 遵循位级浮点编码规则,实现具有如下原型的函数 : Homework Problems 167 2.93 ��� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute 0.5*f. If f is NaN, then return f. */ float_bits float_half(float_bits f); For floating-point number f , this function computes 0.5 . f . If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.94 ��� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute 2*f. If f is NaN, then return f. */ float_bits float_twice(float_bits f); For floating-point number f , this function computes 2.0 . f . If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.95 ���� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute (float) i */ float_bits float_i2f(int i); For argument i, this function computes the bit-level representation of (float) i. Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.96 ���� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* * Compute (int) f. * If conversion causes overflow or f is NaN, return 0x80000000 */ int float_f2i(float_bits f); 对于浮点数 f,这个函数计算 0.5 · f。如果 f 是 NaN,你的函数应该简单地返回 f。 测试你的函数,对参数 f 可以取的所有 232 个值求值,将结果与你使用机器的浮点运算得到的结果相 比较。 2.94 遵循位级浮点编码规则,实现具有如下原型的函数 : Homework Problems 167 2.93 ��� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute 0.5*f. If f is NaN, then return f. */ float_bits float_half(float_bits f); For floating-point number f , this function computes 0.5 . f . If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.94 ��� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute 2*f. If f is NaN, then return f. */ float_bits float_twice(float_bits f); For floating-point number f , this function computes 2.0 . f . If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.95 ���� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute (float) i */ float_bits float_i2f(int i); For argument i, this function computes the bit-level representation of (float) i. Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.96 ���� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* * Compute (int) f. * If conversion causes overflow or f is NaN, return 0x80000000 */ int float_f2i(float_bits f); 对于浮点数 f,这个函数计算 2.0 · f。如果 f 是 NaN,你的函数应该简单地返回 f。 测试你的函数,对参数 f 可以取的所有 232 个值求值,将结果与你使用机器的浮点运算得到的结果相 比较。 2.95 遵循位级浮点编码规则,实现具有如下原型的函数 : Homework Problems 167 2.93 ��� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute 0.5*f. If f is NaN, then return f. */ float_bits float_half(float_bits f); For floating-point number f , this function computes 0.5 . f . If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.94 ��� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute 2*f. If f is NaN, then return f. */ float_bits float_twice(float_bits f); For floating-point number f , this function computes 2.0 . f . If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.95 ���� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute (float) i */ float_bits float_i2f(int i); For argument i, this function computes the bit-level representation of (float) i. Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.96 ���� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* * Compute (int) f. * If conversion causes overflow or f is NaN, return 0x80000000 */ int float_f2i(float_bits f); 对于函数 i,这个函数计算 (float)i 的位级表示。 测试你的函数,对参数 f 可以取的所有 232 个值求值,将结果与你使用机器的浮点运算得到的结果相 比较。 166 Chapter 2 Representing and Manipulating Information significantly, you may not use any floating-point data types, operations, or con- stants. Instead, your code should perform the bit manipulations that implement the specified floating-point operations. The following function illustrates the use of these coding rules. For argument f , it returns ±0 if f is denormalized (preserving the sign of f ) and returns f otherwise. /* If f is denorm, return 0. Otherwise, return f */ float_bits float_denorm_zero(float_bits f) { /* Decompose bit representation into parts */ unsigned sign = f>>31; unsigned exp = f>>23 & 0xFF; unsigned frac = f & 0x7FFFFF; if (exp == 0) { /* Denormalized. Set fraction to 0 */ frac = 0; } /* Reassemble bits */ return (sign << 31) | (exp << 23) | frac; } 2.91 �� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute |f|. If f is NaN, then return f. */ float_bits float_absval(float_bits f); For floating-point number f , this function computes |f |. If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.92 �� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute -f. If f is NaN, then return f. */ float_bits float_negate(float_bits f); For floating-point number f , this function computes −f . If f is NaN, your func- tion should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. * ** *** *** **** 正文.indd 89 2010-10-19 14:19:05  90  第一部分 程序结构和执行  2.96 遵循位级浮点编码规则,实现具有如下原型的函数 : Homework Problems 167 2.93 ��� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute 0.5*f. If f is NaN, then return f. */ float_bits float_half(float_bits f); For floating-point number f , this function computes 0.5 . f . If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.94 ��� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute 2*f. If f is NaN, then return f. */ float_bits float_twice(float_bits f); For floating-point number f , this function computes 2.0 . f . If f is NaN, your function should simply return f . Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.95 ���� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* Compute (float) i */ float_bits float_i2f(int i); For argument i, this function computes the bit-level representation of (float) i. Test your function by evaluating it for all 232 values of argument f and com- paring the result to what would be obtained using your machine’s floating-point operations. 2.96 ���� Following the bit-level floating-point coding rules, implement the function with the following prototype: /* * Compute (int) f. * If conversion causes overflow or f is NaN, return 0x80000000 */ int float_f2i(float_bits f); 对于浮点数 f,这个函数计算 (int) f。如果 f 是 NaN,你的函数应该向零舍入。如果 f 不能用整数 表示(例如,超出表示范围,或者它是一个 NaN),那么函数应该返回 0x80000000。 测试你的函数,对参数 f 可以取的所有 232 个值求值,将结果与你使用机器的浮点运算得到的结果相 比较。 练习题答案 练习题 2.1 在我们开始查看机器级程序的时候,理解十六进制和二进制格式之间的关系将是很重要的。虽 然本书中介绍了完成这些转换的方法,但是做点练习能够让你的转换更加熟练。 A. 将 0x39A7F8 转换成二进制 : 十六进制 3 9 A 7 F 8 二进制 0011 1001 1010 0111 1111 1000 B. 将二进制 1100100101111011 转换成十六进制 : 二进制 1100 1001 0111 1011 十六进制 C 9 7 B C. 将 0xD5E4C 转换成二进制 : 十六进制 D 5 E 4 C 二进制 1101 0101 1110 0100 1100 D. 将二进制 1001101110011110110101 转换成十六进制 : 二进制 10 0110 1110 0111 1011 0101 十六进制 2 6 E 7 B 5 练习题 2.2 这个问题给你一个机会思考 2 的幂和它们的十六进制表示。 n 2n(十进制) 2n(十六进制) 9 512 0x200 19 524 288 0x80000 14 16 384 0x4000 16 65 536 0x10000 17 131 072 0x20000 5 32 0x20 7 128 0x80 练习题 2.3 这个问题给你一个机会试着对一些小的数在十六进制和十进制表示之间进行转换。对于较大的 数,使用计算器或者转换程序会更加方便和可靠一些。 十进制 二进制 十六进制 0 0000 0000 0x00 167 = 10 · 16 + 7 1010 0111 0xA7 62 = 3 · 16 + 14 0011 1110 0x3E 188 = 11 · 16 + 12 1011 1100 0xBC **** 正文.indd 90 2010-10-19 14:19:05 第 2 章 信息的表示和处理  91  十进制 二进制 十六进制 3 · 16 + 7 = 55 0011 0111 0x37 8 · 16 + 8 = 136 1000 1000 0x88 15 · 16 + 3 = 243 1111 0011 0xF3 5 · 16 + 2 = 82 0101 0010 0x52 10 · 16 + 12 = 172 1010 1100 0xAC 14 · 16 + 7 = 231 1110 0111 0xE7 练习题 2.4 当开始调试机器级程序时,你将发现在许多情况中,一些简单的十六进制运算是很有用的。可 以总是把数转换成十进制,完成运算,再把它们转换回来,但是能够直接用十六进制工作更加有效,而且 能够提供更多的信息。 A. 0x503c+0x8=0x5044。8 加上十六进制 c 得到 4 并且进位 1。 B. 0x503c-0x40=0x4ffc。在第二个数位,3 减去 4 要从第三位借 1。因为第三位是 0,所以我们必 须从第四位借位。 C. 0x503c+64=0x507c。十进制 64(26)等于十六进制 0x40。 D. 0x50ea-0x503c=0xae。十六进制数 a(十进制 10)减去十六进制数 c(十进制 12),我们从第 二位借 16,得到十六进制数 e(十进制数 14)。在第二个数位,我们现在用十六进制 d(十进制 13)减去 3, 得到十六进制 a(十进制 10)。 练习题 2.5 这个练习测试你对数据的字节表示和两种不同字节顺序的理解。 小端法 :21 大端法 :87 小端法 :21 43 大端法 :87 65 小端法 :21 43 65 大端法 :87 65 43 回想一下,show_bytes 列举了一系列字节,从低位地址的字节开始,然后逐一列出高位地址的字节。 在小端法机器上,它将按照从最低有效字节到最高有效字节的顺序列出字节。在大端法机器上,它将按照 从最高有效字节到最低有效字节的顺序列出字节。 练习题 2.6 这又是一个练习从十六进制到二进制转换的机会。同时也让你思考整数和浮点表示。我们将在 本章后面更加详细地研究这些表示。 A. 利用书中示例的符号,我们将两个串写成 : 170 Chapter 2 Representing and Manipulating Information Solution to Problem 2.6 (page 80) This problem is another chance to practice hexadecimal to binary conversion. It also gets you thinking about integer and floating-point representations. We will explore these representations in more detail later in this chapter. A. Using the notation of the example in the text, we write the two strings as follows: 00359141 00000000001101011001000101000001 ********************* 4A564504 01001010010101100100010100000100 B. With the second word shifted two positions to the right relative to the first, we find a sequence with 21 matching bits. C. We find all bits of the integer embedded in the floating-point number, except for the most significant bit having value 1. Such is the case for the example in the text as well. In addition, the floating-point number has some nonzero high-order bits that do not match those of the integer. Solution to Problem 2.7 (page 80) It prints 61 62 63 64 65 66. Recall also that the library routine strlen does not count the terminating null character, and so show_bytes printed only through the character ‘f’. Solution to Problem 2.8 (page 83) This problem is a drill to help you become more familiar with Boolean operations. Operation Result a [01101001] b [01010101] ~a [10010110] ~b [10101010] a & b [01000001] a | b [01111101] a ^ b [00111100] Solution to Problem 2.9 (page 84) This problem illustrates how Boolean algebra can be used to describe and reason about real-world systems. We can see that this color algebra is identical to the Boolean algebra over bit vectors of length 3. A. Colors are complemented by complementing the values of R, G, and B. From this, we can see that White is the complement of Black, Yellow is the complement of Blue, Magenta is the complement of Green, and Cyan is the complement of Red. B. 将第二个字相对于第一个字向右移动 2 位,我们发现一个有 21 个匹配位的序列。 C. 我们发现除了最高有效位 1,整数的所有位都嵌在浮点数中。这正好也是书中示例的情况。另外, 浮点数有一些非零的高位不与整数中的高位相匹配。 练习题 2.7 它打印 61 62 63 64 65 66。回想一下,库函数 strlen 不计算终止的空字符,所以 show_bytes 只打印到字符‘f’。 练习题 2.8 这是一个帮助你更加熟悉布尔运算的练习。 运算 结果 运算 结果 a [01101001] a&b [01000001] b [01010101] a | b [01111101] ~a [10010110] a ^ b [00111100] ~b [10101010] (续)   正文.indd 91 2010-10-19 14:19:06  92  第一部分 程序结构和执行  练习题 2.9 这个问题说明了怎样用布尔代数描述和解释现实世界的系统。我们能够看到这个颜色代数和长 度为 3 的位向量上的布尔代数是一样的。 A. 颜色的取补是通过对 R、G 和 B 的值取补得到的。由此我们可以看出,白色是黑色的补,黄色是蓝 色的补,红紫色是绿色的补,蓝绿色是红色的补。 B. 我们基于颜色的位向量表示来进行布尔运算。据此,我们得到以下结果 : 蓝色(001)  | 绿色(010)  =   蓝绿色(011) 黄色(110)  &  蓝绿色(011) =   绿色(010) 红色(100)  ^   红紫色(101) =   蓝色(001) 练习题 2.10 这个程序依赖两个事实,EXCLUSIVE-OR 是可交换的和可结合的,以及对于任意的 a,有 a ^a=0。 步骤 * x * y 初始 a b 步骤 1 a a^ b 步骤 2 a ^ (a ^ b) = (a ^ a) ^ b =b a^ b 步骤 3 b b^ (a ^ b) = (b ^ b) ^ a = a 某种情况下这个函数会失败,参见练习题 2.11。 练习题 2.11 这个题目说明了我们的原地交换规程微妙而有趣的特性。 A. first 和 last 的值都为 k,所以我们试图交换正中间的元素和它自己。 B. 在这种情况下,inplace_swap 的参数 x 和 y 都指向同一个位置。当计算 *x^*y 的时候,我们得 到 0。然后将 0 作为数组正中间的元素,且后面的步骤一直都把这个元素设置为 0。我们可以看到,练习题 2.10 的推理隐含地假设 x 和 y 代表不同的位置。 C. 将 reverse_array 的第 4 行的测试简单地替换成 first>2 x>>2 Hex Binary Binary Hex Binary Hex Binary Hex 0xC3 [11000011] [00011000] 0x18 [00110000] 0x30 [11110000] 0xF0 0x75 [01110101] [10101000] 0xA8 [00011101] 0x1D [00011101] 0x1D 0x87 [10000111] [00111000] 0x38 [00100001] 0x21 [11100001] 0xE1 0x66 [01100110] [00110000] 0x30 [00011001] 0x19 [00011001] 0x19 Solution to Problem 2.17 (page 95) In general, working through examples for very small word sizes is a very good way to understand computer arithmetic. The unsigned values correspond to those in Figure 2.2. For the two’s- complement values, hex digits 0 through 7 have a most significant bit of 0, yielding nonnegative values, while hex digits 8 through F have a most significant bit of 1, yielding a negative value. �x Hexadecimal Binary B2U4(�x) B2T4(�x) 0xE [1110] 23 + 22 + 21 = 14 −23 + 22 + 21 =−2 0x0 [0000] 0 0 0x5 [0101] 22 + 20 = 522 + 20 = 5 0x8 [1000] 23 = 8 −23 =−8 0xD [1101] 23 + 22 + 20 = 13 −23 + 22 + 20 =−3 0xF [1111] 23 + 22 + 21 + 20 = 15 −23 + 22 + 21 + 20 =−1 Solution to Problem 2.18 (page 98) For a 32-bit machine, any value consisting of eight hexadecimal digits beginning with one of the digits 8 through f represents a negative number. It is quite com- mon to see numbers beginning with a string of f’s, since the leading bits of a negative number are all ones. You must look carefully, though. For example, the number 0x8048337 has only seven digits. Filling this out with a leading zero gives 0x08048337, a positive number. 8048337: 81 ec b8 01 00 00 sub $0x1b8,%esp A. 440 804833d: 8b 55 08 mov 0x8(%ebp),%edx 8048340: 83 c2 14 add $0x14,%edx B. 20 8048343: 8b 85 58 fe ff ff mov 0xfffffe58(%ebp),%eax C. -424 8048349: 03 02 add (%edx),%eax 804834b: 89 85 74 fe ff ff mov %eax,0xfffffe74(%ebp) D. -396 8048351: 8b 55 08 mov 0x8(%ebp),%edx 8048354: 83 c2 44 add $0x44,%edx E. 68 8048357: 8b 85 c8 fe ff ff mov 0xfffffec8(%ebp),%eax F. -312 174 Chapter 2 Representing and Manipulating Information 804835d: 89 02 mov %eax,(%edx) 804835f: 8b 45 10 mov 0x10(%ebp),%eax G. 16 8048362: 03 45 0c add 0xc(%ebp),%eax H. 12 8048365: 89 85 ec fe ff ff mov %eax,0xfffffeec(%ebp) I. -276 804836b: 8b 45 08 mov 0x8(%ebp),%eax 804836e: 83 c0 20 add $0x20,%eax J. 32 8048371: 8b 00 mov (%eax),%eax Solution to Problem 2.19 (page 101) The functions T2U and U2T are very peculiar from a mathematical perspective. It is important to understand how they behave. We solve this problem by reordering the rows in the solution of Problem 2.17 according to the two’s-complement value and then listing the unsigned value as the result of the function application. We show the hexadecimal values to make this process more concrete. �x (hex) x T2U4(x) 0x8 −88 0xD −3 13 0xE −2 14 0xF −1 15 0x0 00 0x5 55 Solution to Problem 2.20 (page 102) This exercise tests your understanding of Equation 2.6. For the first four entries, the values of x are negative and T2U4(x) = x + 24. For the remaining two entries, the values of x are nonnegative and T2U4(x) = x. Solution to Problem 2.21 (page 104) This problem reinforces your understanding of the relation between two’s- complement and unsigned representations, and the effects of the C promotion rules. Recall that TMin32 is −2,147,483,648, and that when cast to unsigned it be- comes 2,147,483,648. In addition, if either operand is unsigned, then the other operand will be cast to unsigned before comparing. Expression Type Evaluation -2147483647-1 == 2147483648U unsigned 1 -2147483647-1 < 2147483647 signed 1 -2147483647-1U < 2147483647 unsigned 0 -2147483647-1 < -2147483647 signed 1 -2147483647-1U < -2147483647 unsigned 1 正文.indd 94 2010-10-19 14:19:09 第 2 章 信息的表示和处理  95  它们利用不同移位运算的零填充和符号扩展的属性。请注意强制类型转换和移位运算的顺序。在 fun1 中, 移位是在无符号 word 上进行的,因此是逻辑移位。在 fun2 中,移位是在把 word 强制类型转换为 int 之后进行的,因此是算术移位。 Solutions to Practice Problems 175 Solution to Problem 2.22 (page 108) This exercise provides a concrete demonstration of how sign extension preserves the numeric value of a two’s-complement representation. A. [1011]: −23 + 21 + 20 =−8 + 2 + 1 =−5 B. [11011]: −24 + 23 + 21 + 20 =−16 + 8 + 2 + 1 =−5 C. [111011]: −25 + 24 + 23 + 21 + 20 =−32 + 16 + 8 + 2 + 1 =−5 Solution to Problem 2.23 (page 108) The expressions in these functions are common program “idioms” for extracting values from a word in which multiple bit fields have been packed. They exploit the zero-filling and sign-extending properties of the different shift operations. Note carefully the ordering of the cast and shift operations. In fun1, the shifts are performed on unsigned variable word, and hence are logical. In fun2, shifts are performed after casting word to int, and hence are arithmetic. A. w fun1(w) fun2(w) 0x00000076 0x00000076 0x00000076 0x87654321 0x00000021 0x00000021 0x000000C9 0x000000C9 0xFFFFFFC9 0xEDCBA987 0x00000087 0xFFFFFF87 B. Function fun1 extracts a value from the low-order 8 bits of the argument, giving an integer ranging between 0 and 255. Function fun2 extracts a value from the low-order 8 bits of the argument, but it also performs sign extension. The result will be a number between −128 and 127. Solution to Problem 2.24 (page 110) The effect of truncation is fairly intuitive for unsigned numbers, but not for two’s- complement numbers. This exercise lets you explore its properties using very small word sizes. Hex Unsigned Two’s complement Original Truncated Original Truncated Original Truncated 00 00 0 0 22 22 2 2 91 91−71 B311 3 −53 F715 7 −1 −1 As Equation 2.9 states, the effect of this truncation on unsigned values is to simply find their residue, modulo 8. The effect of the truncation on signed values is a bit more complex. According to Equation 2.10, we first compute the modulo 8 residue of the argument. This will give values 0 through 7 for arguments 0 through 7, and also for arguments −8 through −1. Then we apply function U2T3 to these residues, giving two repetitions of the sequences 0 through 3 and −4 through −1. B. 函数 fun1 从参数的低 8 位中提取一个值,得到范围 0 ~ 255 之间的一个整数。函数 fun2 也从这 个参数的低 8 位中提取一个值,但是它还要执行符号扩展。结果将是介于 -128 ~ 127 之间的一个数。 练习题 2.24 对于无符号数来说,截断的影响是相当直观的,但是对于补码数却不是。这个练习让你使用 非常小的字长来研究它的属性。 正如等式(2-9)所描述的,这种截断无符号数值的结果就是发现它们模 8 余数。截断有符号数的结 果要更复杂一些。根据等式(2-10),我们首先计算这个参数模 8 后的余数。对于参数 0 ~ 7,将得出值 0 ~ 7,对于参数 -8 ~ -1 也是一样。然后我们对这些余数应用函数 U2T3,得出两个 0 ~ 3 和 -4 ~ 1 序 列的反复。 练习题 2.25 设计这个问题是要说明从有符号数到无符号数的隐式强制类型转换很容易引起错误。将参 数 length 作为一个无符号数来传递看上去是件相当自然的事情,因为没有人会想到使用一个长度为 负数的值。停止条件 i<=length-1 看上去也很自然。但是把这两点组合到一起,将产生意想不到的 结果! 因为参数 length 是无符号的,计算 0-1 将进行无符号运算,这等价于模数加法。结果得到 UMax。 ≤ 比较进行同样使用无符号数比较,而因为任何数都是小于或者等于 UMax 的,所以这个比较总是为真! 因此,代码将试图访问数组 a 的非法元素。 有两种方法可以改正这段代码,其一是将 length 声明为 int 类型,其二是将 for 循环的测试条件改 为 i < length。 练习题 2.26 这个例子说明了无符号运算的一个细微的特性,同时也是我们执行无符号运算时不会意识到 的属性。这会导致一些非常棘手的错误。 A. 在什么情况下,这个函数会产生不正确的结果?当 s 比 t 短的时候,该函数会不正确地返回 1。 B. 解释为什么会出现这样不正确的结果。由于 strlen 被定义为产生一个无符号的结果,差和比较都 采用无符号运算来计算。当 s 比 t 短的时候,差 strlen(s)-strlen(t) 会为负,但是变成了一个很大 的无符号数,且大于 0。 C. 说明如何修改这段代码好让它能可靠地工作。将测试语句改成 : 176 Chapter 2 Representing and Manipulating Information Solution to Problem 2.25 (page 111) This problem is designed to demonstrate how easily bugs can arise due to the implicit casting from signed to unsigned. It seems quite natural to pass parameter length as an unsigned, since one would never want to use a negative length. The stopping criterion i <= length-1 also seems quite natural. But combining these two yields an unexpected outcome! Since parameter length is unsigned, the computation 0 − 1is performed using unsigned arithmetic, which is equivalent to modular addition. The result is then UMax. The ≤ comparison is also performed using an unsigned comparison, and since any number is less than or equal to UMax, the comparison always holds! Thus, the code attempts to access invalid elements of array a. The code can be fixed either by declaring length to be an int, or by changing the test of the for loop to be i < length. Solution to Problem 2.26 (page 111) This example demonstrates a subtle feature of unsigned arithmetic, and also the property that we sometimes perform unsigned arithmetic without realizing it. This can lead to very tricky bugs. A. For what cases will this function produce an incorrect result? The function will incorrectly return 1 when s is shorter than t. B. Explain how this incorrect result comes about. Since strlen is defined to yield an unsigned result, the difference and the comparison are both com- puted using unsigned arithmetic. When s is shorter than t, the difference strlen(s) - strlen(t) should be negative, but instead becomes a large, unsigned number, which is greater than 0. C. Show how to fix the code so that it will work reliably. Replace the test with the following: return strlen(s) > strlen(t); Solution to Problem 2.27 (page 115) This function is a direct implementation of the rules given to determine whether or not an unsigned addition overflows. /* Determine whether arguments can be added without overflow */ int uadd_ok(unsigned x, unsigned y) { unsigned sum = x+y; return sum >= x; } Solution to Problem 2.28 (page 116) This problem is a simple demonstration of arithmetic modulo 16. The easiest way to solve it is to convert the hex pattern into its unsigned decimal value. For nonzero values of x, we must have (-u 4 x) + x = 16. Then we convert the complemented value back to hex. 练习题 2.27 这个函数是对确定无符号加法是否溢出的规则的直接实现。 正文.indd 95 2010-10-19 14:19:10  96  第一部分 程序结构和执行  176 Chapter 2 Representing and Manipulating Information Solution to Problem 2.25 (page 111) This problem is designed to demonstrate how easily bugs can arise due to the implicit casting from signed to unsigned. It seems quite natural to pass parameter length as an unsigned, since one would never want to use a negative length. The stopping criterion i <= length-1 also seems quite natural. But combining these two yields an unexpected outcome! Since parameter length is unsigned, the computation 0 − 1is performed using unsigned arithmetic, which is equivalent to modular addition. The result is then UMax. The ≤ comparison is also performed using an unsigned comparison, and since any number is less than or equal to UMax, the comparison always holds! Thus, the code attempts to access invalid elements of array a. The code can be fixed either by declaring length to be an int, or by changing the test of the for loop to be i < length. Solution to Problem 2.26 (page 111) This example demonstrates a subtle feature of unsigned arithmetic, and also the property that we sometimes perform unsigned arithmetic without realizing it. This can lead to very tricky bugs. A. For what cases will this function produce an incorrect result? The function will incorrectly return 1 when s is shorter than t. B. Explain how this incorrect result comes about. Since strlen is defined to yield an unsigned result, the difference and the comparison are both com- puted using unsigned arithmetic. When s is shorter than t, the difference strlen(s) - strlen(t) should be negative, but instead becomes a large, unsigned number, which is greater than 0. C. Show how to fix the code so that it will work reliably. Replace the test with the following: return strlen(s) > strlen(t); Solution to Problem 2.27 (page 115) This function is a direct implementation of the rules given to determine whether or not an unsigned addition overflows. /* Determine whether arguments can be added without overflow */ int uadd_ok(unsigned x, unsigned y) { unsigned sum = x+y; return sum >= x; } Solution to Problem 2.28 (page 116) This problem is a simple demonstration of arithmetic modulo 16. The easiest way to solve it is to convert the hex pattern into its unsigned decimal value. For nonzero values of x, we must have (-u 4 x) + x = 16. Then we convert the complemented value back to hex. 练习题 2.28 本题是对算术模 16 的简单示范。最容易的解决方法是将十六进制模式转换成它的无符号十 进制值。对于非零的 x 值,一定有 ( 176 Chapter 2 Representing and Manipulating Information Solution to Problem 2.25 (page 111) This problem is designed to demonstrate how easily bugs can arise due to the implicit casting from signed to unsigned. It seems quite natural to pass parameter length as an unsigned, since one would never want to use a negative length. The stopping criterion i <= length-1 also seems quite natural. But combining these two yields an unexpected outcome! Since parameter length is unsigned, the computation 0 − 1is performed using unsigned arithmetic, which is equivalent to modular addition. The result is then UMax. The ≤ comparison is also performed using an unsigned comparison, and since any number is less than or equal to UMax, the comparison always holds! Thus, the code attempts to access invalid elements of array a. The code can be fixed either by declaring length to be an int, or by changing the test of the for loop to be i < length. Solution to Problem 2.26 (page 111) This example demonstrates a subtle feature of unsigned arithmetic, and also the property that we sometimes perform unsigned arithmetic without realizing it. This can lead to very tricky bugs. A. For what cases will this function produce an incorrect result? The function will incorrectly return 1 when s is shorter than t. B. Explain how this incorrect result comes about. Since strlen is defined to yield an unsigned result, the difference and the comparison are both com- puted using unsigned arithmetic. When s is shorter than t, the difference strlen(s) - strlen(t) should be negative, but instead becomes a large, unsigned number, which is greater than 0. C. Show how to fix the code so that it will work reliably. Replace the test with the following: return strlen(s) > strlen(t); Solution to Problem 2.27 (page 115) This function is a direct implementation of the rules given to determine whether or not an unsigned addition overflows. /* Determine whether arguments can be added without overflow */ int uadd_ok(unsigned x, unsigned y) { unsigned sum = x+y; return sum >= x; } Solution to Problem 2.28 (page 116) This problem is a simple demonstration of arithmetic modulo 16. The easiest way to solve it is to convert the hex pattern into its unsigned decimal value. For nonzero values of x, we must have (-u 4 x) + x = 16. Then we convert the complemented value back to hex. x) + x = 16。然后,我们就可以将取补后的值转换回十六进制。 练习题 2.29 本题的目的是确定你理解了补码加法。 练习题 2.30 这个函数是对确定补码加法是否溢出的规则的直接实现。 Solutions to Practice Problems 177 x -u 4 x Hex Decimal Decimal Hex 0 000 5 5 11 B 8 888 D 13 3 3 F 15 1 1 Solution to Problem 2.29 (page 120) This problem is an exercise to make sure you understand two’s-complement addition. xyx+ yx+t 5 y Case −12 −15 −27 5 1 [10100] [10001] [100101] [00101] −8 −8 −16 −16 2 [11000] [11000] [110000] [10000] −98 −1 −12 [10111] [01000] [111111] [11111] 25 773 [00010] [00101] [000111] [00111] 12 4 16 −16 4 [01100] [00100] [010000] [10000] Solution to Problem 2.30 (page 120) This function is a direct implementation of the rules given to determine whether or not a two’s-complement addition overflows. /* Determine whether arguments can be added without overflow */ int tadd_ok(int x, int y) { int sum = x+y; int neg_over=x< 0&&y< 0&&sum>=0; int pos_over=x>=0&&y>=0&&sum< 0; return !neg_over && !pos_over; } Solution to Problem 2.31 (page 120) Your coworker could have learned, by studying Section 2.3.2, that two’s- complement addition forms an abelian group, and so the expression (x+y)-x will evaluate to y regardless of whether or not the addition overflows, and that (x+y)-y will always evaluate to x. Solution to Problem 2.32 (page 121) This function will give correct values, except when y is TMin. In this case, we will have -y also equal to TMin, and so function tadd_ok will consider there to be 练习题 2.31 通过对 2.3.2 节的学习,你的同事可能已经学会补码加上形成一个阿贝尔群,以及表达式 (x+y)-x 求值得到 y,无论加法是否溢出,而 (x+y)-y 总是会求值得到 x。 练习题 2.32 这个函数会给出正确的值,除了当 y 等于 TMin 时。在这个情况下,我们有 -y 也等于 TMin, 因此函数 tadd_ok 会认为只要 x 是负数时,就会负溢出。实际上,此时 x-y 根本就没有溢出。 这个练习说明,在函数的任何测试过程中,TMin 都应该作为一种测试情况。 练习题 2.33 本题用非常小的字长帮助你理解补码的非。 对于 w = 4,我们有 TMin4 =-8。因此 -8 是它自己的加法逆元,而其他数值是通过整数非来取非的。 正文.indd 96 2010-10-19 14:19:11 第 2 章 信息的表示和处理  97  对于无符号数的非,位的模式是相同的。 练习题 2.34 本题的目的是确定你理解了补码乘法。 练习题 2.35 用所有可能的 x 和 y 测试一遍这个函数显然是不现实的。当数据类型 int 为 32 位时,即 使你每秒运行一百亿个测试,也需要 58 年才能完成所有的组合。另一方面,把函数中的数据类型改成 short 或者 char,然后再穷尽测试,倒是测试代码的一种可行的方法。 我们提出以下论据,这是一个更理论的方法 : 1. 我们知道 x · y 可以写成一个 2w 位的补码数字。用 u 表示低 w 位的无符号数,v 表示高 w 位的补码 数字。那么,根据等式(2-3),我们可以得到 x · y = v2w+u。 我们还知道 u = T2Uw( p ),因为它们是从同一个位模式得出来的无符号和补码数字,因此根据等式 (2-5),我们有 u = p + pw-12w,这里 pw-1 是 p 的最高有效位。设 t = v + pw-1,我们得到 x · y = p + t2w。 当 t = 0 时,有 x · y = p ;乘法不会溢出。当 t ≠ 0 时,有 x · y ≠ p ;乘法溢出。 2. 根据整数除法的定义,用非零数 x 除以 p 会得到商 q 和余数 r,即 p = x · q + r,且 | r | < | x |。(这里 用的是绝对值,因为 x 和 r 的符号可能不一致。例如,-7 除以 2 得到商 -3 和余数 -1。) 3. 假设 q = y,那么有 x · y = x · y + r + t2w。在此,我们可以得到 r + t2w = 0。但是 | r | < | x | ≤ 2w,所以 只有当 t = 0 时,这个等式才会成立,此时 r = 0。 假设 r = t = 0,那么我们有 x · y = x · q,隐含有 y = q。 当 x = 0 时,乘法不溢出,所以我们的代码提供了一种可靠的方法来测试补码乘法是否会导致溢出。 练习题 2.36 如果用 64 位表示,乘法就不会有溢出。然后我们来验证将乘积强制类型转换为 32 位是否会 改变它的值 : Solutions to Practice Problems 179 We also know that u = T2Uw(p), since they are unsigned and two’s- complement numbers arising from the same bit pattern, and so by Equa- tion 2.5, we can write u = p + pw−12w, where pw−1 is the most significant bit of p. Letting t = v + pw−1, we have x . y = p + t2w. When t = 0, we have x . y = p; the multiplication does not overflow. When t �= 0, we have x . y �= p; the multiplication does overflow. 2. By definition of integer division, dividing p by nonzero x gives a quotient q and a remainder r such that p = x . q + r, and |r| < |x|. (We use absolute values here, because the signs of x and r may differ. For example, dividing −7 by 2 gives quotient −3 and remainder −1.) 3. Suppose q = y. Then we have x . y = x . y + r + t2w. From this, we can see that r + t2w = 0. But |r| < |x|≤2w, and so this identity can hold only if t = 0, in which case r = 0. Suppose r = t = 0. Then we will have x . y = x . q, implying that y = q. When x equals 0, multiplication does not overflow, and so we see that our code provides a reliable way to test whether or not two’s-complement multiplication causes overflow. Solution to Problem 2.36 (page 125) With 64 bits, we can perform the multiplication without overflowing. We then test whether casting the product to 32 bits changes the value: 1 /* Determine whether arguments can be multiplied without overflow */ 2 int tmult_ok(int x, int y) { 3 /* Compute product without overflow */ 4 long long pll = (long long) x*y; 5 /* See if casting to int preserves value */ 6 return pll == (int) pll; 7 } Note that the casting on the right-hand side of line 4 is critical. If we instead wrote the line as long long pll = x*y; the product would be computed as a 32-bit value (possibly overflowing) and then sign extended to 64 bits. Solution to Problem 2.37 (page 126) A. This change does not help at all. Even though the computation of asize will be accurate, the call to malloc will cause this value to be converted to a 32-bit unsigned number, and so the same overflow conditions will occur. B. With malloc having a 32-bit unsigned number as its argument, it cannot possibly allocate a block of more than 232 bytes, and so there is no point attempting to allocate or copy this much memory. Instead, the function 注意,第 4 行右边的强制类型转换至关重要。如果我们将这一行写成以下形式 : 正文.indd 97 2010-10-19 14:19:13  98  第一部分 程序结构和执行  Solutions to Practice Problems 179 We also know that u = T2Uw(p), since they are unsigned and two’s- complement numbers arising from the same bit pattern, and so by Equa- tion 2.5, we can write u = p + pw−12w, where pw−1 is the most significant bit of p. Letting t = v + pw−1, we have x . y = p + t2w. When t = 0, we have x . y = p; the multiplication does not overflow. When t �= 0, we have x . y �= p; the multiplication does overflow. 2. By definition of integer division, dividing p by nonzero x gives a quotient q and a remainder r such that p = x . q + r, and |r| < |x|. (We use absolute values here, because the signs of x and r may differ. For example, dividing −7 by 2 gives quotient −3 and remainder −1.) 3. Suppose q = y. Then we have x . y = x . y + r + t2w. From this, we can see that r + t2w = 0. But |r| < |x|≤2w, and so this identity can hold only if t = 0, in which case r = 0. Suppose r = t = 0. Then we will have x . y = x . q, implying that y = q. When x equals 0, multiplication does not overflow, and so we see that our code provides a reliable way to test whether or not two’s-complement multiplication causes overflow. Solution to Problem 2.36 (page 125) With 64 bits, we can perform the multiplication without overflowing. We then test whether casting the product to 32 bits changes the value: 1 /* Determine whether arguments can be multiplied without overflow */ 2 int tmult_ok(int x, int y) { 3 /* Compute product without overflow */ 4 long long pll = (long long) x*y; 5 /* See if casting to int preserves value */ 6 return pll == (int) pll; 7 } Note that the casting on the right-hand side of line 4 is critical. If we instead wrote the line as long long pll = x*y; the product would be computed as a 32-bit value (possibly overflowing) and then sign extended to 64 bits. Solution to Problem 2.37 (page 126) A. This change does not help at all. Even though the computation of asize will be accurate, the call to malloc will cause this value to be converted to a 32-bit unsigned number, and so the same overflow conditions will occur. B. With malloc having a 32-bit unsigned number as its argument, it cannot possibly allocate a block of more than 232 bytes, and so there is no point attempting to allocate or copy this much memory. Instead, the function 就会用 32 位值来计算乘积(可能会溢出),然后再符号扩展到 64 位。 练习题 2.37 A. 这个改动完全没有帮助。虽然 asize 的计算会更准确,但是调用 malloc 会导致这个值被转换成 一个 32 位无符号数字,因而还是会出现同样的溢出条件。 B. malloc 使用一个 32 位无符号数作为参数,它不可能分配一个大于 232 个字节的块,因此,没有必 要试图去分配或者复印这样大的一块存储器。取而代之的,函数应该放弃,返回 NULL,用下面的代码取代 对 malloc 原始的调用(第 10 行): 180 Chapter 2 Representing and Manipulating Information should abort and return NULL, as illustrated by the following replacement to the original call to malloc (line 10): long long unsigned required_size = ele_cnt * (long long unsigned) ele_size; size_t request_size = (size_t) required_size; if (required_size != request_size) /* Overflow must have occurred. Abort operation */ return NULL; void *result = malloc(request_size); if (result == NULL) /* malloc failed */ return NULL; Solution to Problem 2.38 (page 127) In Chapter 3, we will see many examples of the lea instruction in action. The instruction is provided to support pointer arithmetic, but the C compiler often uses it as a way to perform multiplication by small constants. For each value of k, we can compute two multiples: 2k (when b is 0) and 2k + 1 (when b is a). Thus, we can compute multiples 1, 2, 3, 4, 5, 8, and 9. Solution to Problem 2.39 (page 128) The expression simply becomes -(x<m+ 1. 练习题 2.38 在第 3 章,我们将看到很多实际的 LEA 指令的例子。用这个指令来支持指针运算,但是 C 语言编译器经常用它来执行小常数乘法。 对于每个 k 的值,可以计算出 2 的倍数 :2k(当 b 为 0 时)和 2k + 1(当 b 为 a 时)。因此我们能够计 算出倍数为 1, 2, 3, 4, 5, 8 和 9 的值。 练习题 2.39 这个表达式简单地变成了 -(x<m+1 时,选择形式 B。 这个原则的证明如下。首先假设 m>1。当 n=m 时,形式 A 只需要 1 个移位,而形式 B 需要 2 个移位 和 1 个减法。当 n=m+1 时,这两种形式都需要 2 个移位和 1 个加法或者 1 个减法。当 n>m+1 时,形式 B 只需要 2 个移位和 1 个减法,而形式 A 需要 n-m+1>2 个移位和 n-m>1 个加法。对于 m=1 的情况,对于形 式 A 和 B 都要少 1 个移位,所以在两者中选择时,还是适用同样的原则。 练习题 2.42 这里唯一的挑战是不用任何测试或条件运算计算偏置量。我们利用了一个诀窍,表达式 x>>31 产生一个字,如果 x 是负数,这个字为全 1,否则为全 0。通过掩码屏蔽适当的位,我们就得到期 望的偏置值。 正文.indd 98 2010-10-19 14:19:14 第 2 章 信息的表示和处理  99  Solutions to Practice Problems 181 The justification for this rule is as follows. Assume first that m>1. When n = m, form A requires only a single shift, while form B requires two shifts and a subtraction. When n = m + 1, both forms require two shifts and either an addition or a subtraction. When n>m+ 1, form B requires only two shifts and one subtraction, while form A requires n − m + 1 > 2 shifts and n − m>1 additions. For the case of m = 1, we get one fewer shift for both forms A and B, and so the same rules apply for choosing between the two. Solution to Problem 2.42 (page 131) The only challenge here is to compute the bias without any testing or conditional operations. We use the trick that the expression x >> 31 generates a word with all ones if x is negative, and all zeros otherwise. By masking off the appropriate bits, we get the desired bias value. int div16(int x) { /* Compute bias to be either 0 (x >= 0) or 15 (x < 0) */ int bias = (x >> 31) & 0xF; return (x + bias) >> 4; } Solution to Problem 2.43 (page 132) We have found that people have difficulty with this exercise when working di- rectly with assembly code. It becomes more clear when put in the form shown in optarith. We can see that M is 31; x*M is computed as (x<<5)-x. We can see that N is 8; a bias value of 7 is added when y is negative, and the right shift is by 3. Solution to Problem 2.44 (page 133) These “C puzzle” problems provide a clear demonstration that programmers must understand the properties of computer arithmetic: A. (x > 0) || (x-1 < 0) False. Let x be −2,147,483,648 (TMin32). We will then have x-1 equal to 2147483647 (TMax32). B.(x & 7) != 7 || (x<<29 < 0) True. If (x&7)!=7evaluates to 0, then we must have bit x2 equal to 1. When shifted left by 29, this will become the sign bit. C.(x*x)>=0 False. When x is 65,535 (0xFFFF), x*x is −131,071 (0xFFFE0001). D. x<0||-x<=0 True. If x is nonnegative, then -x is nonpositive. E. x>0||-x>=0 False. Let x be −2,147,483,648 (TMin32). Then both x and -x are negative. 练习题 2.43 我们发现当人们直接与汇编代码打交道时是有困难的。但当把它放入 optarith 所示的形式 中时,问题会变得更加清晰明了。 我们可以看到 M 是 31 ;是用 (x<<5)-x 来计算 x*M。 我们可以看到 N 是 8 ;当 y 是负数时,加上偏置量 7,并且右移 3 位。 练习题 2.44 这些“C 的谜题”清楚地告诉程序员必须理解计算机运算的属性。 A. (x>0)||((x-1)<0) 假。设 x 等于 -2 147 483 648(TMin32)。那么,有 x-1 等于 2147483647(TMax32)。 B. (x&7)!=7||(x<<29<0) 真。如果 (x&7)!=7 这个表达式的值为 0,那么必须有位 x2 等于 1。当左移 29 位时,这个位将变成 符号位。 C. (x*x)>=0 假。当 x 为 65 535(0xFFFF)时,x*x 为 -131 071(0xFFFE0001)。 D. x<0||-x<=0 真。如果 x 是非负数,则 -x 是非正的。 E. x>0||-x>=0 假。设 x 为 -2 147 483 648(TMin32)。那么 x 和 -x 都为负数。 F. x+y==uy+ux 真。补码和无符号乘法有相同的位级行为,而且它们是可交换的。 G. x*~y+uy*ux==-x 真。~y 等于 -y-1。uy*ux 等于 x*y。因此,等式左边等价于 x*-y-x+x*y。 练习题 2.45 理解二进制小数表示是理解浮点编码的一个重要步骤。这个练习让你试验一些简单的例子。 考虑二进制小数表示的一个简单方法是将一个数表示为形如 182 Chapter 2 Representing and Manipulating Information F. x+y == uy+ux True. Two’s-complement and unsigned addition have the same bit-level be- havior, and they are commutative. G. x*~y + uy*ux == -x True. ~y equals -y-1. uy*ux equals x*y. Thus, the left hand side is equivalent to x*-y-x+x*y. Solution to Problem 2.45 (page 136) Understanding fractional binary representations is an important step to under- standing floating-point encodings. This exercise lets you try out some simple ex- amples. Fractional value Binary representation Decimal representation 1 8 0.001 0.125 3 4 0.11 0.75 25 16 1.1001 1.5625 43 16 10.1011 2.6875 9 8 1.001 1.125 47 8 101.111 5.875 51 16 11.0011 3.1875 One simple way to think about fractional binary representations is to repre- sent a number as a fraction of the form x 2k . We can write this in binary using the binary representation of x, with the binary point inserted k positions from the right. As an example, for 25 16 , we have 2510 = 110012. We then put the binary point four positions from the right to get 1.10012. Solution to Problem 2.46 (page 136) In most cases, the limited precision of floating-point numbers is not a major problem, because the relative error of the computation is still fairly low. In this example, however, the system was sensitive to the absolute error. A. We can see that 0.1 − x has binary representation 0.000000000000000000000001100[1100] ...2 B. Comparing this to the binary representation of 1 10 , we can see that it is simply 2−20 × 1 10 , which is around 9.54 × 10−8. C. 9.54 × 10−8 × 100 × 60 × 60 × 10 ≈ 0.343 seconds. D. 0.343 × 2000 ≈ 687 meters. Solution to Problem 2.47 (page 141) Working through floating-point representations for very small word sizes helps clarify how IEEE floating point works. Note especially the transition between denormalized and normalized values. 的小数。我们将这个形式表示为二进制 的过程是 :使用 x 的二进制表示,并把二进制小数点插入从右边算起的第 k 个位置。举一个例子,对于 182 Chapter 2 Representing and Manipulating Information F. x+y == uy+ux True. Two’s-complement and unsigned addition have the same bit-level be- havior, and they are commutative. G. x*~y + uy*ux == -x True. ~y equals -y-1. uy*ux equals x*y. Thus, the left hand side is equivalent to x*-y-x+x*y. Solution to Problem 2.45 (page 136) Understanding fractional binary representations is an important step to under- standing floating-point encodings. This exercise lets you try out some simple ex- amples. Fractional value Binary representation Decimal representation 1 8 0.001 0.125 3 4 0.11 0.75 25 16 1.1001 1.5625 43 16 10.1011 2.6875 9 8 1.001 1.125 47 8 101.111 5.875 51 16 11.0011 3.1875 One simple way to think about fractional binary representations is to repre- sent a number as a fraction of the form x 2k . We can write this in binary using the binary representation of x, with the binary point inserted k positions from the right. As an example, for 25 16 , we have 2510 = 110012. We then put the binary point four positions from the right to get 1.10012. Solution to Problem 2.46 (page 136) In most cases, the limited precision of floating-point numbers is not a major problem, because the relative error of the computation is still fairly low. In this example, however, the system was sensitive to the absolute error. A. We can see that 0.1 − x has binary representation 0.000000000000000000000001100[1100] ...2 B. Comparing this to the binary representation of 1 10 , we can see that it is simply 2−20 × 1 10 , which is around 9.54 × 10−8. C. 9.54 × 10−8 × 100 × 60 × 60 × 10 ≈ 0.343 seconds. D. 0.343 × 2000 ≈ 687 meters. Solution to Problem 2.47 (page 141) Working through floating-point representations for very small word sizes helps clarify how IEEE floating point works. Note especially the transition between denormalized and normalized values. , 有 2510 = 110012。然后把二进制小数点放在从右算起的第 4 位,得到 1.10012。 练习题 2.46 在大多数情况下,浮点数的有限精度不是主要的问题,因为计算的相对误差仍然是相当低 的。然而在这个例子中,系统对于绝对误差是很敏感的。 A. 我们可以看到 0.1-x 的二进制表示为 : 182 Chapter 2 Representing and Manipulating Information F. x+y == uy+ux True. Two’s-complement and unsigned addition have the same bit-level be- havior, and they are commutative. G. x*~y + uy*ux == -x True. ~y equals -y-1. uy*ux equals x*y. Thus, the left hand side is equivalent to x*-y-x+x*y. Solution to Problem 2.45 (page 136) Understanding fractional binary representations is an important step to under- standing floating-point encodings. This exercise lets you try out some simple ex- amples. Fractional value Binary representation Decimal representation 1 8 0.001 0.125 3 4 0.11 0.75 25 16 1.1001 1.5625 43 16 10.1011 2.6875 9 8 1.001 1.125 47 8 101.111 5.875 51 16 11.0011 3.1875 One simple way to think about fractional binary representations is to repre- sent a number as a fraction of the form x 2k . We can write this in binary using the binary representation of x, with the binary point inserted k positions from the right. As an example, for 25 16 , we have 2510 = 110012. We then put the binary point four positions from the right to get 1.10012. Solution to Problem 2.46 (page 136) In most cases, the limited precision of floating-point numbers is not a major problem, because the relative error of the computation is still fairly low. In this example, however, the system was sensitive to the absolute error. A. We can see that 0.1 − x has binary representation 0.000000000000000000000001100[1100] ...2 B. Comparing this to the binary representation of 1 10 , we can see that it is simply 2−20 × 1 10 , which is around 9.54 × 10−8. C. 9.54 × 10−8 × 100 × 60 × 60 × 10 ≈ 0.343 seconds. D. 0.343 × 2000 ≈ 687 meters. Solution to Problem 2.47 (page 141) Working through floating-point representations for very small word sizes helps clarify how IEEE floating point works. Note especially the transition between denormalized and normalized values. 把这个表示与 182 Chapter 2 Representing and Manipulating Information F. x+y == uy+ux True. Two’s-complement and unsigned addition have the same bit-level be- havior, and they are commutative. G. x*~y + uy*ux == -x True. ~y equals -y-1. uy*ux equals x*y. Thus, the left hand side is equivalent to x*-y-x+x*y. Solution to Problem 2.45 (page 136) Understanding fractional binary representations is an important step to under- standing floating-point encodings. This exercise lets you try out some simple ex- amples. Fractional value Binary representation Decimal representation 1 8 0.001 0.125 3 4 0.11 0.75 25 16 1.1001 1.5625 43 16 10.1011 2.6875 9 8 1.001 1.125 47 8 101.111 5.875 51 16 11.0011 3.1875 One simple way to think about fractional binary representations is to repre- sent a number as a fraction of the form x 2k . We can write this in binary using the binary representation of x, with the binary point inserted k positions from the right. As an example, for 25 16 , we have 2510 = 110012. We then put the binary point four positions from the right to get 1.10012. Solution to Problem 2.46 (page 136) In most cases, the limited precision of floating-point numbers is not a major problem, because the relative error of the computation is still fairly low. In this example, however, the system was sensitive to the absolute error. A. We can see that 0.1 − x has binary representation 0.000000000000000000000001100[1100] ...2 B. Comparing this to the binary representation of 1 10 , we can see that it is simply 2−20 × 1 10 , which is around 9.54 × 10−8. C. 9.54 × 10−8 × 100 × 60 × 60 × 10 ≈ 0.343 seconds. D. 0.343 × 2000 ≈ 687 meters. Solution to Problem 2.47 (page 141) Working through floating-point representations for very small word sizes helps clarify how IEEE floating point works. Note especially the transition between denormalized and normalized values. 的二进制表示进行比较,我们可以看到这就是 182 Chapter 2 Representing and Manipulating Information F. x+y == uy+ux True. Two’s-complement and unsigned addition have the same bit-level be- havior, and they are commutative. G. x*~y + uy*ux == -x True. ~y equals -y-1. uy*ux equals x*y. Thus, the left hand side is equivalent to x*-y-x+x*y. Solution to Problem 2.45 (page 136) Understanding fractional binary representations is an important step to under- standing floating-point encodings. This exercise lets you try out some simple ex- amples. Fractional value Binary representation Decimal representation 1 8 0.001 0.125 3 4 0.11 0.75 25 16 1.1001 1.5625 43 16 10.1011 2.6875 9 8 1.001 1.125 47 8 101.111 5.875 51 16 11.0011 3.1875 One simple way to think about fractional binary representations is to repre- sent a number as a fraction of the form x 2k . We can write this in binary using the binary representation of x, with the binary point inserted k positions from the right. As an example, for 25 16 , we have 2510 = 110012. We then put the binary point four positions from the right to get 1.10012. Solution to Problem 2.46 (page 136) In most cases, the limited precision of floating-point numbers is not a major problem, because the relative error of the computation is still fairly low. In this example, however, the system was sensitive to the absolute error. A. We can see that 0.1 − x has binary representation 0.000000000000000000000001100[1100] ...2 B. Comparing this to the binary representation of 1 10 , we can see that it is simply 2−20 × 1 10 , which is around 9.54 × 10−8. C. 9.54 × 10−8 × 100 × 60 × 60 × 10 ≈ 0.343 seconds. D. 0.343 × 2000 ≈ 687 meters. Solution to Problem 2.47 (page 141) Working through floating-point representations for very small word sizes helps clarify how IEEE floating point works. Note especially the transition between denormalized and normalized values. ,也就是大约 9.54 ×10-8。 正文.indd 99 2010-10-19 14:19:17  100  第一部分 程序结构和执行  B. 9.54×10-8 ×100×60×60×10 ≈ 0.343 秒。 C. 0.343×2000 ≈ 687 米。 练习题 2.47 研究字长非常小的浮点表示能够帮助澄清 IEEE 浮点是怎样工作的。要特别注意非规格化数 和规格化数之间的过渡。 练 习 题 2.48  十六进制0x359141等价于二进制[1101011001000101000001]。 将之右移21位得到 1.1010110010001010000012×221。除去起始位的1并增加2个0形成小数域,从而得到[10101100100010100000100]。阶码是通过 21 加上偏置量 127 形成的,得到 148(二进制 [10010100])。我们把它和符号字段 0 联合起来,得到二进制表示 [01001010010101100100010100000100] 我们看到两种表示中匹配的位对应于整数的低位到最高有效位等于 1,匹配小数的高 21 位 : Solutions to Practice Problems 183 Bits eE2E fM 2E × MVDecimal 0 00 00 001 0 4 0 4 0 4 0 0.0 0 00 01 001 1 4 1 4 1 4 1 4 0.25 0 00 10 001 2 4 2 4 2 4 1 2 0.5 0 00 11 001 3 4 3 4 3 4 3 4 0.75 0 01 00 101 0 4 4 4 4 4 1 1.0 0 01 01 101 1 4 5 4 5 4 5 4 1.25 0 01 10 101 2 4 6 4 6 4 3 2 1.5 0 01 11 101 3 4 7 4 7 4 7 4 1.75 0 10 00 212 0 4 4 4 8 4 2 2.0 0 10 01 212 1 4 5 4 10 4 5 2 2.5 0 10 10 212 2 4 6 4 12 4 3 3.0 0 10 11 212 3 4 7 4 14 4 7 2 3.5 0 11 00 ——— —— — ∞ — 0 11 01 ——— —— — NaN — 0 11 10 ——— —— — NaN — 0 11 11 ——— —— — NaN — Solution to Problem 2.48 (page 144) Hexadecimal value 0x359141 is equivalent to binary [1101011001000101000001]. Shifting this right 21 places gives 1.1010110010001010000012 × 221. We form the fraction field by dropping the leading 1 and adding two 0s, giving [10101100100010100000100]. The exponent is formed by adding bias 127 to 21, giving 148 (binary [10010100]). We combine this with a sign field of 0 to give a binary representation [01001010010101100100010100000100]. We see that the matching bits in the two representations correspond to the low-order bits of the integer, up to the most significant bit equal to 1 matching the high-order 21 bits of the fraction: 00359141 00000000001101011001000101000001 ********************* 4A564504 01001010010101100100010100000100 Solution to Problem 2.49 (page 144) This exercise helps you think about what numbers cannot be represented exactly in floating point. 练习题 2.49 这个练习帮助你思考什么数不能用浮点准确表示。 A. 这个数的二进制表示是 :1 后面跟着 n 个 0,其后再跟 1,得到的值是 2n+1 + 1。 B. 当 n = 23 时,值是 224 + 1 = 16 777 217。 练习题 2.50 人工舍入帮助你强化二进制数舍入到偶数的概念。 正文.indd 100 2010-10-19 14:19:19 第 2 章 信息的表示和处理  101  练习题 2.51 A. 从 1/10 的无穷序列中我们可以看到,舍入位置右边 2 位都是 1,所以 1/10 更好一点儿的近似值应 该是对 x 加 1,得到 x' = 0.000110011001100110011012,它比 0.1 大一点儿。 B. 我们可以看到 x'-0.1 的二进制表示为 : 0.0000000000000000000[1100] 将这个值与 1/10 的二进制表示比较,我们可以看到它等于 2-22×1/10,大约等于 2.38×10-8。 C. 2.38×10-8×100×60×60×10 ≈ 0.086 秒,爱国者导弹系统中的误差是它的 4 倍。 D. 0.086×2000 ≈ 171 米。 练习题 2.52 这个题目考查了很多有关浮点表示的概念,包括规格化和非规格化的值的编码,以及舍入。 向上舍入 向下舍入 练习题 2.53 一般来说,使用库宏(library macro)会比你自己写的代码更好一些。然而这段代码似乎可以 在多种机器上工作。 假设值 le400 溢出为无穷。 Solutions to Practice Problems 185 We assume that the value 1e400 overflows to infinity. #define POS_INFINITY 1e400 #define NEG_INFINITY (-POS_INFINITY) #define NEG_ZERO (-1.0/POS_INFINITY) Solution to Problem 2.54 (page 151) Exercises such as this one help you develop your ability to reason about floating- point operations from a programmer’s perspective. Make sure you understand each of the answers. A. x == (int)(double) x Yes, since double has greater precision and range than int. B. x == (int)(float) x No. For example, when x is TMax. C. d == (double)(float) d No. For example, when d is 1e40, we will get +∞ on the right. D. f == (float)(double) f Yes, since double has greater precision and range than float. E. f == -(-f) Yes, since a floating-point number is negated by simply inverting its sign bit. F. 1.0/2 == 1/2.0 Yes, the numerators and denominators will both be converted to floating- point representations before the division is performed. G. d*d >= 0.0 Yes, although it may overflow to +∞. H. (f+d)-f == d No, for example when f is 1.0e20 and d is 1.0, the expression f+d will be rounded to 1.0e20, and so the expression on the left-hand side will evaluate to 0.0, while the right-hand side will be 1.0. 练习题 2.54 这个练习可以帮助你从程序员的角度来提高研究浮点运算的能力。确信自己理解下面每一个答案。 A. x==(int)(double)x 真,因为 double 类型比 int 类型具有更大的精度和范围。 B. x==(int)(float)x 假,例如当 x 为 TMax 时。 C. d==(double)(float)d 假,例如当 d 为 le40 时,右边得到 + ∞。 D. f==(float)(double)f 真,因为 double 类型比 float 类型具有更大的精度和范围。 E. f==-(-f) 真,因为浮点数取非就是简单地对它的符号位取反。 F. 1.0/2==1/2.0 真,在执行除法之前,分子和分母都会被转换成浮点表示。 G. d*d>=0.0 真,虽然它可能会溢出到 + ∞。 H. (f+d)-f==d 假,例如当 f 是 1.0e20 而 d 是 1.0 时,表达式 f+d 会舍入到 1.0e20,因此左边的表达式求值得 到 0.0,而右边是 1.0。 正文.indd 101 2010-10-19 14:19:20
还剩101页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 20 金币 [ 分享pdf获得金币 ] 13 人已下载

下载pdf