计算机组成原理 第二章 数据的表示和运算
🚥计算机组成原理 系列文章导航🚥
一、进位计数制及其相互转换
1.进位计数制
r进制计数法,逢r进一,这里的r为基数。
基数:每个数码位所用到的不同符号的个数,r进制的基数为r。
二进制(Binary)数的表示方法:11002B
十进制(Decimalism)数的表示方法:985D
十六进制(Hexadecimal)数的表示方法:9494H 或 0x9494 或 0X9494
计算机系统内部采用二进制的原因
- 二进制只有两种状态,使用有两个稳定状态的物理器件就可以表示二进制数的每一位,制造成本比较低。
- 二进制位 1 和 0 正好与逻辑值“真”和“假”相对应,为计算机实现逻辑运算和程序中的逻辑判断提供了便利条件。
- 二进制的编码和运算规则都很简单,通过逻辑门电路能方便地实现算术运算。
2.不同进制数之间的相互转换
花里胡哨,实际上熟悉每一位的权重自己拼凑更快。
一定要熟悉各个位的权重:
位数 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
权重 | 32768 | 16384 | 8192 | 4096 | 2048 | 1024 | 512 | 256 | 128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
2 的 n 次方 | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
二级制数转换为八进制和十六进制数:以小数点为界,每 3 位(八进制)或 4 位(十六进制)一组,分别用对应的八进制或十六进制数取代。不足的部分,整数部分高位补 0,小数部分低位补 0。
十进制小数转换为二进制小数:整数部分使用除基取余法,小数部分使用乘基取整法。
除基取余法顾名思义,乘基取整法如下图所示:
因此 0.3 用二进制表示为 0.01001…B,后面无限循环1001,这里精确到小数点后五位。
注意:在计算机中,小数和整数不一样,整数可以连续表示,但小数是离散的,所以并不是每个十进制小数都可以准确地用二进制小数表示,例如这里的 0.3 就无法得到精确的结果。但每一个二进制小数都可以用十进制小数精确表示。
二、定点数的编码表示
1.真值和机器数
真值:生活中,通常用正负号来分别表示整数和负数。+7,-8 这种带正负号的数称为真值。真值是机器数所代表的实际值。
机器数:计算机中,通常把数据的符号和数字部分一起编码,将符号数字化,通常用 0 表示正,用 1 表示负。这种把符号数字化的数称为机器数。
2.定点数和浮点数
根据小数点的位置是否固定,在计算机中有两种数据格式:定点表示和浮点表示
定点数:小数点的位数固定,如 996.007(常规计数)
浮点数:小数点的位置不固定,如9.96007*10^2(科学计数法)
定点数又分为无符号数和有符号数,有符号数又可以用原码、反码、补码、移码四种方式表示。
现代计算机中,通常用补码整数表示整数,用原码小数浮点数的尾数部分,用移码表示浮点数的阶码部分。
3.机器数的定点表示
(1)无符号数的表示
无符号数:所有的二进制位数均为数值位,没有符号位
n 位 r 进制数可表示 r^n 个无符号数,范围为 0 到 r^n-1。
通常无符号数只有整数,不存在无符号小数(例如 C 语言中 unsigned 只能用来修饰整数相关的关键字,不能修饰 float)。
(2)有符号数的定点表示
定点表示法用来表示定点小数和定点整数:
-
定点小数是纯小数,约定小数点在符号位之后。
-
定点整数是纯整数,约定小数点位置在有效数值部分最低位之后。
注意:机器内部实际上并无小数点,只是人为约定。此外,可以用原码、反码补码表示定点整数和定点小数,用移码表示定点整数。
对于定点整数我们常用逗号区分符号位和有效数值部分:

对于定点整数我们常用点号区分符号位和小数部分:

因此这里的 1.11 的真值实际上并不是 1 点 11,而是负 0 点 11。
(3)原码
原码:用机器的最高位表示数的符号,其余各位表示数的绝对值。实际上我们上面两张图片里的就是使用的原码表示法。
原码整数和小数的表示范围如下图所示:


优点 | 缺点 |
---|---|
与真值的对应关系简单、直观,与真值的转化简单。 | 0 的表示不唯一,有 +0 和 -0 两个编码。 |
用原码实现乘除运算比较简单。 | 用原码实现加减运算比较复杂。 |
(4)反码
反码:在原码的基础上,若符号位为0,则反码与原码相同;若符号位为1;则数值位全部取反。
因此反码与原码所能表示的范围相同,且也存在 +0 和 -0两种表示形式。
在计算机中,反码是原码转换到补码的中间形式,很少拿来使用。
(5)补码
补码:正数的补码 = 原码;负数的补码 = 反码末位 + 1(要考虑进位)
0 的补码是唯一的,因此补码可以比原码多表示一个数字。

补码的作用:使用补码可以将减法操作转变为等价的加法,ALU中无需集成减法器,执行加法操作时,符号位一起参与运算。
由 [x]补 快速求 [-x]补 的技巧:符号位、数值位全部取反,末位 +1。
负数的原码补码转换技巧:最右边的 1 及其右边部分保持不变,剩下的数值位全部取反。
说到补码就不得不拿出 CSAPP 上的这张图了,设计这个图的人简直就是天才:

(6)移码
移码:在补码的基础上将符号位取反。
因此移码和补码的表示范围相同,且 0 也只有一种表示形式。
注意:移码常用来表示浮点数的阶码,它只能表示整数,不能表示小数
优点:便于计算机比较大小,如下图所示。
移码的定义:移码 = 真值 + 偏置值
例如八位数的移码偏置值为 2^7 = 128D = 1000 0000B,即2^(n-1)。
若真值为 -127,则移码为 -127 +128 = -11111111 + 10000000 = 0000 0001
(7)四种编码总结
符号位为 0 时,原码等于反码等于补码;符号位为 1 时,原码和补码之间相互转换的方式相同,均为数值位按位取反再加一。
4.神奇的补码
补码的作用:使用补码可以将减法操作转变为等价的加法,ALU中无需集成减法器,执行加法操作时,符号位一起参与运算。
为什么补码能够实现这样的功能呢?
以 4 位的补码为例,因为只能存储 4 位数字,所以它本质上相当于对数字进行取模运算,模为 2 的 4 次方,即为 16。而后将得到的余数存储下来(这里需要了解负数的取模运算规则,例如 -5 对 16 取模,得到的余数为11)。
对于真值 b,我们定义 b 的补数为 模 + b = b的补数。实际上,这里 b 的补数就是补码。
可以看到,当 b 为正值时,直接溢出,进行取模运算;当b为负值时,补数相当于 模 - |b|。
对于 b < 0,如果我们想要运算 a + b,只需要计算 a + b的补数 即可。因为代入我们上面对补数的定义可以得到下面的公式:
a + b的补数 = a + 模 + b = a + b,因为计算机会自动抛弃溢出的位数(假设 0 < a + b < 模)。
举个栗子,a 取 5,b 取 -3 ,-3 的补数为 模 + (-3) = 16 +(-3)= 13 = 1101。
可以看到 1101实际上就是我们定义的 -3 的补码。
那么,5 +(-3) = 0101 + 1101 = 10010,计算机自动保留四位,得到结果为 0010,即为 2。
此外,从这个公式我们也能看出为什么负数补码和原码的转换方式是数字位取反再加一:
同样以 4 位补码为例,b 的补数 - b = 模 = 16 = 1111 +1,那么 b 的补数 + (|b| -1) = 1111
-3 的补数 1101 和什么样的数相加才能得到 1111呢?答案是,0010。
所以 |b| - 1 = 0010,|b| = 0010 + 1 = 0011,那 b 的原码不就是 1011吗?
反过来原码转换成补码我们可以用(b 的补数 - 1)+ |b| = 1111,只需要改变一下 -1的位置就行了。
感觉写的有点乱啊,拼尽全力只能这样了。
5.C语言强制类型转换
C语言中的有符号数以补码的形式存储,chart型默认按照无符号数整数解释。
无符号变有符号:二进制各个位数的值不变,仅改变解释这些位的方式。
长整数变短整数:直接高位截断,只保留低位,简单粗暴。
短整数变长整数:无符号数进行零扩展,高位用零填充;有符号数进行符号扩展,高位部分用原数字的符号位填充(0 或 1)。
三、基本运算部件
补充常用逻辑门电路:
1.一位全加器
一位全加器(FA,full adder) 是最基本的加法单元,有加数 A_i、加数 B_i 与低位传来的进位 C_i-1 共三个输入,有本位和 S_i 及向高位的进位 C_i 共两个输出。
2.串行进位加法器
将 n 个 FA 相连可以得到 n 位加法器,称为串行进位加法器。
在串行进位加法器中,低位运算的产生进位所需的时间将影响高位运算的时间,位数越多,延迟时间就越长。
3.并行进位加法器
并行进位(也称先行进位)加法器可以加快进位产生的速度,进而提升加法运算产生结果的速度。
从前面的串行进位加法器中我们可以得到:
一直展开到 C_0,我们可以发现,第 i 位向更高位的进位 C_i 其实从一开始就能够根据被加数、加数的第 1 至 i 位,再结合 C_0 得到,而不用等待前一位的进位。
我们可以按照这种思路设计出一个四位并行进位加法器:


构造一个 n 位并行进位加法器,需要将 n 个一位全加器连接上 n 位先行进位部分(简称 CLA 部件),其作用是“并行产生进位”,即 n 位进位信息几乎是同时产生的。当计算更多位时,我们可以选择将多个 4 并行进位加法器进行串联。
4.带标志加法器
在 n 位加法器的基础上还会再增加一些逻辑电路,使其不仅能计算和/差,还能生成相应的标志信息,分别是:
-
OF(Overflow Flag)溢出标志,用于判断有符号数的加减运算是否溢出。0 表示未溢出,1 表示溢出。
-
SF(Sign Flag)符号标志,用于表示有符号数的加减运算结果的正负性。 0 表示正,1 表示负。
-
ZF(Zero Flag)零标志,用于判断加减运算结果是否为0。1 表示结果为 0,0表示结果非 0。
-
CF(Carry Flag)进位/借位标志,用于判断无符号数的加减运算是否溢出。0 表示未溢出,1表示溢出。
5.算术逻辑单元(ALU)
ALU 是运算器的核心,加法器是 ALU 的核心。
ALU的运算数,运算结果位数与计算机的机器字长相同。
ALU 的功能包括:
-
算术运算 - 加、减、乘、除 等
-
逻辑运算 - 与、或、非、异或、移位 等
-
其他 - 求补码、直送(即不做任何操作)等
ALU 的基本结构如上图,op 信号用来控制 ALU 所执行的功能,其位数决定了操作数的种类。例如,位数为 3 时,ALU 最多只能有 8 种操作。
四、定点数的运算
1.定点数的移位运算
(1)逻辑移位
将操作数视为无符号整数,直接整体左移或右移,空位补 0,简单粗暴。
(2)算术移位
算术移位的一个特点是,移位后符号位不会改变(注意说的是符号位不会改变,而不是符号位不会移动)。
左移可能会溢出,右移可能会丢失精度。
实际上想象成对真值进行乘 2 ,除 2 运算,自然就知道要补 1 还是 0 了。
(3)循环移位
循环移位即为将移出的那一位再填补到另一侧的空位上去。
对于一些有进位位的情况,将进位位视为普通的比特位即可,例如
1 01110101,这里标粗的位置是进位位,循环左移结果为 0 11101011。
2.定点数的加减运算
(1)补码加减运算
(2)溢出判断方法
只有以下两种情况才会导致溢出:
正 + 正 = 负(上溢);负 + 负 = 正(下溢)
方法一:根据操作数和结果的符号判断
若 V = 0,则未溢出;若 V = 1,则发生溢出。
方法二:根据进位判断
若符号位的进位 Cs 与最高数值位的进位 C1 不同,则发生溢出,相同则没有溢出。
若 V = 0,则未溢出;若 V = 1,则发生溢出。
方法三:双符号位判断
双符号位补码又称模 4 补码,单符号位补码又称模 2 补码。
运算结果的两个符号位 Ss1 和 Ss2 相同,则未溢出,不同则发生溢出,此时最高位符号位代表真正的符号(实际上原理与方法二相同)。
若 V = 0,则未溢出;若 V = 1,则发生溢出。
注意:对于双符号位补码,实际存储时只存一个符号位,运算时会复制一个符号位,因此不会增加存储所需要的空间。
(3)无符号数加减运算
加法:直接按位相加,同补码
减法:减数全部位按位取反,末尾加一,减法变加法。
注意:本质上和补码的加减运算是一样的,因为减数的全部位按位取反再加一后,得到的结果就是减数的相反数的补码。而对于正数来说,原码和补码是相同的,所以依旧可以将其视为补码的加减运算。
溢出判断:n bit 无符号整数运算结果是否落在 0 到 2^n - 1 范围内
-
加法:最高位产生的进位 = 1 时,发生溢出,否则未溢出。
-
减法:减法变加法,最高位产生的进位 = 0 时,发生溢出,否则未溢出(因为最高位进行的运算是 0 + 1,如果没有进位,说明结果的符号位是 1,为负数,不在表示范围内)。
(4)补码加减运算电路

补码加减运算电路如上图所示,Sub(Subtraction) 信号用于控制电路进行加法还是减法运算,0 表示加法,1 表示减法。当进行加法运算时,加数 B 不会被进行任何处理;进行减法运算时,B 先被按位全部取反,同时,Sub 信号还与 Cin 相连,相当于还会再进行一次加一的操作。
因为无符号数加减运算本质上和补码相同,因此该电路同样也能用于无符号数加减运算。
下图为四种标志信息的逻辑电路:
3.定点数的乘除运算
本节中,n 均指数值位的位数。
(1)原码的乘法运算
原码乘法的特点是:乘积的符号位由两个乘数的符号位异或得到;乘积的数值位是两个乘数的绝对值之积。
原码一位乘
在进行运算时,需要先清空 ACC 寄存器,此时 MQ 寄存器中存储的是乘数。
第一步,MQ 寄存器中的最低位与 X 寄存器中的被乘数相乘,结果加到 ACC 寄存器中。
第二步,将 ACC 寄存器和 MQ 寄存器中的数视为一个整体,进行逻辑右移。
就这样每次都先进行加法,然后再进行移位,重复 n 次,n 为数值位的长度,最后得到结果,乘积的高位存储在 ACC 中,低位存储在 MQ 中。
(2)补码的乘法运算
需要知道所以然吗?
补码一位乘(booth算法)
补码一位乘的规则是进行 n 轮加法、移位(注意是算术右移),最后再多来一次加法。
根据辅助位(初始为 0)和 MQ 最低位来决定加数的内容:
-
辅助位 - MQ 最低位 = 1 时,(ACC)+ [x]补
-
辅助位 - MQ 最低位 = 0 时,(ACC)+ 0
-
辅助位 - MQ 最低位 = -1 时,(ACC)+ [-x]补
注意:图中红色比特位我们称之为辅助位,虽然他也在 MQ 寄存器,但为了统一,我们依旧将灰色比特位称为 MQ 最低位,在进行算术右移时,ACC 和 MQ(包括辅助位)的内容看做一个整体进行右移。
(3)阵列乘法器
阵列乘法器通过与门和一位全加器 FA 的组合来模仿乘法手算的过程:
参考上图我们可以发现:
-
该阵列乘法器共需全加器 n(n - 1) 个,与门 n*n 个。
-
前 n -1 行 FA 在行内之间没有进位依赖,并行执行。
-
最后一行是 n - 1 个 FA 构成的 n - 1 位串行进位加法器,互相之间存在进位依赖。
-
每一行 FA 都依赖于上一行 FA 的进位。
为了提高速度,还可以将第一行 FA 和最后一行的最后一个 FA 替换为半加器 HA(可以看到这些 FA 的输入均为固定的 0)。此外,还可以将最后一行的 FA 改用先行进位(并行进位)电路,进一步优化运算速度。
阵列乘法器结构规范,标准化程度高,有利于布局步线,适合用超大规模集成电路实现,且可以获得较高的运算速度,其运算速度仅取决于逻辑门和加法器的传输延迟。
(4)原码的除法运算
原码除法的特点是:商的符号位由被除数和除数的符号位异或得到;商的数值位是这两个数的绝对值之商。
恢复余数法

初始情况下如上图所示,MQ 寄存器清零,从末位开始上商。
但计算机不会直接判断商 1 还是商 0,而是选择先默认商1,然后求余数(ACC)-(除数)-> ACC。
以改图为例就是计算出 01101 的负数的补码为 10011,然后将其与(ACC)相加,得到结果为 11110,首位为 1,说明是负数,不应该商 1。
于是计算机将商上去的 1 改为 0,再恢复余数(ACC)-(除数)-> ACC。
(什么?你问我为什么原码除法里涉及了补码?计算机:什么原码补码,我不到啊,我只会算二进制加法。)
如果求出的余数是正数,则说明商 1 是正确的,不需要修改。
确定了商之后,将 ACC 和 MQ 的内容视作整体进行左移,然后继续在 MQ 的末尾上商。
以此类推,共计上商 n+1 次,逻辑左移 n 次。
注意:如果最后一步上商后余数为负,依旧需要恢复余数并改商 0 。
最后结果如上图所示,求得商为 0.1101,余数为 0.0111*2^(-n),因为共左移 n 次。
不恢复余数法(加减交替法)
回顾恢复余数法的过程,在发现余数 a 为负后,选择进行加 b 左移再减 b,最后得到新的余数。我们可以将这三步合并为两步,直接进行左移然后加 b,这就是不恢复余数法(也称加减交替法)。
-
恢复余数法:当余数为负时商 0,并 + |除数|,再左移,再 - |除数|(三步)
-
加减交替法:当余数为负时商 0,并左移,再 + |除数|(两步)
注意:虽然这种方式叫不恢复余数法,但如果最后得到的余数为负,依旧要改商 0,并加上 [|y|] 补得到正确的余数。也就是说依旧有可能会出现恢复余数的步骤。
(5)补码的除法运算
补码除法的特点:符号位参与运算,被除数(余数)、除数均采用双符号位。
具体计算流程为:
-
被除数和除数同号,则被除数减去除数;异号则被除数加上除数。
-
余数和除数同号,商 1,余数左移一位减去除数。
-
余数和除数异号,商 0,余数左移一位加上除数。
-
重复进行 n 次。
-
末尾商恒置为 1(省事且精度误差不超过 2^(-n))。
注意:因为补码的加减交替法最后的末位商恒置为1,因此不会出现恢复余数的情况。
原码、补码加减交替法的比较
除法类型 | 符号位参与运算 | 加减次数 | 移位情况 | 上商、加减原则 | 说明 |
---|---|---|---|---|---|
原码加减交替法 | 否 | n + 1 或 n + 2 | 左移 n 次 | 余数的正负 | 若最终余数为负,则需恢复余数 |
补码加减交替法 | 是 | n + 1 | 左移 n 次 | 余数和除数是否同号 | 末位商恒置 1 |
五、浮点数的表示与运算
1.浮点数的表示
(1)浮点数的表示格式
一般浮点数的表示为:
S 取 0 或 1,来决定浮点数的符号;M 是个二进制定点小数,称为尾数;E 是一个二进制定点整数,称为阶或者指数;R 是基数,默认为 2,也可以是 4、8 等。
以下图为例,0 号位为 S,1-7 位为移码表示的阶码 E(偏置值为 64);8-31 位为 24 位二进制原码小数表示的尾数 M;基数 R 为 2。

(2)浮点数的表示范围
原码是关于原点对称的,所以这种方式下的补码也是关于原点对称的:

数据一旦产生上溢,计算机必须中断运算操作,进行溢出处理;数据下溢时,浮点数值趋于零,计算器将其当做机器零处理。
(3)浮点数的规格化
为了使有效数字尽量占满尾数位数,需要进行规格化操作。对于规格化的浮点数,要求尾数的最高数值位必须是一个有效值(对于原码来说 1 为有效值,对于补码来说与符号位相反的数为有效位)。
-
左规:当运算结果的尾数的最高位不是有效位时,进行左规,尾数每左移一位、阶码减一。左规可能要进行多次。
-
右规:当运算结果的尾数的有效位进到小数点前面时,进行右规(双符号位为 10 或 01)。尾数右移一位、阶码加一。右规只需进行一次,且右规时可能会导致溢出。
左规的时候会出现溢出吗?下溢算吗?
格式 | 最大值 | 最小值 | |
---|---|---|---|
原码正数 | 0.1**** | 0.11…1 | 0.10…0 |
原码负数 | 1.1**** | 1.10…0 | 1.11…1 |
补码正数 | 0.1**** | 0.11…1 | 0.10…0 |
补码负数 | 1.0**** | 1.01…1 | 1.00…0 |
注意:这里讨论的情况为阶码的基数(底)r 为 2 的情况。当 r 为 4 时,每次尾数每次要移动两位,规格化后要求最高两位不全为无效位。
2.IEEE 754 标准
(1)IEEE 754 标准
IEEE 754 标准提供了两种基本浮点格式:32 位单精度(float)和 64 位双精度(double)格式。
类型 | 符号s | 阶码e | 尾数f | 总位数 | 偏置值 |
---|---|---|---|---|---|
单精度 | 1 | 8 | 23 | 32 | 127(即 2^7 - 1) |
双精度 | 1 | 11 | 52 | 64 | 1023(即 2^10 - 1) |
IEEE 754 规定如下:
-
阶码用移码表示,尾数用原码表示。
-
移码的偏置常数采用 2^(n-1) - 1,而不是通常使用的 2^(n-1)。
-
省略尾数的第一位 1,称为隐藏位,使得单精度的 23 位尾数实际上表示了 24 位有效数字(精度更高)。
注意:IEEE 754 规定的隐藏位 1 的位置在小数点之前。例如对于 -0.75,其绝对值的二进制编码为 0.11,需要将其左规为 1.10,然后省略小数点前的 1,将小数点后的 10 存入尾数 f 部分(符号位是存放于 s 中的)。
(2)特殊的偏置值
移码 = 真值 + 偏置值
因为 IEEE 754 标准中规定移码的偏置值为 2^(n-1) - 1,所以其会与常规的补码有所区别:

其中,全 1 和全 0 的移码(分别对应真值 -128 和 -127)用作其它用处,因此单精度浮点数 8 位的阶码所能表示的范围只有 -126 到 127(双精度浮点数的 11 位阶码所能表示的范围为 -1022 到1023)。
阶码全 1 和全 0 时的特殊含义
下表以单精度浮点数为例:
值的类型 | 符号 | 阶码 | 尾数 | 值 |
---|---|---|---|---|
正零 | 0 | 0 | 0 | 0 |
负零 | 1 | 0 | 0 | 0 |
正无穷大 | 0 | 255(全 1) | 0 | 正无穷 |
负无穷大 | 1 | 255(全 1) | 0 | 负无穷 |
无定义数 | 0 或 1 | 255(全 1) | 非 0 | NaN |
规格化非零正数 | 0 | 0 < e < 255 | f | 2^(e - 127)(1.f) |
规格化非零负数 | 1 | 0 < e < 255 | f | -2^(e - 127)(1.f) |
非规格化正数 | 0 | 0 | f 不为 0 | 2^(-126)(0.f) |
非规格化负数 | 1 | 0 | f 不为 0 | -2^(-126)(0.f) |
1.全 0 阶码,全 0 尾数
IEEE 754 的零有正负两种,主要取决于数符 s,一般情况下两者是等效的。
2.全 0 阶码,非全 0 尾数:非规格化数
非规格化的特点是阶码为全 0,尾数高位有一个或几个连续的 0,但不全为 0。因此,非规格化数的隐藏位为 0,并且单精度和双精度浮点数的指数分别为 -126 或 -1022。
非规格化数可用于处理阶码下溢,使得出现比最小规格化还小的数时程序也能继续进行下去。
下图为引入非规格化数后,可表示范围的变化:
此外,从这张图中我们还可以发现,每个右边区间内相邻数间的距离总比左边一个区间的相邻数距离大一倍,因此离原点越近的区间内的数的间隙越小。
3.全 1 阶码,全 0 尾数:正负无穷
正无穷在数值上大于所有有限数,负无穷在数值上小于所有有限数。引入无穷大数的目的是,在计算过程出现异常的情况下使得程序能继续进行下去。
4.全 1 阶码,非 0 尾数:NAN(Not a Number)
5.非全 0 且非全 1 阶码:规格化非 0 数
注意:偏置值采用 127 而不是标准的 128 还有一个原因就是,采用偏移常数表示的最小规格化数的倒数会发生溢出,而采用偏移常数 127 表示的任何一个规格化数的倒数则不会溢出。例如阶码为 -128 时,浮点数的倒数阶码为 128,超越最大值发生溢出。而在 -126 到 127范围内则不会出现这种现象,即使阶码取 127,其倒数也只是会是非规格化数,而不会发生溢出。
3.浮点数的加减运算
浮点数运算的特点是:阶码运算和尾数运算分开进行
1.对阶
对阶使得两个数的阶码相同,遵循小阶向大阶看齐的原则。
注意:如果采用大阶向小阶看齐的原则,则尾数需要左移,最高有效位被移出会导致结果出错。
2.尾数加减
一般采用双符号位表示位数,这样可以挽救尾数溢出的情况。
对于 IEEE 754 标准下的浮点数,进行尾数加减时,必须将隐藏位还原到尾数部分。
3.规格化
-
左规:尾数最高数值位为无效位时,尾数左移。
-
右规:尾数双符号位不同时,尾数右移。
4.舍入
在进行对阶和尾数右移时,为保证运算精度,一般会将移出的位保留下来,参加中间过程的运算,最后再将运算结果进行舍入,还原表示成 IEEE 754 格式。例如有的计算机会把浮点数的位数部分单独拆出去计算(24bit -> 32bit),算完了经过舍入(32bit -> 24bit)再拼回浮点数(因为隐藏位的存在,float 用 23 位的位数表示 24 位的精度)。
IEEE 754 提供了一下 4 种可选的舍入模式:
-
就近舍入:舍入为最近的可表示数,即 0 舍 1 入。
-
正向舍入:超数轴正无穷方向舍入,即取右边最近的可表示数。
-
负向舍入:超数轴负无穷方向舍入,即取左边最近的可表示数。
-
截断法:直接截断所需位数,最简单,是一种趋向原点的舍入。
5.溢出判断
在进行尾数规格化和尾数舍入时,可能会对结果的阶码进行加减运算。当阶码超过最大允许值时(127 或 1023),会发生指数上溢时,机器产生异常;当阶码超过最小允许值时(-149 或 -1074),则发生指数下溢,将结果置为机器零(数符不变)。
注意:对于非规格化数的情况,当尾数 f 为 0.0…01 时,指数的最小允许值为 -126 - 23 = -149 或 -1022 - 52 = -1074。
4.C语言中的浮点数类型
32 位机器中不同变量类型的大小:
类型 | char | short | int | long | long long | float | double |
---|---|---|---|---|---|---|---|
比特数 | 8 | 16 | 32 | 32 | 64 | 32 | 64 |
在 C 程序中,等式的赋值和判断会导致强制类型转换,以 char -> int -> long -> double 和 float -> double 最为常见,从前到后范围和精度都从小到大,转换过程没有损失。
不同类型数的混合运算时,遵循的原则是类型提升,如 float 和 double 一起运算,会先将 float 转换为 double 后再进行运算,结果为 double 型。由于这些转换是系统自动进行的,因此称为隐式类型转换。
注意:一般情况下,我们默认计算机为 32 位。64 计算机不同变量的大小与 32 位基本一致,但 long 类型为 64 比特。因此,对于 64 位计算机,long 类型转换为 double 类型会损失精度,因为 double 的 52 位尾数最多只能表示 53 位精度(包括一个隐含位)。
5.数据的大小端和对齐存储
(1)大小端存储
-
大端存储:先存储高位字节,后存储低位字节。字节中的字节顺序和原序列相同(便于人类阅读)。
-
小端存储:先存储低位字节,后存储高位字节。字节中的字节顺序和原序列相反(便于机器处理)。
因为数据有大端和小端两种存储方式,无法用左右进行表述,因此我们通常用最低有效字节(LSB) 和最高有效字节(MSB) 来分别表示数据的低位和高位。
(2)边界对齐方式
现代计算机都是按字节编址的,假设字长为 32 位,数据按边界对齐方式存放要求其存储地址是自身大小的整倍数,半字地址一定是 2 的整倍数。字地址一定是 4 的整倍数,这样无论所取的数据是字节、半字还是字,均可一次访存取出(空间换时间的思想)。
C 语言的 struct 类型中,边界对齐有两个重要要求:
-
每个成员按其类型大小对齐,char 型的对齐值为 1,short 型的对齐值为 2,int 型的对齐值为 4,单位为字节。
-
struct 的长度必须是成员中最大对齐值的整倍数(不够就补空字节)。
因此两个包含变量相同但定义顺序不同的 struct 的长度可能会不同。
精简指令系统计算机 RISC 通常采用边界对齐方式,因为边界对齐方式取指令时间相同,能够适应流水线。