【考纲内容】
【复习提示】
本章内容较为繁杂,由于计算机中数的表示和运算方法与人们日常生活中的表示和运算方法不同,因此理解也较为困难。纵观近几年的真题,不难发现
unsigned、short、int、long、float、double
等在C语言中的表示、运算、溢出判断、隐式类型转换、强制类型转换、
IEEE754 浮点数的表示,以及浮点数的运算,
都是考研考查的重点,需要牢固掌握。
在计算机系统内部,所有的信息都是用二进制进行编码的,这样做的原因有以下几点。
常用的进位计数法有十进制、二进制、八进制、十六进制等。十进制数是日常生活中最常使用的,而计算机中通常使用二进制数、八进制数和十六进制数。 在进位计数法中,每个数位所用到的不同数码的个数称为基数。十进制的基数为10(0~9),每个数位计满10就向高位进位,即“逢十进一” 十进制数101,其个位的1显然与百位的1所表示的数值是不同的。每个数码所表示的数值等于该数码本身乘以一个与它所在数位有关的常数,这个常数称为位权。一个进位数的数值大小就是它的各位数码按权相加。
一个
式中,
在日常生活中,通常用正号、负号来分别表示正数(正号可省略)和负数,如 +15、-8 等。这种带“+”或“-”符号的数称为真值。真值是机器数所代表的实际值。
在计算机中,通常将数的符号和数值部分一起编码,将数据的符号数字化,通常用“0”表示“正”,用“1”表示“负”。这种把符号“数字化”的数称为机器数。常用的有原码、补码和反码表示法。如 0,101(这里的逗号 “,” 仅为区分符号位与数值位)表示 +5。
二进制编码的十进制数(Binary-Coded Decimal,BCD)通常采用 4位二进制数来表示一位十进制数中的 0~9 这10个数码。这种编码方法使二进制数和十进制数之间的转换得以快速进行。但 4 位二进制数可以组合出 16 种代码,因此必有 6 种状态为冗余状态。
下面列举几种常用的BCD码。
根据小数点的位置是否固定,计算机中有两种数据格式:定点表示和浮点表示。
定点表示法用于表示定点小数和定点整数。
定点数编码表示法主要有以下四种:原码、补码、反码和移码。
用机器数的最高位表示数的符号,其余各位表示数的绝对值。
例如,若
其中最高位是符号位。
若字长为
例如,若
其中最高位是符号位。
若字长为
注意:真值零的原码表示有正零和负零两种形式,即
和
优点:原码表示与真值的对应关系简单、直观,与真值的转换也简单,并且用原码实现乘除运算比较方便。
缺点:零的表示不唯一,更重要的是原码加减运算比较复杂。
原码加减运算规则比较复杂,对于两个不同符号数的加法(或同符号数的减法),先要比较两个数的绝对值大小,然后用绝对值大的数减去绝对值小的数,最后还要给结果选择合适的符号。而补码表示法中的加减运算则统一采用加法操作实现。
例如,若
若字长为 n+1,则补码的表示范围为
例如,若
若字长为 n+1,则补码的表示范围为
注意:零的补码表示是唯一的,即 [+0]补 = [-0]补=0.0000。 由定义 [-1]补= 10.0000- 1.0000 =1.0000,可见,小数补码比原码多表示一个 -1;整数补码比原码多表示一个
。
变形补码,又称模 4 补码,双符号位的补码小数,其定义为
模 4 补码双符号位 00 表示正,11 表示负,用在完成算术运算的 ALU 部件中。 将 [x]补 的符号位与数值位一起右移并保持原符号位的值不变,可实现除法功能。
对补码而言,正数和负数的转换不同。正数补码的转换方式与原码的相同。
反码表示法的负数是通过“各位取反,末位减 1”来得到的。正数的反码表示与其补码或原码表示相同。反码表示的主要缺点如下:
由于上述缺点,反码在计算机中很少使用,通常作为数码变换的中间表示形式。
移码常用来表示浮点数的阶码,只能表示整数。移码是在真值
移码定义为:
其中机器字长为
例如,若正数
移码具有以下特点:
当一个编码的全部二进制位均为数值位而没有符号位时,该编码表示就是无符号整数,也直接称为无符号数。此时,默认数的符号为正。由于无符号整数省略了一位符号位,所以在字长相同的情况下,它能表示的最大数比带符号整数能表示的大。例如,8 位无符号整数,对应的表示范围为 0~28-1,即最大数为 255,而 8 位带符号整数的最大数是 127。
一般在全部是正数运算且不出现负值结果的场合下,使用无符号整数表示。例如,可用无符号整数进行地址运算,或用它来表示指针。
将符号数值化,并将符号位放在有效数字的前面,就组成了带符号整数。虽然前面介绍的原码、补码、反码和移码都可以用来表示带符号整数,但补码表示有其明显的优势:
在计算机中,运算器由算术逻辑单元(Arithmetic Logic Unit,ALU)、移位器、状态寄存器和通用寄存器组等组成。运算器的基本功能包括加、减、乘、除四则运算,与、或、非、异或等逻辑运算,以及移位、求补等操作。ALU 的核心部件是加法器。
全加器(FA)是最基本的加法单元,具有加数
一位全加器对应的逻辑结构如下图所示:
将 n 个全加器相连可得到 n 位加法器,称为串行进位加法器,如下图所示。串行进位又称行波进位,每级进位直接依赖于前一级的进位,即进位信号是逐级形成的。
图 2.4 中的加法器实现了两个 n 位二进制数
在串行进位加法器中,低位运算产生进位所需的时间将影响高位运算的时间。因此,串行进位加法器的最长运算时间主要由进位信号的传递时间决定,位数越多延迟时间就越长,而全加器本身的求和延迟只为次要因素,因此加快进位产生和提高传递的速度是关键。
设
其中,
代入前面
从上述表达式可以看出,
通过这种进位方式实现的加法器称为全先行进位加法器。因为各个进位是并行产生的,所以是一种并行加法器。
这种进位方式是快速的,与位数无关。但随着加法器位数的增加,
无符号数加法器只能用于两个无符号数相加,不能进行带符号整数的加/减运算。为了进行带符号整数的加/减运算,还需要在无符号数加法器的基础上增加相应的逻辑门电路,使加法器不仅能计算和/差,还要生成相应的标志信息。如下图所示:
在图 2.6 中,溢出标志的逻辑表达式为
ALU 是一种功能较强的组合逻辑电路,它能进行多种算术运算和逻辑运算。由于加、减、乘、除运算最终都能归结为加法运算,因此 ALU 的核心是带标志加法器,同时也能执行“与”、“或”、“非”等逻辑运算。ALU 的基本结构如下图所示:
其中
注意:如对电路基础知识不太熟悉,可参阅电路相关教材的基础部分。对此节电路内容亦不必过分深究,目前统考对电路的要求并不高,很少涉及。
算术移位的对象是有符号数,在移位过程中符号位保持不变。对于正数,由于
表 2.1 不同机器数算术移位后的空位填补规则:
对于带符号数,左移一位若不产生溢出,相当于乘以 2 右移一位,相当于除以 2
从表 2.1 可以得出以下结论:
总结:
逻辑移位将操作数视为无符号数。移位规则如下:
循环移位的特点是移出的数位又被移入数据中,而是否带进位则取决于是否将进位标志位加入循环位移。例如,带进位位的循环左移如图 2.9(d) 所示,即数据位连同进位标志位一起左移,数据的最高位移入进位标志位 CF,而进位位则依次移入数据的最低位。循环移位操作特别适合将数据的低字节数据和高字节数据互换。
在机器内部并没有小数点,只是人为约定了小数点的位置,小数点约定在最左边就是定点小数,小数点约定在最右边就是定点整数。因此,在运算过程中,无需考虑对应的定点数是小数还是整数,只需关心它们的符号位和数值位
补码加减运算规则简单,易于实现。补码加减运算的公式如下(设机器字长为
补码运算的特点如下:
例题: 设机器字长为 8 位(含 1 位符号位),
, ,求 和 。 解:
; 得 。求得 。 所以
符号位为 0,对应真值为
。 符号位为 1,对应真值为
。
已知一个数的补码表示为
图 2.10 中的加法器是带标志加法器。无符号整数的二进制表示相当于正整数的补码表示,因此,该电路同时也能实现无符号整数的加/减运算。对于带符号整数
标志位的含义如下:
零标志
溢出标志
符号标志
进/借位标志
仅当两个符号相同的数相加或两个符号相异的数相减才可能产生溢出,如两个正数相加,结果的符号位为 1(结果为负);一个负数减去一个正数,结果的符号位为 0(结果为正)。补码定点数加减运算溢出判断的方法有三种:
采用一位符号位:由于减法运算在机器中是用加法器实现的,因此无论是加法还是减法,只要参加操作的两个数符号相同,结果又与原操作数符号不同,则表示结果溢出。设
若
采用双符号位:双符号位法也称模 4 补码。运算结果的两个符号位
溢出逻辑判断表达式为:
若
根据数据位的进位情况判断溢出:若符号位的进位
若
加法规则:先判断符号位,若相同,则绝对值相加,结果符号位不变;若不同,则做减法,绝对值大的数减去绝对值小的数,结果符号位与绝对值大的数相同。
减法规则:两个原码表示的数相减,首先将减数符号取反,然后将被减数与符号取反后的减数按原码加法进行运算。注意运算时机器字长,当左边位出现溢出时,将溢出位丢掉。
乘法运算由累加和右移操作实现,可分为原码一位乘法和补码一位乘法。
乘积的数值部分是两个数的绝对值相乘之积。设
由于参与运算的是两个数的绝对值,因此运算过程中的右移操作均为逻辑右移。 考虑到运算过程中部分积和乘数做加法时,可能出现部分积大于 1 的情况(产生进位),但此刻并非溢出,所以部分积和被乘数取双符号位
例题:设
,采用原码一位乘法求 设 过程如下:
符号位
图 2.11 是实现两个 32 位无符号数乘法的逻辑结构图。
在图 2.11 中,部分积和被乘数
Booth 算法是一种有符号数的乘法,采用相加和相减操作计算补码数据的乘积。
设
表 2.2 如下所示:
操作 | ||
---|---|---|
0 | 0 | 部分积右移一位 |
0 | 1 | 部分积加 |
1 | 0 | 部分积加 |
1 | 1 | 部分积右移一位 |
例题:设
,采用 Booth 算法求 设 ,求解过程如下:
所以
图 2.12 是实现 32 位补码一位乘法的逻辑结构图,和图 2.11 所示的逻辑结构很相似。因为是带符号数运算,不需要专门的进位位。每次循环,乘积寄存器
除法运算可转换成”累加-左移“(逻辑左移),分为原码除法和补码除法。
在算术运算中,有时必须把带符号的定点数转换成具有不同位数的表示形式。例如,某个程序需要将一个 8 位整数与另外一个 32 位整数相加,要想得到正确的结果,在将 8 位整数与 32 位整数相加之前,必须将 8 位整数转换成 32 位整数形式,这称为“符号扩展”。 正数的符号扩展非常简单,即符号位不变,新表示形式的所有扩展位都用 0 填充。 负数的符号扩展方法则根据机器数的不同而不同。 原码表示负数的符号扩展方法与正数相同,只不过此时符号位为 1。 补码表示负数的符号扩展方法是:原有形式的符号位移动到新形式的符号位上,新表示形式的所有附加位都用 1(对于整数)或 0(对于小数)填充。
原码不恢复余数法也称原码加减交替除法。特点是商符和商值是分开进行的,减法操作用补码加法实现,商符由两个操作数的符号位“异或”形成。求商值的规则如下:
设被除数
求
例题:设
,采用原码加减交替除法求
因此
,则 余
补码一位除法的特点是,符号位与数值位一起参加运算,商符自然形成。除法第一步根据被除数和除数的符号决定是做加法还是减法;上商的原则根据余数和除数的符号位共同决定,同号上商“1”,异号上商“0”;最后一步商恒置“1”。加减交替法的规则如下:
例题:设
,采用补码交替法求 采用两位符号表示:
因此
,余
图 2.13 是一个 32 位除法逻辑结构图,它和乘法逻辑结构也很相似。
初始时,寄存器
C 语言允许在不同的数据类型之间进行强制类型转换。对于两者都能表示的数值,转换过程中数值本身不发生变化。但是,对于无法表示的数值,转换结果可能会产生不一致性。
观察如下代码:
int main() {
short x = -4321;
unsigned short y = (unsigned short)x;
printf("x=%d, y=%u\n", x, y);
}
在采用补码表示法的机器上,上述代码会输出:
xxxxxxxxxx
x=-4321, y=61215
虽然 y
的值似乎与 x
没有关系,但将这两个数转换为二进制表示时,可以发现其中的规律。表 2.3 显示了 x
为补码表示,y
为无符号的二进制真值。
表 2.3 y
与 x
的对比
变量 | 值 | 二进制表示 |
---|---|---|
x | -4321 | 1110111100011111 (补码) |
y | 61215 | 1110111100011111 (无符号二进制真值) |
强制类型转换的结果保持位值不变,仅改变了解释这些位的方式。
再来看一个 unsigned short
型转换到 short
型的例子:
xxxxxxxxxx
int main() {
unsigned short x = 65535;
short y = (short)x;
printf("x=%u, y=%d\n", x, y);
}
在采用补码的机器上,上述代码会输出:
xxxxxxxxxx
x=65535, y=-1
通过将这两个数用二进制表示,可以证实我们之前得出的结论。
另一种常见的转换是在不同字长的整数之间进行数值转换。
观察如下代码:
xxxxxxxxxx
int main() {
int x = 165537, u = -34991; // int 型占用 4B
short y = (short)x, v = (short)u; // short 型占用 2B
printf("x=%d, y=%d\n", x, y);
printf("u=%d, v=%d\n", u, v);
}
这段程序的输出如下:
xxxxxxxxxx
x=165537, y=-31071
u=-34991, v=30545
x
、y
、u
、v
的十六进制表示分别为 0x000286A1
、0x86A1
、0xFFFF7751
、0x7751
。大字长变量向小字长变量转换时,系统直接截断高位部分,低位直接赋值。因此也是一种保持位值的处理方法。
再来看小字长变量向大字长变量转换的情况:
xxxxxxxxxx
int main() {
short x = -4321;
int y = x;
unsigned short u = (unsigned short)x;
unsigned int v = u;
printf("x=%d, y=%d\n", x, y);
printf("u=%u, v=%u\n", u, v);
}
运行结果如下:
xxxxxxxxxx
x=-4321, y=-4321
u=61215, v=61215
x
、y
、u
、v
的十六进制表示分别是 0xEFAF
、0xFFFFEFAF
、0xEFAF
、0x0000EFAF
。短字长到长字长的转换不仅要使相应的位值相等,还要对高位部分进行扩展:
注意,char
类型为 8 位无符号整数,在转换为 int
时高位补 0 即可。
在存储数据时,数据从低位到高位可以按从左到右排列,也可以按从右到左排列。因此,无法用最左或最右来表征数据的最高位或最低位,通常用最低有效字节(LSB)和最高有效字节(MSB)来分别表示数的低位和高位。例如,在 32 位计算机中,一个 int
型变量 i
的机器数为 01 23 45 67H
,其最高有效字节 MSB=01H,最低有效字节 LSB=67H。
现代计算机基本上都采用字节编址,即每个地址编号中存放 1 字节。不同类型的数据占用的字节数不同。假设变量 i
的地址为 0800H
,字节 01H
、23H
、45H
、67H
应该各有一个内存地址,那么地址 0800H
对应 4 字节中哪字节的地址呢?这就是字节排列顺序问题。
多字节数据都存放在连续的字节序列中,根据数据中各字节在连续字节序列中的排列顺序不同,可以采用两种排列方式:大端方式(big endian)和小端方式(little endian),如图 2.14 所示。
大端方式按从最高有效字节到最低有效字节的顺序存储数据,即最高有效字节存放在前面;小端方式按从最低有效字节到最高有效字节的顺序存储数据,即最低有效字节存放在前面。
在检查底层机器级代码时,需要分清各类型数据字节序列的顺序。例如,以下是由反汇编器(汇编的逆过程,即将机器代码转换为汇编代码)生成的一行机器级代码的文本表示:
xxxxxxxxxx
4004d3: 01 05 64 94 04 08 add %eax, 0x8049464
其中,“4004d3”是十六进制数表示的地址,“010564940408”是指令的机器代码,“add %eax, 0x8049464”是指令的汇编形式。该指令的第二个操作数是立即数 0x8049464
。执行指令时,从指令代码的后 4 字节中取出该立即数,立即数存放的字节序列为 64H
、94H
、04H
、08H
,正好与操作数的字节顺序相反,即采用的是小端方式存储,得到 08049464H
,去掉开头的 0
,得到 0x8049464
。在阅读小端方式存储的机器代码时,要注意字节是按相反顺序显示的。
假设存储字长为 32 位,可按字节、半字和字寻址。对于机器字长为 32 位的计算机,数据以边界对齐方式存放,半字地址一定是 2 的整数倍,字地址一定是 4 的整数倍,这样无论所取的数据是字节、半字还是字,均可一次访存取出。
所存储的数据不满足上述要求时,通过填充空白字节使其符合要求。这样虽然浪费了一些存储空间,但可提高取指令和取数的速度。若不对齐,则可能需要两次访存,并且对高低字节的位置进行调整、连接之后才能得到所要的指令或数据,从而影响了指令的执行效率。
例如,“字节 1、字节 2、字节 3、半字 1、半字 2、半字 3、字 1”的数据按序存放在存储器中,按边界对齐方式和不对齐方式存放时,格式分别如图 2.15 和图 2.16 所示。
边界对齐方式相对边界不对齐方式是一种空间换时间的思想。精简指令系统计算机(RISC)通常采用边界对齐方式,因为对齐方式取指令时间相同,因此能适应指令流水。
通常,浮点数表示为
可见浮点数由数符、尾数和阶码三部分组成。图 2.17 是一个 32 位短浮点数格式的示例。
其中,第 0 位为数符
原码是关于原点对称的,故浮点数的范围也是关于原点对称的,如图 2.18 所示。
当运算结果大于最大正数时称为正上溢,小于绝对值最大负数时称为负上溢,正上溢和负上溢统称为上溢。数据一旦产生上溢,计算机必须中断运算操作,进行溢出处理。当运算结果在 0 至最小数据值之间时,浮点数值趋于零,计算机仅将其当作机器零处理。
尾数的位数决定浮点数的有效数位,有效数位越多,数据的精度越高。为了在浮点数运算过程中保证非零浮点数在尾数的最高数位上是一个有效值,需要进行规格化操作。
规格化浮点数的尾数
按照 IEEE 754 标准,常用的浮点数格式如图 2.19 所示。
IEEE 754 标准规定常用的浮点数格式有单精度(float 型)、双精度(double 型)和临时浮点数,基数隐含为 2,见表 2.4。
表 2.4 IEEE 754 浮点数的格式
类型 | 数符 | 阶码 | 尾数 | 总位数 | 偏置值 |
---|---|---|---|---|---|
单精度 | 1 | 8 位 | 23 位 | 32 位 | 127 |
双精度 | 1 | 11 位 | 52 位 | 64 位 | 1023 |
临时浮点数 | 1 | 15 位 | 63 位 | 80 位 | 16383 |
在浮点格式中,23 位尾数是纯小数。对于规格化的二进制浮点数,数值的最高位总是“1”,为了能使尾数多表示一位有效位,将这个“1”隐藏,称为隐藏位,因此 23 位尾数实际上表示了 24 位有效数字。
浮点数真值计算公式:
其中,单精度浮点数
表 2.5 IEEE 754 浮点数的范围
格式 | 最小值 | 最大值 |
---|---|---|
单精度 | ||
双精度 |
浮点数运算的特点是阶码运算和尾数运算分开进行,浮点数加减运算分为以下几步。
对阶的目的是使两个操作数的小数点位置对齐,即使得两个数的阶码相等。步骤如下:
尾数右移时,舍弃掉有效位会产生误差,影响精度。
将对阶后的尾数按定点数加(减)运算规则进行运算。运算后的尾数不一定是规格化的,因此,浮点数的加减运算需要进一步进行规格化处理。
IEEE 754 规格化尾数的形式为
注意:
运算结果需要进行舍入处理,并还原表示成 IEEE 754 格式。常见的舍入方法有:
在尾数规格化和尾数舍入时,可能会对阶码执行加/减运算,因此必须考虑指数溢出的问题。
右规和尾数舍入:数值很大的尾数舍入时,可能因为末位加 1 而发生尾数溢出,此时需要通过右规来调整尾数和阶码。右规时阶码加 1,需要判断是否发生了指数上溢。当调整前的阶码为 11111110
时,加 1 后,会变成 11111111
而发生指数上溢。
左规:左规的阶码减 1,需要判断是否发生了指数下溢。其判断规则与指数上溢类似,左规一次,阶码减 1,然后判断阶码是否为全 0 来确定是否指数下溢。
浮点数的溢出并不是以尾数溢出来判断的,尾数溢出可以通过右规操作得到纠正。运算结果是否溢出主要看结果的指数是否发生了上溢,因此是由指数上溢来判断的。
注意:某些题目可能会指定尾数或阶码采用补码表示。通常采用双符号位,当尾数求和结果溢出(如尾数为 10.xxxx
或 01.xxxx
)时,需右规一次;当结果出现 00.0xxxx
或 11.1xxxx
时,需要左规,直到尾数变为 00.1xxxx
或 11.0xxxx
。
C 语言中的 float
和 double
类型分别对应于 IEEE 754 单精度浮点数和双精度浮点数。long double
类型应为扩展双精度浮点数,但 long double
的长度和格式随编译器和处理器类型的不同而异。
在不同数据类型之间转换时,往往隐藏着一些不容易察觉的错误,编程时要非常小心。
408 不考,但是哈工程的 811 考