基于里昂学长的计算机组成原理课程。
笔记目录
第一章-计算机系统概述
第二章-数据的表示和运算
第三章-存储系统
第四章-指令系统
第五章-中央处理器
第六章-总线
第七章-输入输出系统
第二章 数据的表示和运算
2.1 数制与编码
2.1.1 进位计数法
在进位计数法中,每个位数所用到的不同数码的个数称为基数。10进制的基数就是10。计算机中通常使用二进制数、八进制数、十六进制数。
每个数码表示的数值等于该数码本身乘以一个与它所在数位有关的常数,这个常数称为位权。
一个r进制数($K_nr^nK_{n-1}r^{n-1}\ldots K_0r^0K_{-1}r^{-1}\ldots K_{-m}$)的数值可以表示为
$K_{n}r^{n}+K_{n-1}r^{n-1}+\ldots+K_{0}r^{0}+K_{-1}r^{-1}+\ldots+K_{-m}r^{-m}=\sum^{-m}_{i=n}K_ir^i$
式子中r是基数,$r^i$是第i位的位权。
为了区分不同的进制的数,一般用后缀作区分。
进制 | 英文 | 数码范围 | 后缀 | 示例 |
---|---|---|---|---|
二进制 | Binary | 0-1 | B | 110 B |
八进制 | Octal | 0-7 | O | 211 O |
十进制 | Decimal | 0-9 | D | 101 D |
十六进制 | Hexadecimal | 0-F | H | BE H |
十六进制也可以用前缀0x标识,例如0xBE58。
采用二进制编码的原因
- 二进制只有两种状态,使用有两个稳定状态的物理器件就可以表示二进制的每一位,制造成本比较低
- 1和0正好与逻辑值“真”“假”对应,为逻辑运算和程序的逻辑判断提供了便利条件。
- 二进制的编码和运算规则都很简单,通过逻辑门电路可以方便地实现算数运算。
二进制转八进制
由于$2^3=8$,可以将三维二进制转换成一位八进制数。
转换过程:
- 转换时以小数点为界
- 整数部分从右往左数每3位一组,不足3位补0
- 小数部分从左往右数每3位一组,不足3位补0
- 每3为用对应的八进制数取代
二进制转十六进制
同二进制转八进制,改成每4组一位。
十六进制转二进制
反过来就行,每一位展开拆成4个,八进制同理,展开拆成3个即可。
十进制和其他进制之间的转换
整数部分和小数部分分开处理,整数部分除基取余,小数部分乘基取整。
- 整数部分(除基取余法)
设整数部分为$K_nr^nK_{n-1}r^{n-1}\ldots K_0r^0$,写出展开式
$K_{n}r^{n}+K_{n-1}r^{n-1}+\ldots+K_{0}r^{0}$
每次除以r得到一个余数,然后把余数放到结果的最左边,然后用商继续除以r,直到商为0。
- 小数部分(乘基取整法)
将小数部分的展开式乘以r,得到整数部分就放到结果的最右边,然后继续用小数部分乘以r,直到小数部分为0。
2.1.2 真值和机器数、定点数和浮点数
真值和机器数
带有正负号的数我们都称为真值,正号可以被省略。计算机中正负号也用0和1表示,一般用0表示正号,用1表示负号。把真值转换为01序列后存储到计算机中,此时对应的01序列称为机器数。
定点数和浮点数
整数的小数点固定在数的最右边,计算机无法表示小数点。
通常通过约定小数点的位置解决这个问题。
约定小数点在固定位置,称为定点数。
约定小数点在不固定位置,称为浮点数。
在现代计算机中,通常用补码整数表示整数,用原码小数表示浮点数的尾数部分,用移码表示浮点数的阶码部分。
2.1.3 原码表示法(定点整数)
当约定小数点在数值最低位后面时寄存器中存储的就是定点整数。
原码表示法:用最高位表示符号,其余各位表示数的绝对值。
若字长为n+1,则表示范围为$-(2^n-1)-(2^n-1)$,总共可以表示$2^{n+1}-1$个数。
这种表示方法简单直观,但是缺点在于0000和1000都表示同一种状态,浪费了一个状态。除此之外,进行异号数相加时,必须判断两个数的绝对值大小,根据结果判断符号,并用绝对值大的数减去绝对值小的数。
2.1.4 模运算与补码(定点整数)
补码实现了计算机在加减运算的统一,对于异号的数直接相加即可。
对于任何的n+1位机器数都可以推导出真值和机器数的关系。
若字长位n+1,补码的表示范围为$-(2^n)-(2^n-1)$,比原码多表示一个最小的数。
补码和真值的相互转换:
- 真值转为补码:对于正数,与原码的方式一样。对于负数,符号位取1,其余各位由真值“各位取反,末位+1”得到。(反码+1)
- 补码转换为真值:若符号位为0,和原码的方式一样。若符号位为1,真值的符号为负,数值部分各位由补码“各位取反,末位+1”得到。(反码+1)
- 求变补:若真值X的补码为$[X]补$,-X对应的补码为$[-X]补$,则直接对$[X]补$的各个位(包括符号位)按位取反,末位+1,则可以得到$[-X]补$,这个过程叫求变补。
2.1.5 反码以及原反补码相互转换
将原码正数部位保持一致,负数部分符号位不变,其余数值位按位取反,便得到了反码。反码并不常用,通常作为数码变换的中间表示形式。
反码依然浪费了0000和1000重复表示的0的位置。
2.1.6 移码表示法(定点整数)
移码常用于表示浮点数的阶码,只能表示整数。
移码就是在真值X上加上一个常数(偏置值),通常这个常数取$2^n$,相当于在数轴正向移动了若干单位。移码和补码都可以多表示一个$-2^n$的最低位。
移码定义为
$[x]_移=2^n+x (-2^n<=x<2^n,其中机器字长为n+1)$
在移码中,机器数和真值的单调性是一致的,非常适合比大小(偏置值为$2^n$的情况)。
2.1.7 机器数与真值的单调性(考察率不大,理解)
2.1.8 无符号数(定点整数)
所有二进制位都用来表示数值而没有符号位时,表示的就是无符号整数,一般是在全部是整数且不出现负值结果的场合下,它能表示的最大整数比带符号整数能表示的值更大。
2.1.9 定点小数
约定小数点在符号位后面时,寄存器中存储的数称为定点小数。定点小数的表示和定点整数的表示非常类似。
- 原码表示法:用机器数的最高位表示符号,其余各位表示数的绝对值。若字长为n+1,则表示范围为$-(1-2^{-n})-(1-2^{-n})$,总共可以表示$2^{n+1}-$1个数。
- 补码表示法:和定点整数类似。补码表示范围比原码多表示一个-1。
[!TIP]
在计算机中不是每个十进制小数都可以准确地用二进制表示。
另外,对于基数越大的数在同样数位下能表示的真值数量越多,基数为10时密度比2高,因此任何二进制小数都可以用十进制表示,但是反过来不可以。
2.1.10 C语言中的整数类型及类型转换
C语言中的整型数据类型
C语言的整型数据类型就是定点整数,根据位数不同分为:
- 字符型(char),8位
- 短整型(short),16位
- 整型(int),32位
- 长整型(long),在32位机器中为32位,在64位机器中为64位。
char
char类型一般由8位二进制数构成(1B),采用无符号数表示,主要用来表示字符,采用ASCII编码表示。
short
short类型一般由16位二进制数构成(2B),默认采用补码表示,只需要表示整数的时候在short前面加一个unsigned表示是16位机器字长的无符号数。
int
int类型一般由32为二进制数构成(4B),默认采用补码表示,只需要表示整数的时候在int前面加一个unsigned表示是16位机器字长的无符号数。
其他整型构造方式同理。
有符号数和无符号数的转换
C语言允许在不同的数据类型之间做类型转换,强制类型转换就是一种转换方法。
强制转换格式:Type b = (Type) a
,强制转换后,返回一个具有Type类型的数值并复制给b,这个操作不改变操作数本身。
#include <stdio.h>
int main(){
short x = -4321;
unsigned short y = (unsigned short) x;
printf("x =%d, y = %u\n",x,y);
}
输出结果:
x = -4321, y= 61215
可知,将short类型强制转换为unsigned short类型只改变数值,而每个变量对应的每位都是一样的。强制类型转换的结果是保持位值不变,仅改变了解释这些位的方式。
若同时有无符号数和有符号数参与运算,C语言标准规定按无符号数进行运算。
不同字长整数之间的转换
- 大字长变量转小字长变量:大字长变量向小字长变量强制类型转换时,系统把多余的高位部分直接截断,低位部分直接赋值。
- 小字长变量转大字长变量:若原数字是无符号整数,则进行零扩展,扩展后的高位部分用0填充。否则进行符号扩展,扩展后的高位部分用原数字符号位填充。可以理解为,扩展的高位都是用原数字的符号位。
char类型为8位无符号整数,在转换到int时高位补0即可。
总结:
- 有符号数和无符号数转换:机器数相同,解释方式不同。
- 大字长变量向小字长变量转换:高位截断,低位保留。
- 小字长变量向大字长变量转换:高位扩展,低位复制。
2.2 运算方法和运算电路
2.2.1 逻辑运算以及逻辑门
计算机中,运算器是由算术逻辑单元(ALU)、移位器、状态寄存器(PSW)和通用寄存器组等组成。
ALU提供算数运算和逻辑运算,算术运算用于执行数学操作,包括加法、减法、惩罚、除法。逻辑运算用于判断条件真假,包括逻辑与、逻辑或等。ALU的核心部件是加法器。
累加器(AC):累加器暂存源操作数和运算结果。
状态寄存器(PSW):状态寄存器保存状态标志和控制标志,如进位标志、溢出标志等。
通用寄存器组:通用寄存器用于暂存数据,供运算器进行操作。
算数运算实际上是通过一系列逻辑运算来实现的,一个加法步骤可以通过多个逻辑操作实现。
逻辑运算(了解)
- 与运算(&):全1则1,有0则0。
- 或运算(|):有1则1,全0则0。
- 非运算(~):有0则0,全1则1。(也称取反运算)
- 与非运算:与运算的输出上取反。(有0则1,全1则0)
- 或非运算:或运算的输出上取反。(有1则0,全0则1)
- 异或运算(⊕):A、B不同则为1,否则为0。
逻辑门(了解)
逻辑门电路就是用来实现逻辑运算的。
2.2.2 半加器、全加器、串行进位加法器
半加器
半加器可以用来实现一位加法。
输入:A,B;进位计数:C;输出:S。
真值表(ABC的关系和与门真值表相同,ABS的关系和异或门的真值表相同):
因此可以通过一个与门得到C,在与门的基础上再接入一个异或门。至此我们就得到了半加器。
全加器
半加器的输入只有A和B,还需要考虑进位C,即S=A+B+C。半加器只有两位输入,可以用两个半加器构造一个全加器。(T=A+B,S=T+B)。
真值表如下:
无论是A+B产生的进位还是T+C产生的进位,都会导致$C_i=1$。因此在$C_{t1}$和$C_{t2}$之间加个或门,即可得到最终的进位结果,至此就得到了全加器。
串行进位加法器
将n个一位全加器组合起来可以实现n位加法运算,串行传送位间进位,仓内诶串行进位加法器。输入n位二进制数A、B,输出n位二进制数S和1位高位进位输出$C_n$。
并行进位加法器能够优化串行进位(不考察)。
组合逻辑电路仅由逻辑门组成,其输出取决于当前输入的组合,不具备存储状态的能力。
时序逻辑电路不仅取决于当前输入,还取决于先前的输出,具备存储状态的能力,例如锁存器,触发器,寄存器。
2.2.3 补码加减法运算(重点)
补码加减法运算(重点)
补码加减运算公式如下:(设机器字长为n+1)
$[A+B]补=[A]补+[B]_补(mod 2^{n+1})$
$[A-B]补=[A]补+[-B]_补(mod 2^{n+1})$
公式说明:
- 逢二进一。
- 若做加法,两个数的补码直接相加。
- 若做减法,将减法转换成加法,对减数求变补(求相反数的补码),最后将两个补码直接相加。
- 符号位和数值位一起参与运算。
- 若最高位产生进位,则丢弃,保留n+1位,运算结果仍为补码。
补码的溢出判断(重点)
当两个异号数相加或两个同号数相减,不会溢出。然而两个同号数相加或两个异号数相减,有可能溢出。由于可以将异号数相减转换为同号数相加,这里只需要考虑同号相加判断的情况即可。
常用的溢出判断方法有以下三种:
- 正+正=负,负+负=正
- 判断最高位进位和次高位进位是否相同:如果符号位的进位和最高数位(次高位)的进位相同,则没有溢出,否则发生溢出。
如果符号位的进位为C1,最高数位的进位为C2,OF表示溢出标志,则OF=C1⊕C2。 - 采用双符号位判断:双符号位的补码也称为模4补码(变形补码)。设两个数为A和B,将AB补成双符号位。具体来说,AB都为正数,符号位前补一个0,AB都为负数,符号位前补一个1。运算结束后,如果两个符号位相同则表示未溢出,两个符号位不同则表示溢出。
双符号位的各种情况如下:
模4补码(了解)
定点小数的补码表示本质上是模2补码,正数部分,$[x]补=(x+2)%2$,负数部分$[x]补=(x+2)%2$。
模4补码就是把模长变成4。此时,正数部分,$[x]补=(x+4)%4$,负数部分$[x]补=(x+4)%4$。
此时符号位就从单符号变成了双符号。对于定点正数,将模长变为原来的两倍也可以将单符号位变成双符号位(多了一个权重位)。
2.2.4 整数加减运算器(重点)
整体电路
整数加减运算器用来实现具体实现补码加减法的运算,它是整个ALU的核心。
- Sub(减法)信号:作为MUX的控制信号,又作为$C_{in}$信号。其值为0或1。当Sub为0时做加法操作,此时$C_{in}=Sub=0$。当Sub为1时做减法操作,此时$C_{in}=Sub=1$。(这样就可以对B求变补)
- MUX(多路选择器):B的信号作为MUX的两个输入,第一路信号为B信号,第二路信号为~B。根据控制信号的状态,选择相应的输入信号并将其输出。当Sub为0时输出左边的信号,Sub=1时输出右边的信号。
该电路对于无符号整数加减法和有符号加减法是通用的(无符号正数相当于正整数的补码表示,不论是补码减法还是无符号数减法,都是用被减数加上减数的负数的补码来实现的)。
标志寄存器(重点)
标志寄存器(Flag)也称为程序状态寄存器(PSW),保存了多个标志位,包含OF,CF,SF,ZF。这些标志位共同构成了一个程序状态字。
CPU通过标志位记录相关指令的执行结果,并为CPU执行某些指令提供依据。
以下是常见的ALU标志位:
- 零标志(ZF):
ZF=1标志结果为0,否则不为0。对于无符号数和有符号数都有意义。
- 溢出标志(OF):
判断有符号数运算是否溢出,它是符号位进位和最高数位进位的异或结果,即OF=$C_{n}⊕C_{n-1}$,OF=1表明结果溢出,否则不溢出。OF对无符号数没有意义。
- 符号标志(SF):
表示结果的符号。SF=S的最高位,SF=1表明结果为负数,否则为正数。SF对无符号数没有意义。
- 进/借位标志(CF):
表示无符号数运算时的进位/错位,判断是否发生溢出,$CF=Sub⊕C_{out}$($C_{in}=Sub$)。加法时,CF=1表示有进位。减法时,CF=1表示有借位。CF对有符号数没有意义。
- 奇偶标志位(Parity Flag):
用于检测运算结果中1的个数。如果结果中1的个数为偶数则为1,为奇数则为0。
对于有符号数:ZF、SF、OF有意义。
对于无符号数:ZF、CF有意义。两数相减:当ZF=1,说明A=B。当ZF=0且CF=0,说明A>B。当CF=1,说明A<B。
另外要注意,机器本身不知道当前处理的机器数对应的是有符号数还是无符号数,运算结束后都会更新各个符号位。
2.2.5 移位运算与乘除运算
当计算机中没有乘除法运算电路时,我们可以采用移位和加法相结合的方式来实现乘除运算。
移位运算
- 逻辑移位
逻辑移位不考虑符号位,一般用于无符号数的移位。
逻辑移位的规则:
- 逻辑左移时,低位补0,高位移出。
- 逻辑右移时,高位补0,低位移出。
- 算数移位(一般只考补码的移位,默认有符号数)
算数移位需要考虑符号位的问题,一般用于有符号数的移位。计算机中的有符号整数都是用补码表示的,对有符号整数应该采用补码算术移位方式。
算术移位的规则:
- 左移时,低位补0,高位移出。
- 右移时,高位补符号位,低位补0。
表达式x<<k表示对数x左移k位。对于左移来说,逻辑移位和算术移位都一样,都是丢弃k个最高位,并在低位补k个0。每左移一位相当于数值扩大一倍,左移k位相当于数值乘以$2^k$。对于无符号数的逻辑左移,若高位的1被移出则会发生溢出。对有符号数的逻辑左移,如果左移前后的符号位不同,则发生溢出。
表达式x>>k表示对数x右移k位。对于右移来说,逻辑移位高位补k个0,算数高位补k个符号位,低位部分丢弃。每右移一位相当于数值缩小一半,右移k位相当于数值除以$2^k$。若低位的1移出则会影响精度。
乘法运算(非重点)
乘法操作可以通过右移和加法实现。
原码一位乘法
原码一位乘法分为两步。
- 确定乘积的符号位,由两个乘数的符号异或得到。
- 计算乘积的数值位。乘积的数值部分为两个乘数的数值部分之积。
公式法:
Pi称为部分积,每一步迭代:
$P_{i+1}=2^{-1}(P_i+X*Y_{n-i})(i=0,1,2,\ldots,n-1)$
因此可以总结原码一位乘法规则如下:
- 初始部分积为0($P_0=0$)
- 每次计算$X*Y_{n-1}$,将计算结果加入到部分积中,随后整体除2(逻辑右移一位),得到新的部分积$P_{i+1}$。
- 重复步骤2.n次即可得到乘积。
对步骤2进一步研究:
- $Y_{n-1}1$时,即部分积加上x后整体右移。
- $Y_{n-1}0$时,即部分积加上0后整体右移。
乘法运算电路(非重点)
以4位乘法运算电路为例(实际上并不存在,仅仅参考方便理解)
- 初始时,X放入被乘数寄存器(X)中,乘积寄存器P初始为0,Y放入乘数寄存器(Y)中。考虑到对部分积和X做加法的时候可能会溢出,因此进位触发器C保存加法器的进位信号,初始为0。控制逻辑计数器用于判断$Y_{n-i}$,以及发出加法、移位等控制信号,内置计数器$C_{n}$,初始时为4,每循环一次减少1。(注意,P和Y实际上是同一个寄存器分为两个部分,即8位寄存器)
- 判断$Y_4=1$,因此加X。(只需要对部分积的高4位进行加法操作)
- 做完,C、P、Y整体右移一位,$C_n–$。(逻辑移位)
- 重复循环直到$C_n$变为0,算法停止。此时P和Y共同保存了乘积结果。
字长32位的计算机中,当x和y都为unsigned int型变量时,若乘积的高32位全为0,则表示不溢出,否则表示溢出。当都为int型变量时,若高32位的每一位都相同且都等于乘积低32位的符号,则表示不溢出,否则表示溢出。(结果会把高32位截断)
除法运算
运算原理(理解)
类似乘法,符号位和数值位分开计算。
- 确定商的符号位,由除数和被除数的符号异或得到。
- 用两个数的数值部分计算商。
二进制除法商的每一位只能为0或1,因此只需要每次比较两个数的大小即可确定商0或1。
每做一次减法,总是保持余数不动,低位补0。如果商为1,则减去右移后的除数。
由于计算机没法一直右移,不断的右移除数可能会导致超出ALU的位数。因此可以用左移余数的方法代替右移除数,最后乘上$2^{-n}$得到真正的余数。
计算机没法将每位商直接写到寄存器的不同位,因此每位商直接写到寄存器的最低位,并把原来的部分商左移一位。
因此优化后如下:
除法运算电路(理解并记忆)
以32为除法电路为例。
除法运算时,两个32位数相除,必须把被除数扩展成一个64位的数。
n位定点数的除法实际上使用2n位的数去除以一个n位的数,得到一个n位的商,需要对被除数进行扩展。对于定点正小数(原码小数),被除数低位添加n个0;对于定点正整数(无符号数),在被除数高位添加n个0。
- 初始时,除数寄存器Y存放除数,余数寄存器R开始时存放被除数的高32位,结束时存放的是余数。余数/商寄存器Q开始时存放的是被除数的低32位,结束时存放的是32位商。计数器$C_n$存放循环次数,初始值是32,每循环一次减1,减到0时除法运算结束。ALU在控制逻辑控制下,R和Y的内容进行加减运算,在“写使能”控制下运算结果被送回寄存器R。
- 每次将余数与除数Y做减法,判断是否够减,够减上商1,否则上商0。
- 每次循环对R和Q实现同步左移,左移时,Q的最高位移入R的最低位,Q中空出的最低位上商。
- 重复循环直到$C_n$为0。
2.3 浮点数的表示和运算
2.3.1 浮点数的表示
浮点数的表示格式
由于大数字需要很多位来存储,为了用有限的位数尽可能扩大数的表示范围,同时保持数的有效精度,就产生了浮点数。
浮点数的格式如下:$真值=尾数*基数^{阶码}$($N=(-1)^SMR^E$)
第0位为符号S;第1-7位为移码表示的阶码E(偏置值64);第8-31位为24位二进制原码小数表示的尾数M;基数R为2。阶码的值反映浮点数小数点的实际位置;阶码的位数反映浮点数的表示范围;尾数的位数反映浮点数的精度。
浮点数的表示范围
正上溢和负上溢统称为上溢,正下溢和负下溢统称为下溢。数据一旦产生上溢,计算机必须中断运算操作进行溢出处理。数据一旦产生下溢,浮点数值趋于0,计算机将其当做机器0处理。
浮点数的规格化
规格化就是通过某种操作,使得尽可能多地保留有效数字的位数,使有效数字尽量占满位数数位。
尾数部分的最高数位上是无效值时,称为非规格化浮点数,要调整它的位数和阶码的大小,使非零浮点数在尾数的最高数位上保证是一个有效值。
规格化分为左规和右规:
- 左规:尾数的最高数位不是有效值,需要进行左规。左规时,尾数每左移一位,阶码-1。左规可能要进行多次。
- 右规:尾数的有效位进到小数点前面时,需要进行右规。右规时,将尾数右移一位,阶码+1。右规只需要进行一次。右规时,阶码增加可能导致溢出。
补充算数移位:
基数为2的原码规格化尾数
注意这里的规格化尾数是原码小数。补码小数的规格化是小数点后一位与符号位不同。
IEEE754标准
几乎所有计算机都采用IEEE754标准表示浮点数。这个标准提供了两种基本浮点格式:32位单精度(短浮点数、float型)和64位双精度(长浮点数、double型)。
- float型:$真值=(-1)^S1.Mr^E$
float的阶码E采用的是移码表示法,偏移值是127。求阶码对应的真值时,只要用机器数-127。公式里的1称为“隐含位”,这样能表示的有效位数更多。
- double型:阶码偏移值为1023。
float和double类型的表示范围如下图所示:
IEEE754标准格式解释
2.3.2 浮点数的加减运算
浮点数的运算特点是阶码运算和尾数运算分开进行。浮点数加减运算分为:对阶、尾数加减、尾数规格化、舍入、溢出判断。
对阶
我们可以将阶码部分作为公因式提出来,然后让尾数加减法。为了让尾数加减公因式相同,需要对阶。
对阶有两种方式:
- 小阶码向大阶码看齐:将小阶码转换成大阶码。小阶码变大时,尾数需要右移一位,可能会丢失尾数的最低有效位,影响精度。右移时,低位移出的位不要丢掉,应保留并参加尾数部分的运算。
- 大阶码向小阶码看齐:将大阶码转换成小阶码。大阶码变小时,尾数需要左移一位,可能会丢失尾数的最高有效位,问题比较大。
因此采用小阶码向大阶码看齐的原则。
尾数加减运算
将对阶后的两个尾数按定点加减运算规则进行运算。运算后的尾数不一定是规格化的,因此还需要进行规格化处理。
尾数规格化
见2.3.1浮点数的规格化。
- 左规一次相当于乘以2,右规一次相当于除以2。
- 需要右规时,只需要进行一次。
舍入
在对阶和右规的过程中,可能会将尾数的低位丢失,影响精度,可以用舍入法来提高位数的精度。
常用的舍入方式有以下几种:
- 就近舍入(0舍1入):被移去的最高位数值位为0则舍去,为1则在尾数的末位加1。这样做可能使得尾数又溢出,这时需要再做一次右规。
- “恒置1”:尾数右移时不管丢掉的最高数值位是0还是1,都在右移后的尾数末位恒置1。
- 正向舍入:朝数轴$+\infty$方向舍入,取右边最近的可表示数。
- 负向舍入:朝数轴$-\infty$方向舍入,取左边最近的可表示数。
- 截断法:直接截取所需位数,丢弃后面的所有位。这是一种趋向原点的舍入。
溢出判断
在规格化和舍入的时候可能会对结果的阶码进行加减运算,必须考虑阶码溢出问题。
- 若阶码超过了最大允许值,则发生上溢,产生异常。
- 若阶码超过了最小允许值,则发生指数下溢,通常把结果按机器0处理。
浮点数的溢出不以尾数溢出来判断(尾数溢出可以通过右规纠正),主要看的是结果的指数是否发生了上溢,因此是由阶码的上溢来判断的。
2.3.4 定点、浮点表示的区别
- 数值的表示范围:若字长相同,则浮点表示法所能表示的数值范围远大于定点表示法。
- 精度:浮点数虽然扩大了数的表示范围,但是精度降低了。
- 数的运算:浮点数包括阶码和尾数两部分,运算时不仅要做尾数的运算,还要做阶码的运算,运算的结果还需要规格化,因此浮点运算比定点运算复杂。
- 溢出问题:在定点运算中,运算结果超出数的表示范围时发生溢出。在浮点运算中,运算结果超出尾数表示范围却不一定溢出;只有规格化后,阶码超出能表示的范围时,才发生溢出。
2.3.5 C语言中的浮点数类型和类型转换
在C程序中,等式的赋值和判断都会导致强制类型转换,低类型转换为高类型:范围和精度都从小到大,转换过程都没有损失(例如char->short->int->double或float->double)。
不同类型数的混合运算时,遵循的原则是“类型提升”,即较低类型转换为较高类型。所有转换都是系统自动进行的,这种转换称为隐式类型转换。例如short+int,需要先将short转换为int,结果仍然为int型。
- int->float,虽然不会发生溢出,但是float型尾数连隐藏位共24位,当int型数的第24-31位非0时,无法精确转换成24位浮点数的尾数,需要舍入处理,影响精度。
- int/float->double,由于double型的有效位数更多,因此能够保留精确值。
- double->float,因为float型的表示范围更小,因此大数转换时可能会发生溢出。此外,由于有效位数变少,因此高精度数转换时会发生舍入。
- float/double->int,因为int没有小数部分,因此数据会向0方向截断,只保留整数部分,发生舍入。此外,由于int型表示范围更小,因此大数转换时可能会溢出。
2.3.6 数据的大小端和对齐存储
数据的“大端方式”和“小端方式”存储
现代计算机基本采用字节编址,即每个地址编号中存放1字节。每个存储字也有一定地址,这个地址叫做字地址。
以12345678H
的存储为例:
通常用最低有效字节(LSB)和最高有效字节(MSB)来分别表示数据的低位和高位。
- 大端方式:MSB作为字地址,先存储高位字节,后存储低位字节。字中的字节顺序和原序列相同。
- 小端方式:LSB作为字地址,先存储低位字节,后存储高位字节。字中的字节顺序和原序列相反。(口诀:大同小异)
数据按“边界对齐”方式存储
数据按边界对齐方式要求数据存储时必须按边界对齐,即数据的存储地址是自身大小的整数倍,半字地址一定是2的整数倍,字地址一定是4的整数倍。这样无论所取的数据是什么,均可以一次访存取出。
C语言中的边界对齐(struct类型)
在C语言的struct类型中,“边界对齐”有两个重要要求:
- 每个成员按其类型的大小对齐,char型的对齐值为1B,short型的对齐值为2B,int型的对齐值为4B。
- struct的长度必须是成员中最大对齐值的整数倍(不够就补空字节),这样就能保证struct数组的每项都满足边界对齐的条件。
#include<stdio.h>
struct Example{
char a; //对齐值为1,占用1字节
short b;//对齐值为2,占用2字节
int c; //对齐值为4,占用4字节
};
int main(){
struct Example example;
printf("Size of struct Example: %d types\n",sizeof(example));
printf("a的地址:%p\n",&example.a);
printf("b的地址:%p\n",&example.b);
printf("c的地址:%p\n",&example.c);
return 0;
}
运行结果如下: