第二章 数据的表示和运算

186 阅读53分钟

第二章 数据的表示和运算

【考频分析】

年份考点分值
2009定点数的表示和运算、C语言数据类

型转换、浮点数的表示和加减运算
44
2010补码的表示范围、定点数的表示和运算、C语言中各种数据类型的强制转换2*2+4=88
2011IEEE 754IEEE\ 754单精度浮点数的表示、标志位的判断;
综合体:C语言中数据类型范围、有符号数加减法、无符号数加减法、加法器电路、数据溢出判断
2*2+11=1515
2012真值与机器数的转换、IEEE 754IEEE\ 754单精度浮点数的表示范围、数据边界对齐、小端方式
综合体:真值在机器中的表示方式
3*2+4=1010
2013IEEE 754IEEE\ 754单精度浮点数的机器数与真值转换、定点数的表示和运算(补码)、校验码(海明码)、标志位的判断3*2+8=1414
2014数据溢出判断、定点数的表示和运算(补码的加减运算)、IEEEIEEE单精度浮点数的表示4分
2015浮点数的表示与运算(加减)、补码表示、ALUALU4分
2016C语言数据类型转换、小端方式4分
2017应用题两道:定点数的表示和运算、IEEE 754IEEE\ 754单精度浮点数、C语言中数据表示和运算、浮点数的表示和运算、
C语言数据类型转换、标志位的判断、左移*2操作
13+10=2323
2018定点数的表示和运算(补码)、数据的表示范围、C语言数据类型转换、IEEE 754IEEE\ 754单精度浮点数(最小规格化正数)、小端方式、
逻辑右移、算术右移、标志位判断
5*2=1010
2019C语言中数据表示和运算、大端方式、定点数的表示和运算(补码)、标志位的判断、2*2+3=77
2020定点数的表示和运算(补码)、IEEE 754IEEE\ 754单精度浮点数的表示、小端编址、小端方式、边界对齐、ALUALU的原理、带符号与无符号整数的乘法运算
溢出判断
4+13=1717
2021定点数的表示和运算、IEEE 754IEEE\ 754单精度浮点数的表示、ALUALU、数据溢出判断、零扩展与符号扩展4+7=1111
2022补码表示范围、IEEE 754IEEE\ 754单精度浮点数的表示、标志位的判断、溢出判断4+3=77
2023C语言类型转换、IEEE 754IEEE\ 754单精度浮点数(非规格化小数)、标志位的判断(有、无符号数加减法溢出判断)3*2 = 66

2.1 定点数的表示和运算

image-20240429104121970

  1. 机器数真值:像3+5-3、+5这样带**“++”、“”号的数称为真值**,可以将其理解为真正的数,一般用十进制来表示,也可以用二进制来表示。将数值数据在计算机中编码表示后的数据称为机器数,一般采用补码表示,也可以用原码、反码、移码来表示。

  2. 定点数:定点表示就是约定机器数中的小数点位置固定不变,小数点不再使用.表示,而是约定其位置。理论上,小数点位置固定在任何一位都可以,但是在计算机中通常采用两种简单的约定:

  • 定点整数:将小数点的位置固定在数据的最低位之后
img
  • 定点小数:将小数点的位置固定在数据的最高位之前
img
  1. 有符号定点整数的表示(原码、补码、反码和移码)
  • 原码:带符号的绝对值表示,最高位表示真值的符号(1 表示负,0 表示正),其余各位表示真值的绝对值

    优点:表示方法简单直观

    缺点:

    1. 真值00在原码种有两种不同的表示
    2. 原码加减法运算规则复杂,符号位不能参与运算,需要设计复杂的硬件电路。现代计算机不用原码表示整数,只用定点原码小数表示浮点数的尾数。
    3. image-20240501124712513
  • 反码:正数的反码与原码一样,负数的反码其数值部分全部取反;

    用途:反码通常用来作为由原码求补码或者由补码求原码的中间过渡

    优点:符号位可以参与运算

    缺点:(在计算机中很少被使用)

    1. 最高位(符号位)产生的进位要加到运算结果的低位(循环进位)
    2. 真值00​在反码中有两种不同的表示
    image-20240501125618731
  • 补码::star:正数的补码与原码一样,负数的补码是对其原码数值位按位取反然后末位加1;

    1. 原补码转换快捷手算方法:从右往左找到第一个1,然后将其左边的所有数值位按位取反即可;

    2. 引入补码的原因:在原码的加减运算中,由于最高位表示的是符号位,若将其直接参与运算,其得到的结果是错误的,除非改造ALU,但是成本会很高。联想到【如果有一个时钟,指针开始时指向的是10,如果要让其指向7,那么有两种方法,要么逆时针旋转至7,相当于做减法;要么顺时针旋转至7,相当于做加法】,我们可以用加法实现减法运算,为此引入了补码表示法。其表示方法为正数仍为其原码,负数为模(nn位补码的模为2n2^n)与该负数绝对值之差。

    3. 补码的表示范围n+1n+1位补码的表示范围为 2n-2^n~2n12^n-1,比原码多表示一个2n-2^n,例如,8位补码的表示范围为128 ~ 127。

    4. 机器数0的表示:不同于原码和反码有正零和负零之分,补码的零只有一种表示方法,即数值位和符号位为全0.

    5. 补码表示的一些常见二进制形式:n+1n+1位补码的最小整数2n-2^n的二进制形式为1000001000\cdots00; 表示的最大整数形式为0111110111\cdots11;补码表示-1的形式为111111111\cdots111

    6. 目前在计算机中普遍采用补码表示有符号定点整数,例如C语言中char、 short、 int、 long型整数都是补码表示。

    7. 为什么用补码表示带符号整数? (50年代以来,所有计算机都用补码来表示带符号整数)

      • 补码运算系统是模运算系统,加、减运算统一
      • 数0的表示唯一,方便使用
      • 比原码多表示一个最小负数
    8. 根据补码的设计思想和编码规则,以下技巧可以更快地分析负数补码

      补码实现了模运算,使其可以用加法来实现减法,并且符号位和数值位一同参与运算,方便了加法器的设计。以3位补码为例,其补码数轴如图所示:

      未命名绘图.drawio **符号位为0,补码从000→011,真值从0→3不断增大。** 在**负半轴**上,符号位为1,补码从100→111,真值从$-4$→$-1$不断增大;

      非负半轴上,所以可以得到如下结论:补码符号位不变时,其真值随着数值位的增大而增大。该结论可以帮助比较补码的真值大小,例如补码 F340H<FF30H<FF40H7010H<7500H<7740HF340H<FF30H<FF40H、7010H<7500H<7740H

      若补码之间跨越了正负半轴,直接通过符号位比较正负,例如F300H<7070HF300H<7070H

      若把两个互为相反数的补码相加,得到的真值为0,但是最高位进位为1。例如(3)+3:101+011=(1)000(2)+2=110+010=(1)000(-3)+3:101+011=(1)000、(-2)+2=110+010=(1)000

      利用该结论可以**:star:方便求出以16进制表示的补码负数的真值**。考试中大多为16位或者32位的补码,以16位补码为例:对于补码FF3AHFF3AH,需要求出其加上哪一个数机器数变为0,并且最高位进位为1,有FF3AH+00C6H=(1)0000H00C6HFF3AH+00C6H=(1)0000H,00C6H的真值为12x16+6=198,所以补码FF3AH的真值为-198

  • 移码:在真值的基础上加上一个偏置值,可以得到移码。n+1n+1位移码的偏置值通常取2n2^n2n12^n-1移码只用于表示定点整数。

    • 当偏置值为2n2^n​时,则在补码的基础上,对符号位取反即可;

      image-20240501150334008
    • 当偏置值为2n12^n-1时,通常用于表示浮点数的指数部分(阶码)。

移码由于偏置值的存在,使它可以按照无符号数比较的大小反映真值的相对大小。(保持了真值原有的大小顺序)

为什么要用移码来表示指数(阶码)?

便于浮点数加减运算时的对阶操作(比较大小)

img

假设编码字长为 n+1n+1 位, 移码偏置值为 2n2^n ,各种码的特性总结

整数合法表示范围最大的数最小的数真值0的表示用途
原码(2n1)-(2^n-1) ~ 2n12^n-10,111110,111\cdots11
=2n1=2^n-1
1,111111,111\cdots11
=(2n1)=-(2^n-1)
[+0]=0,00000[+0]_{原}=0,000\cdots00
[0]=1,00000[-0]_{原}=1,000\cdots00
表示IEEE 754IEEE\ 754浮点数的尾数
反码(2n1)-(2^n-1) ~ 2n12^n-10,111110,111\cdots11
=2n1=2^n-1
1,000001,000\cdots00
=(2n1)=-(2^n-1)
[+0]=0,00000[+0]_{反}=0,000\cdots00
[0]=1,11111[-0]_{反}=1,111\cdots11
某些数码转换的中间形式
补码2n-2^n ~ 2n12^n-10,111110,111\cdots11
=2n1=2^n-1
1,000001,000\cdots00
=2n=-2^n
[0]=0,00000[0]_{补}=0,000\cdots00
真值0只有一种补码
机器数,表示计算机中的有符号数
移码2n-2^n ~ 2n12^n-11,111111,111\cdots11
=2n1=2^n-1
0,000000,000\cdots00
=2n=-2^n
[0]=1,00000[0]_{移}=1,000\cdots00
真值0只有一种移码
表示浮点数的指数(阶码)
无符号整数00 ~ 2n+112^{n+1}-11,111111,111\cdots11
=2n+11=2^{n+1}-1
0,000000,000\cdots00
=0=0
0,000000,000\cdots00

未命名绘图

image-20240501152139465

  1. 定点小数的原/反/补码表示:

    其小数表示与整数表示的原理类似,例如,机器字长为8位,若x=+0.1101x=+0.1101,则xx的原码,反码,补码的表示分别为0.11010000.11010000.11010000.1101000、0.1101000、0.1101000

    对两个定点小数A,B进行加法/减法时,需要先转换成补码,从最低位开始,按位相加(符号位参与运算),并向更高位进位。

    注意定点小数和定点整数的符号扩展位置不一样,定点小数在数值位后面扩展,定点整数在数值位之前扩展。

image-20230828154102385

2.2 整数表示与运算

2.2.1 无符号整数的表示与运算

计算机中的数均放在寄存器或内存中,一般寄存器的位数与机器字长相等。无符号整数指整个机器字长的全部二进制位均为数值位,没有符号位,相当于数的绝对值。

在C语言中,只需要在 intshort等关键字前加unsigned即可定义一个无符号数。当无符号数与有符号数一起运算时,编译器默认将有符号数转换为无符号数再参与运算,运算结果为无符号数。

无符号数的加法:直接 NN 位二进制按位相加即可;

无符号数的减法:计算 ABA-B,可以将其转化为等价的加法,将BBNN 位全部按位取反末位加1,然后与 AA 相加。

2.2.2 有符号整数的表示和运算

有符号整数的表示即上面介绍的原码、反码、补码等编码方式,下面介绍其运算方式。

2.2.2.1 移位运算

当计算机没有乘除法运算电路时,可以采用移位和加法相结合的方法,来实现乘除运算。对于任意二进制数,无论是无符号数还是有符号数,将其相对于小数点做nn位左移或者右移时,相当于该数乘以或除以2n2^n。由于机器字长是固定的,当机器数左移或右移时,都会使其低 nn 位或高 nn 位出现空缺,因此需要对空缺部分补充0或1.

  1. 逻辑移位:不考虑符号位,将操作数视为无符号数,无论左移还是右移都是添 00

  2. 算术移位:考研只考补码的算术移位,其规则为:算术左移时,低位补0,高位移出;算术右移时,高位补符号位,低位移出。

    如果补码算术左移使得符号位发生改变,则说明发生溢出,例如8位补码 1000 00111000\ 00110100 00110100\ 0011 算术左移后均发生溢出。

【例题】:若计算机的机器字长为88,数据以补码形式表示,并且机器数含 1 位符号位, 现有整数 x, y, zx,\ y,\ z,其中 [x]=36H[x]_{补}=36H[y]=54H[y]_{补}=54H,[z]=D5H[z]_{补}=D5H, 请分别求 x2yx-2yx/4+2zx/4+2z 的机器数,并指明计算结束后的溢出标志OFOF 的值。

解:[y]=54H=0101 0100B[y]_{补}=54H=0101\ 0100B2y2y 相当于将 yy 算术左移1位,左移后为 1010 10001010\ 1000[2y]=0101 1000B[-2y]_{补}=0101\ 1000B, x2y=0011 0110B+0101 1000B=1000 1110Bx-2y=0011\ 0110B + 0101\ 1000B=1000\ 1110B,两正数相加却为负数,结果溢出,因此OF=1OF=1;

x/4x/4 相当于将 x x 算术右移两位, xx为正数,右移高位补 00xx 的机器数为 0011 0110B0011\ 0110B, 所以x/4x/4的机器数为 0000 11010000\ 1101;2z2z相当于zz算术左移1位,zz的机器数为1101 01011101\ 0101,所以2z2z1010 10101010\ 1010,二者相加结果为1011 0111B1011\ 0111B,两个异号的数相加,不会溢出,所以OF=0OF=0

2.2.2.2 补码加减运算

  1. 补码加法:两个数的补码相加,符号位参与运算,且两数和的补码 = 两数的补码之和。符号位相加后若有进位,进位数字直接舍去。
  2. 补码减法:对于减法,因为xy=x+[y]x-y=x+[-y], 则[xy]=[x+(y)]=[x]+[y][x-y]_{补}=[x+(-y)]_补=[x]_补+[-y]_补

[y][-y]_补 只需将[y] [y]_补 连同符号位在内,按位取反, 末位加1即可。

这个规则当y=2n1y=-2^{n-1}(nn 位补码 ) 时失效,例如 128-12888 位补码为 1000 0000B,1000\ 0000B,按此规则得到的 128128 的补码为1000 0000B1000\ 0000B,但这显然不正确。这是因为 nn 位补码不能表示 2n12^{n-1}。但是注意,在不溢出的情况下,按此规则计算补码减法仍然正确、例如7(128)=1111 1001B+1000 0000B=0111 1001B=121-7-(-128)=1111\ 1001B+1000\ 0000B=0111\ 1001B=121,结果仍然正确。这体现了按此规则计算补码减法的通用性。

2.2.2.3 溢出概念及其判别方法

溢出即指运算结果超出机器数的表示范围,关于溢出有以下结论:

  • 两个异号数相加 或 两个同号数相减不会溢出
  • 两个同号数相加 或 两个异号数相减有可能溢出

只考虑有符号数,在计算机中,补码定点数加减运算常用的溢出判断方法有以下几种:

  1. 判断结果与操作数符号是否相等

减法运算在计算机内部是用加法器来实现的,因此无论是加法还是减法,若送至加法器的两个操作数的符号相同,结果又与原操作数符号不同,则表示结果溢出,否则未溢出。

  1. 判断最高位进位和次高位进位是否相同

若符号位(最高位)和最高数值位(次高位)的进位相同,则说明没有溢出,若不同则溢出。

假设符号位进位为 C1C_1, 最高数值位的进位为 C2C_2, 若OF=1OF=1表示溢出,则逻辑表达式为OF=C1C2OF=C_1⊕C_2

  1. 采用双符号位判断

双符号位的补码又叫模 44 补码, 运算结果的两个符号位 K1K2K_1K_2相同, 表示未溢出;若不同则表示溢出,此时最高位的符号代表计算结果的真正符号

符号位K1K2 K_1K_2 的各种情况如下:

  • K1K2=00K_1K_2=00:表示结果为正数,未溢出;
  • K1K2=11K_1K_2=11:表示结果为负数,未溢出;
  • K1K2=01K_1K_2=01:表示结果正溢出;
  • K1K2=10K_1K_2=10:表示结果负溢出;
  1. 将其结果手算出来,看结果是否超过了当前机器字长所能表示的范围即可;

上面介绍的是有符号数的溢出判断,**对于无符号数的溢出判断,可以通过CFCF 标志位判断,当CFCF 标志位等于1,即最高位向更高位产生进/借位时,无符号数加减法溢出。**具体来说,当无符号数加法运算最高位产生进位,或无符号数减法运算最高位产生借位时,结果溢出。(2023年考察)

2.2.3 无符号整数和有符号整数的位权:star:

  1. 无符号整数的位权

对于 nn 位无符号数 xn1xn2x0x_{n-1}x_{n-2}\cdots x_0 ,其第 ii 位的位权为 2i(i=0,1,n1)2^i(i=0,1,\cdots n-1),那么我们可以通过公式 i=0n1xi2i\sum_{i=0}^{n-1} x_i2^i来计算无符号数编码的真值。

例如[1011]unsigned=120+121+022+123=11[1011]_{unsigned}=1*2^0+1*2^1+0*2^2+1*2^3=11

image.png
  1. 有符号数的位权

对于补码表示的 nn 位有符号整数xn1xn2x0 x_{n-1}x_{n-2}\cdots x_0 来说,其第 ii 位的位权为 2i(i=0,1,n2)2^i(i=0,1,\cdots n-2), 最高位的位权为 2n1-2^{n-1}, 即只有最高位的位权与无符号数不同(为负权,是无符号数最高位权值的相反数)。

可以通过公式i=0n2xi2i+xn1(2n1) \sum_{i=0}^{n-2} x_i2^i+x_{n-1}(-2^{n-1})来计算补码编码的真值。举例如下

[1011]=120+121+022+1(23)=5[1011]_{补}=1*2^0+1*2^1+0*2^2+1*(-2^3)=-5

image.png

通过位权的角度来分析补码的表示范围

  • **当 nn 位补码的最高位设置为 11, 其他位全部清 00 **时,考虑补码位权可知, 此时只保留了负权,而清除了所有的正权,这种情况下,nn 位补码取得最小值 2n1-2^{n-1},对应的二进制编码为[1000][100\cdots 0]
  • **当 nn 位补码的符号位设置为 00时, 其他位全部设置为 11**时, 考虑补码位权可知, 此时清除了负权, 而保留了所有的正权,该情况下 nn 位补码取得最大值 2n112^{n-1}-1,对应的二进制编码为[011111][0111\cdots 11]

如果知道了该知识点,则【2015】年的真题可以手到擒来

【2015年计组】:由 33 个 “1” 和 55 个 “0” 组成的 8 位二进制补码, 能表示的最小整数为 ()

A: -126 B:-125 C:-32 D:-3

解析:要想表示最小,由于符号位是负权值,因此符号位得设置为1,还有两个1其位权为正数,放在最后两位,其正权值最小,机器数表示为[1000 0011][1000\ 0011],值为127+120+121=128+1+2=125-1*2^7+1*2^0+1*2^1=-128+1+2=-125

【扩展】原码和反码的位权:对于原码来说,其最高位用来决定其余各位权值的正负。符号位若为正,数值位则全为正权;若符号位为负,数值位则全为负权。

对于反码来说,其最高位(符号位)的权值为 (2n11)-(2^{n-1}-1) 而不是 2n1-2^{n-1},其余位的权值和补码一样。


2.3 浮点数的表示和运算

2.3.1 浮点数的表示

  1. 表示格式

    image-20230828203450821
  • 阶码 EE:常用 补码移码 表示的定点整数,其位数反映了浮点数的表示范围,阶码的真值反映了小数点的实际位置。
  • 尾数 MM :常用 原码补码 表示的定点小数, 其位数 nn 反映了浮点数的精度 ,其数符表示浮点数的正负。
  • 浮点数的真值: N=2EMN=2^E * M

通俗地说,尾数给出一个小数,阶码指明了小数点要向前/向后移动几位。

示例: a=0,01,1.1001a=0,01,1.1001, 阶码、尾数均用补码表示,求aa 的值

解:阶码 0,010,01对应真值为 +1+1, 尾数1.10011.1001转为原码表示为1.01111.0111,真值为0.0111-0.0111,因此 aa 的真值为21(0.0111)=0.1112^1*(-0.0111)=-0.111

  1. 浮点数尾数的规格化

尾数的位数决定浮点数的有效数位,有效数位越多,数据的精度越高。为了在浮点数运算过程种尽可能多地保留有效数字的位数,使有效数字尽量占满尾数数位,必须通过调整阶码和尾数的大小对浮点数进行规格化操作。

规格化浮点数:规定非零的浮点数在尾数的最高数位上保证是一个有效值。

  • 左规:当运算结果的尾数最高数位不是有效位,需要进行左规。左规时,将尾数算术左移 一位,阶码减1;左规可能要进行多次。
  • 右规:当浮点数运算结果尾数出现溢出(双符号位为 01011010 时),将尾数算术右移一位,阶码加1;

采用双符号位时,当发生溢出时,可以挽救。因为更高的符号位是正确的符号位。

【2009年真题】年真题就考到了该知识点。

右规示例:image-20230828210854686

image-20230828211819383
  • 规格化的原码尾数,最高数值位一定为1;
  • 规格化的补码尾数,符号位与最高数值位一定相反;

这两点必须记住!!!

为了确保满足规格化的要求,补码的最高数位与符号位相反,在求其表示的尾数的最大值或最小值的机器数表示时,我们依旧可以采用位权的思想,当该数为负数时,符号位和最高数位都已确定,剩下的位权皆为正权,若全为11[1.01111][1.011\cdots 11],则为负数最大值;若全为 00[1.0000][1.000\cdots 0],则为负数最小值,符号位的位权为1 -1,所以最小值为 1-1.

  1. 表示范围
image-20230828212956516

当浮点数阶码小于最小允许值时,发生下溢,此时通常将结果置为 +0(符号位为 0)或 -0(符号位为1),按机器零处理;当浮点数阶码超过最大允许值时, 称为上溢,此时机器产生异常,停止运算,也有的机器会将结果置为 ++∞-∞


2.3.2 IEEE 754IEEE\ 754 标准

image-20230828213817993

这里的临时浮点数已经过时了,现行标准已经改为 128128 位浮点数(1+15+1121+15+112)。

IEEE 754IEEE\ 754 标准里的阶码 采用 移码 表示, 其偏置值为2n112^{n-1}-1

image-20230828214126161

在计算阶码的真值时,一般先将阶码看作无符号数计算,然后减去偏置值,单精度和双精度的偏置值分别为 12712710231023。例如 88 位阶码 1100 00001100\ 0000表示指数部分为192127=65192-127=65.

尾数原码表示,有一个隐藏位为1,省略。

  1. 必须熟练掌握十进制数与IEEE 754IEEE\ 754 单精度浮点数 floatfloat的相互转换
  • floatfloat转十进制(真值)(2013、2014、2020、2023年考察)
image-20230828215309181
  • 十进制用floatfloat 表示(2011、2022年考过)

对于C语言代码float y = -36.625,若编译器将yy 分配在一个32位寄存器中,求该寄存器内容。

yy 用二进制小数表示为 y=100100.101y=-100100.101,将yy 右移,使得小数点前面只保留一位“11”,得y=1.0010010125y=-1.00100101*2^5

数符 =1= 1数部分 =.001001010000 .001001010000….(隐含最高位1)

阶码真值 = 5, 移码 =5+127=132=10000 100= 5 + 127 = 132 = 10000\ 100

组合得1 10000100 00100101000000000000000=C212 8000H1\ 10000100\ 00100101000000000000000=C212\ 8000H

  1. IEEE 754IEEE\ 754单精度浮点型所能表示的最小绝对值,最大绝对值是多少?

阶码全0和阶码全1均被用来表示特殊值,因此阶码的范围12541 \sim 254,机器数表示最大为 1111 11101111\ 1110, 最小为0000 00010000\ 0001其真值的范围为126127-126\sim127

image-20230828220645290

2012年考察了 floatfloat 所能表示的最大正整数,即规格化的最大绝对值; 2018年考察了 floatfloat所能表示的最小规格化正数;

如果继续考察 floatfloat 的规格化表示,可能的出题形式有floatfloat 能表示的最大负数的机器数是多少?能表示的最小规格化负数的机器数是多少?

【注意】:最大负数绝对值最小,最小负数绝对值最大,别混淆喽!其答案分别为8080 0000H8080\ 0000HFF7F FFFFFF7F\ FFFF.

另外注意一下 doubledouble 的规格化最大绝对值与最小绝对值,其出题模式也可能换个说法,类似于求doubledouble型变量所能表示的最接近于 0 的规格化负数,或所能表示的正数的取值范围之类。

关于doubledouble 历年还未考过,要注意一下!

  1. floatfloat 的特殊表示

    ieee 754ieee\ 754标准中, 浮点数包含三种状态

    1. normal number(规格数)
    2. subnormal number(非规格数)
    3. non-number(特殊数)

    这三种状态是通过指数部分区分的, 而且很容易区分.以32位浮点数为例, 其内存状态分为3部分:

    1位符号位 8位指数位 23位尾数位

    其中, 如果8位指数位全为0, 就代表当前数是个非规格数. 或者说, 形如 * 00000000 *********************** 格式的数就是非规格数.

    如果8位指数位全为1, 就代表当前数是个特殊数. 或者说, 形如 * 11111111 *********************** 格式的数就是特殊数.

    如果8位指数不全为0, 也不全为1(也就是除去以上两种状态外, 剩下的所有状态), 这个数就是规格数.

    随便几个例子: * 10101100 ***********************就是一个规格数

    可见: 非规格数特殊数是两种特殊状态, 规格数则是非常常见的状态.

    为什么要把浮点数分为这三种状态呢? 答案当然是有用啊, 而且作用相当直观:

    规格数: 用于表示最常见的数值, 比如1.2, 34567, 7.996, 0.2. 但规格数不能表示0和非常靠近0的数.

    非规格数: 用于表示0, 以及非常靠近0的数, 比如1E-38.

    特殊数: 用于表示"无穷"和"NaN":

    浮点数的存储和计算中会涉及到"无穷"这个概念, 比如:

    32位浮点数的取值范围是

    img

    如果你要往里面存储4e38(这超过了最大的可取值), 32位浮点数就会在内存中这样记录 "你存储的数超过了我的最大表示范围, 那我就记录你存储了一个无穷大..."

    浮点数的存储和计算中还会涉及到"NaN (not a number)"这个概念, 比如:

    你要给一个负数开根号(如 √-1), 但是ieee754标准中的浮点数却不知道该怎么进行这个运算, 它就会在内存中这样记录 "不知道怎么算, 这不是个数值, 记录为NaN"

    C9594A819AA816E9685CE0E2C2A8328F

上面我们提到了IEEE 754IEEE\ 754所能表示的最小绝对值,那个是规格化数,如果想要用IEEE 754IEEE\ 754 表示绝对值还要小的数,怎么办呢?还记得我们在移码中提到的用做特殊用途的阶码全0,和阶码全1吗,下面它们就要派上用场了!

  • 当阶码全0,尾数不全为0时,用来表示非规格化小数,其表示的数的真值为±(0.×××)2×2126\pm (0.\times \times\dots\times )_2\times 2^{-126}

    【注】:此时,隐含最高位变为 00, 阶码真值并不是 0127=1270-127=-127, 而是固定视为 126-126, 这点要注意!这样,就可以用IEEE 754IEEE\ 754 表示比规格化最小绝对值还要小的数了!这个知识点【2023年】刚考察过。

  • 当阶码全0,尾数全0时,表示真值 ±0\pm 0

  • 当阶码全1,尾数全0时,表示无穷大 ±\pm \infty

    引入无穷大,使得计算过程中出现 \infty 的情况下程序可以继续运行,而不会发生异常。无穷大数既可以作为操作数,也可能是运算的结果。

  • 当阶码全1,尾数不全为0时,表示非数值“NAN”(Not a Number)

    引入非数既可以检测非初始化值,又可以使得计算出现异常时程序能继续下去。

    注意到没有: 非规格数的最大值是:

    img

    规格数的最小值是:

    img

    两者之间实现了非常平滑的过度, 非规格数的最大值非常紧密的连接上了规格数的最小值

    非规格数 "一点点逐渐变大, 最后其最大值平稳的衔接上规格数的最小值", 这种特性在ieee754中被叫做逐渐溢出(gradual underflow)

    明白了这一点, 就很容易想通:

    ① 为什么规定非规格数的尾数前隐藏的整数部分是 0. 而规格数尾数前隐藏的整数部分是1.

    ② 为什么非规格数的真实指数的计算公式是 1 - bias, 而规格数的真实指数的计算公式是 指数部分的值 - bias

    仔细思考一下, 就是这些设计实现了逐渐溢出这种特性.

    ↑ 关于第①点: 这使得非规格数的尾数取值范围是[0,1), 而规格数的尾数取值范围是[1,2), 两者平滑的衔接在了一起

    ↑ 关于第②点: 这使得对于32位浮点数来说, 非规格数的真实指数固定为-126, 而规格数的指数是[-126, 127], 两者也平滑的衔接在了一起...

IEEE 754IEEE\ 754 特殊表示总结(1~4列为单精度浮点数floatfloat,5 ~ 8列为双精度浮点数doubledouble

值的类型符号位(1位)阶码(8位)尾数(23位)真值符号位(1位)阶码(11位)尾数(52位)真值
正零000000 00000000\ 000000+0+0000000+0+0
负零110000 00000000\ 0000000-01100000-0
正无穷大001111 11111111\ 1111
(255)(255)
00++\infty002047(1)2047(全1)00++\infty
负无穷大111111 11111111\ 1111
(255)(255)
00-\infty112047(1)2047(全1)00-\infty
非规格化小数00110000 00000000\ 0000不全为 00±(0.×××)2×2126\pm (0.\times \times\dots\times )_2\times 2^{-126}001100不全为 00±(0.×××)2×21022\pm (0.\times \times\dots\times )_2\times 2^{-1022}
**非数 **00111111 11111111\ 1111不全为 00NANNAN00112047(1)2047(全1)不全为 00NANNAN
最大正数001111 11101111\ 11101 12127×(2223)=212821042^{127}\times(2-2^{-23})=2^{128}-2^{104}00111 1111 1110111\ 1111\ 11101 121023×(2252)=2102429712^{1023}\times(2-2^{-52})=2^{1024}-2^{971}
最小正数000000 00010000\ 0001001.0×2126=21261.0\times2^{-126}=2^{-126}00000 0000 0001000\ 0000\ 0001001.0×21022=210221.0\times2^{-1022}=2^{-1022}
最大负数110000 00010000\ 0001001.0×2126=2126-1.0\times2^{-126}=-2^{-126}11000 0000 0001000\ 0000\ 0001001.0×21022=21022-1.0\times2^{-1022}=-2^{-1022}
最小负数111111 11101111\ 11101 1(1)×2127×(2223)=21042128(-1)\times2^{127}\times(2-2^{-23})=2^{104}-2^{128}11111 1111 1110111\ 1111\ 11101 1(1)×21023×(2252)=297121024(-1)\times2^{1023}\times(2-2^{-52})=2^{971}-2^{1024}

2.3.3 浮点数的加减运算

  1. 操作数的检查

当两个浮点数进行加减运算时,需要检查操作数是否为0。一种情况是阶码和尾数均为0,浮点数值为0;另一种情况是浮点数下溢, 浮点数被当作机器0处理。若加数、被加数或减数为0.则结果为另一个操作数;若被减数为0,结果为减数的相反数。如果都不为0,转换格式后进行以下操作。

image-20230829001318199
  1. 比较阶码大小完成对阶操作

首先求阶差,将两个数的阶码相减,阶差记作E\bigtriangleup E,若E=0\bigtriangleup E=0, 说明两数阶码相等,无需对阶;若E0\bigtriangleup E\ne 0,就需要对阶。对阶时,小阶向大阶对齐,将小阶数的尾数右移E\bigtriangleup E位,阶码加E\bigtriangleup E,使得阶码对齐。

image-20230829001423410
  • 小阶向大阶对齐的原因:如果是”大阶向小阶对齐”, 尾数需要左移,则其数值部分的高位需要被移出;而小阶向大阶对齐,尾数右移,需要移出的是尾数数值部分的低位,这样损失的精度更小。
  • 由于是小阶向大阶对齐,因此不会出现溢出现象。
  1. 尾数加减

将对阶后的尾数按定点小数加减运算规则运算。若运算后的尾数是规格化的,则直接结束流程,得出结果。否则,需要进一步进行规格化处理。

image-20230829004529515
  1. 结果规格化

若尾数采用的是双符号位,当尾数相加得到的结果符号位为10 或 01 时,需要右规,如本例,

image-20230829114630862

若该浮点数采用IEEE 754IEEE\ 754 标准,其规格化尾数的形式是 ±1.×××\pm 1.\times \times \cdots \times 。 尾数相加减后会得到各种结果,例如

出现 1.××+1.××=±1×.××1. \times \cdots \times +1. \times \cdots \times=\pm 1\times .\times\cdots\times 这种情况,需要进行右规,尾数右移一位,阶码加1。尾数右移时,最高位1被移到小数点前一位作为隐藏位,最后一位移出时要考虑舍入;

出现1.×× 1.××=±0.001××1. \times \cdots \times -\ 1. \times \cdots \times=\pm 0.0\cdots01\times\cdots \times这种情况,需要进行左规,尾数每左移一位,阶码减1。需要一直将第一位1移到小数点左边。

  1. 舍入处理

    本例中无需舍入。

对阶操作或右规时,尾数需要向右移动,这样,尾数的最低位就会被移出。为了保证运算精度,移出的位通常会被保留下来参与运算,最后再对结果进行舍入处理,使其符合 IEEE 754IEEE\ 754 标准。常见的舍入方法有两种:

  • “0” 舍 “1” 入法:类似于十进制中的“四舍五入法”,即在尾数右移时,被移去的最高数值位为0,则舍去;被移去的最高数值位为1,则在尾数的末尾加1。

IEEE 754IEEE\ 754 舍入方法取“0” 舍 “1” 入法,对于floatfloat 浮点数加减运算,若运算结果直接取阶大的数,则两操作数的阶码之差大于等于()

A: 24 B:25 C:126 D:128

【解】:若两数阶差E\bigtriangleup E 为24, 则阶码小的那个数 2424 位尾数(包括隐藏位11)右移全部丢弃,由于移去的最高数位为1,则末尾加1,这样运算结果不一定是阶大的那个数;若阶差为25,则尾数全部为0,阶码小的数直接真值为0,符合要求;所以本题选B。

  • 恒置“1”法:尾数右移时,不论丢掉的最高数值位是“1”还是“0”,都使右移后的尾数末尾恒置为“1”。
  • 截断法:直接截取所需位数,丢弃后面的所有位,这种舍入处理最简单。
  1. 判断溢出(2015年考察)

在进行尾数规格化和尾数舍入时,可能会对结果的阶码执行加、减运算。因此,必须考虑结果的指数溢出问题

  • 若一个正指数超过了最大允许值(12712710231023),则发生指数上溢,机器产生异常,也有的机器把结果置为++∞(数符为0时)或-∞(数符为1时)后,继续执行下去
  • 若**一个负指数超过了最小允许值(-149或1074),则发生指数下溢,**此时,**一般把结果置为+0+0(数符为0时)或0-0(数符为1时),**也有的机器引起异常。

溢出判断是在上述尾数规格化 和 尾数舍入 过程中进行的,只要涉及阶码求和/差,就可能发生溢出。发生溢出的情况可能有以下三种:

  • 右规和尾数舍入:一个数值很大的尾数舍入时,可能因为末尾加1而发生尾数溢出,此时,可以通过右规来调整尾数和阶。右规时阶加1,导致阶增大,因此需要判断是否发生了指数上溢。只有当调整前的阶码为1111 11101111\ 1110, 加11后,才会变成 1111 11111111\ 1111 而发生**上溢;**如果右规前阶码已经是1111 11111111\ 1111 ,右规后变0000 00000000\ 0000,因而会造成判断出错。所以,右规前应先判断阶码是否为全1,若是,则不需右规,直接置结果为指数上溢;否则,阶码加1,然后判断阶码是否为全1来确定是否指数上溢。
  • 左规:左规时数值位逐次左移,阶码逐次减1,所以左规使阶码减小,故需判断是否发生指数下溢。其判断规则与指数上溢类似,首先判断阶码是否为全0,若是,则直接置结果为指数下溢;否则,阶码减1,然后判断阶码是否为全0来确定是否指数下溢。

从浮点数加、减运算过程可以看出,浮点数的溢出并不以尾数溢出来判断,尾数溢出可以通过右规操作得到纠正。**运算结果是否溢出主要看结果的指数是否发生了上溢,**因此是由指数上溢来判断的。(即2015年真题所说的尾数溢出时结果不一定溢出)

下例为IEEE 754IEEE\ 754 加减法的示例,目前只考察过补码表示尾数和阶码的浮点数的加减运算,由于IEEE 754IEEE\ 754 加减运算的尾数加减涉及到了原码加减运算,所以有点麻烦,考察可能性不大,了解即可。

​ 用 IEEE 754IEEE\ 754 单精度浮点数加减运算计算0.5+(0.4375)0.5+(-0.4375)

​ 解:x=0.5=0.1000B=(1.000)2×21x=0.5=0.100\cdots 0B=(1.00\cdots 0)_2\times2^{-1}

y=0.4325=0.011100B=(1.1100)2×22 y=-0.4325=-0.01110…0B=(-1.1100)_2×2^{-2}

image-20230829123648765 image-20230829123728128

image-20230829135730704image-20230829231030235


2.4 C语言中的类型转换、数据对齐与大/小端存放方式

该考点考过8次选择,2道大题与之有关,属于重要知识点!

2.4.1 整数类型强制转换

类型shortshortunsigned shortunsigned\ shortintint unsigned intunsigned\ int
shortshort(16位)机器数不变,改变解释方式符号扩展先符号扩展为intint,再改变解释方式
unsigned shortunsigned\ short(16位)机器数不变,改变解释方式高16位零扩展,再改变解释方式高16位零扩展,无符号数真值不变
intint(32位)高位截断高位截断机器数不变,改变解释方式
unsigned intunsigned\ int(32位)高位截断高位截断机器数不变,改变解释方式
  1. 有符号数与无符号数的转换

C语言允许在不同的数据类型之间做强制类型转换,有符号与无符号整数的互相转换的规则是:不改变机器数(存储值不变),仅改变这些位的解释方式。

【例1】:求 yy 的值

short x = -4320;
unsigned short y = (unsigned short) x;

解:[x]=1110 1111 0010 0000B[x]_补=1110\ 1111\ 0010\ 0000B,保持机器数不变,将其当作无符号数解释,得y=61216y=61216

【例2】:求 sisi 的值 (2019年真题改编

unsigned short usi = 62080;
short si = usi;

解:usiusi 的机器数为1111 0010 1000 0000B1111\ 0010\ 1000\ 0000B ,改变解释方式为补码,将其转变为原码为1000 1101 1000 0000B1000\ 1101\ 1000\ 0000B, 即 sisi 的值为 3456-3456;

提速小技巧】:当遇到同类型(同长度)的有符号数 sisi 与 无符号数 usiusi 转换的题型时,若 sisi 为负数,可以用简便方法 si+usi=2n|si|+|usi|=2^nnn 为数据位数);证明见书本P65.

  1. 不同整数类型之间转换

C语言之支持在不同字长的整数之间进行数据转换,例如 intint 型变量与 shortshort 变量相加时,会自动对 shortshort 变量进行强制类型转换,通过符号扩展将其变为 intint 变量。

  • 大字长变量向小字长变量强制类型转换时,系统把**高位部分直接截断,**低位直接赋值;
  • 小字长变量向大字长变量强制类型转换时
    • 若原数字是无符号数,进行零扩展(即高位直接补零);
    • 若原数字是有符号数,进行符号扩展(即高位直接补充符号位);

【例3】:求 yy 的值

int x = -34990;
short y = (short)x;

解:[x]=1111 1111 1111 1111 0111 0111 0101 0010B[x]_补=1111\ 1111\ 1111\ 1111\ 0111\ 0111\ 0101\ 0010B, 高16位直接截断,[y]=0111 0111 0101 0010B[y]_补=0111\ 0111\ 0101\ 0010B,所以y=30546y=30546

【例4】:求 yy 的值

short x = -1234;
int y = (int)x;

解:[x]=1111 1011 0010 1110B[x]_补=1111\ 1011\ 0010\ 1110B, 将其高位补16个1(符号扩展)得到[y]=1111 1111 1111 1111 1111 1011 0010 1110B[y]_补=1111\ 1111\ 1111\ 1111\ 1111\ 1011\ 0010\ 1110B,所以 y=1234y = -1234

或者换个思路,intint 的表示范围更大,这两个变量之间的转换不会有精度丢失,所以不变;

【例5】:yy 的机器数为?(2012真题)

unsigned short x = 65530;
unsigned int y = x;

解:xx 的机器数为FFFAHFFFAH,由于xx是无符号数,所以高位补0,得到 yy 的机器数为 0000 FFFAH0000\ FFFAH

【例6】:求 yy 的值

short x = -1234;
unsigned int y = (unsigned int)x;

解:[x]=1111 1011 0010 1110B[x]_补=1111\ 1011\ 0010\ 1110B,将[x][x]_补 的高位补充 16个1(因为 xx是有符号数,进行符号扩展)得到[y]=1111 1111 1111 1111 1111 1011 0010 1110B[y]_补=1111\ 1111\ 1111\ 1111\ 1111\ 1011\ 0010\ 1110B,将其解释为无符号数得到 y=4294966062y=4294966062.

:star:从本例可以看出将带符号 shortshort 转换为 unsigned intunsigned\ int 的思路是:先将带符号shortshort 按符号扩展为 intint,再改变解释方式,按无符号数的编码规则对其解读。

【例7】:求 ii 的值

short si = -32768;
unsigned short usi = si;
int i = usi;

解:[si]=1000 0000 0000 0000[si]_补=1000\ 0000\ 0000\ 0000 ,无符号数 usiusi 与它机器数一样,然后把它的值赋给 intint 型变量 ii, 由于后者足以表示前者的表示范围,进行零扩展即可,则 ii 的值为3276832768


2.4.2 整数与浮点数强制类型转换

类型intintfloatfloatdoubledouble
intint (4B)可能会丢失精度完美转换
floatfloat (4B)可能会溢出或精度丢失完美转换
doubledouble(8B)可能会溢出或精度丢失可能会溢出或精度丢失
  1. intint 转换为 floatfloat

由于 floatfloat 表示范围比 intint 大,因此不会溢出;但是 floatfloat 尾数共有2424位,而 intint 数值部分有 3131 位,如果 intint 的第 253125\sim 31位非00​时,需要舍入,会影响精度;

精度丢失2.drawio
  1. intintfloatfloat 转换为 doubledouble

由于 doubledouble 的尾数部分有53位,表示范围更大,有效位更多,因此将intintfloatfloat 转换为 doubledouble 不会发生舍入或溢出。

  1. doubledouble 转换为 floatfloat

由于 doubledouble 的尾数部分有53位,表示范围更大,有效位更多,因此可能会发生 溢出或舍入。

  1. floatfloatdoubledouble 转换为 intint

由于 intint 的表示范围小于 floatfloatdoubledouble ,因此可能会发生溢出;

由于 intint 不能表示小数,因此转换后会将小数部分截断,所以可能会丢失精度;

【例题】

  1. 假定变量 xfdx、f、d 的类型分别为intfloatdoubleint、float、double。判断下面哪些说法正确。

x == (int)(float)xx == (int)(double)xd == (double)(float)df == (float)(double)f

f == -(-f) d * d >= 0(f+d)-f==d(d+f)-d==f

解:①假,②真,③假,④真,⑤真,浮点数有一个数符位,表示其真值的正负性,因此浮点数取反只要改变符号位即可;⑥真,注意有可能溢出到正无穷;

⑦假,**如果二者阶码相差过大,相加后对结果舍入可能会将阶码小的那个忽略掉,**例如f=1e20,d=1.0,执行f+d会将结果舍入为1e20,则等式左边为0,两边不等;⑧假,与⑦同理。

  1. 假定变量 ifdi、f、d 的类型分别为intfloatdoubleint、float、double,已知 i=16777217(224+1),f=2.34567e4,d=3.14e150i=16777217(2^{24}+1), f = 2.34567e4, d=3.14e150,则下列结果正确的是()

A:i == (int)(float)i B:f == (float)(int)f C:f == (float)(double)f D:(d+f)-d==f

解:A错,超出了floatfloat 尾数所能表示的精度范围,精度丢失;B错,intint 表示不了小数,精度丢失,C正确,doubledouble 很厉害,D错,二者指数相差太大,对阶后,ff 的尾数部分原来的有效位全部移出,直接变为00,等式左边为0则等式不成立。


2.4.3 数据的存储与排布

数据类型C声明无符号版本(32位)字节数(64位)字节数
字符型charunsigned char11
短整型shortunsigned short22
整型intunsigned44
长整型longunsigned long48
32位整数int32_tuint32_t44
64位整数int64_tuint64_t88
字符指针char *48
浮点型float44
双精度浮点型double88

请注意,在 C 语言中,char 类型默认是有符号的,但可以通过指定 signed 关键字来明确表示有符号字符型。同样的,指针类型的大小会根据操作系统的位数变化(32位系统上是4字节,64位系统上是8字节),这是因为指针需要能够指向系统中任何位置的地址。

大多数计算机使用8位的块,也就是字节,作为最小可寻址的内存单位。机器级程序将内 存视作一个巨大的字节数组,称为虚拟内存。内存的每个字节都由一个唯一的数据来标识, 称为它的地址

对于跨越多个字节的程序对象,我们必须建立两个规则:

  1. 这个对象的地址是什么? -- 以最低地址作为该对象的存储地址
  2. 如何在内存中排列这些字节? --大端方式和小端方式、数据对齐

2.4.3.1大小端方式

对于第一个规则,我们的回答是:在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用字节中最小的地址。例如,如果一个intint类型对象的地址是 0x1000x100,那么该对象存储在 0x1000x1030x100-0x103上。

对于第二个规则,我们给出两种规则,分别是大端法小端法

考虑一个 ww 位的整数,其位表示为[xω1,xω2,...,x1,x0] [x_{ω−1} , x_{ω−2} , ..., x_1 , x_0 ],我们将xω1x_{ω−1} 称作最高有效位x0x_0称作最低有效位。这里我们要求ww 是8的倍数,那么这些位就能被分组为字节,其中最高有效字节包含位[xω1,xω2,...,xω7,xω8][x_{ω−1} , x_{ω−2} , ..., x_{ω−7} , x_{ω−8} ] ,最低有效字节包含位[x7,x6,...,x1,x0][x_7 , x_6 , ..., x_1 , x_0 ] ,其他字节包含中间的位。

某些机器(intel 80x86) 选择在内存中按照从最低有效字节到最高有效字节的顺序来存储对象,称为小端法,而另外一些机器(IBM360 /370,MIPS)则按照从最高有效字节到最低有效字节的顺序来存储,称为大端法。 这里举一个例子帮助同学们理解: 某机器数的16进制表示为 01 23 45 67H,存放在从0001H开始的内存单元中。

当采用小端法时:

0001H0002H0003H0004H
67H45H23H01H

当采用大端法时:

0001H0002H0003H0004H
01H23H45H67H

2.4.3.2数据对齐

计算机为什么要进行数据对齐呢?

假设某计算机按字节编址,主存每次最多发送32位数据,并与宽度为32位的存储器总线相 连,假设我们访问的某个double类型变量并没有遵循对齐规则(在32位计算机下存放的起始地址并不整除4),会发生什么情况?

当存储器一次性将32位数据通过总线发送给CPU时,CPU并不清楚存储体发送的这4个字节的先后顺序,但CPU必须需要知道这四个字节哪个是0哪个是1哪个是2哪个是3。因此,我们做出了规定---我每一次发送的4个字节的数据,必须以4B的整数倍为起点,从低到高依次由0-3号存储体发送。因此如果这个double变量如果不按4B对齐,那么从存储器读取该 变量就需要3个存储周期;如果double变量按照4B对齐,那么从存储器读取该变量就只需要两个存储周期。

因此,我们可以总结出计算机要进行数据对齐的原因:提高数据存取的效率,在尽可能少的存储周期内取用数据,同时尽量节省存储空间,避免不必要的空隙。

平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些平台只能在某些地址处取得某些特定类型的数据,否则抛出硬件异常。比如,当一个平台要取一个整型数据时只能在地址为4的倍数的位置取得,那么这时就需要内存对齐,否则无法访问到该整型数据。

1.基本类型变量的对齐规则

基本类型变量我们讨论char short int double 四种,其大小为1 2 4 8 字节。基本数据类型变量的对齐规则与计算机的字长密切相关,不过,我们一般只讨论32位字长和64位字长的计算机。首先,我们讨论32位字长的情况。(408考试一般都是32位机器

  1. 对于char类型的变量,其对齐没有任何限制,因为无论其存放在哪里,都可以在1个存储周期内取得数据
  2. 对于short类型变量,其变量地址必须模2为0。这里我们进一步加以讨论,看看为什么模4余3和模4余1不行:
    • 模4余3的存放方式首先排除 这种存放方式会导致取用该short类型变量需要2个时钟周期。
    • 模4余1貌似可行,但是如果该short类型变量后再跟随一个short变量呢?
  3. 对于int类型变量,其变量地址必须模4为0,这样才能保证能在一个存储周期内从存储器取得数据。
  4. 对于double类型变量。其本身就占用8个字节,从存储器中取用该变量至少需要两个存储周期。在变量地址满足模4余0的情况下,就可以在存储周期内获得数据。模8余0条件更为严苛,会浪费存储空间,我们不予采用。
边界对齐.drawio

下面给出64位计算机基本数据类型变量的对齐规则

  1. 对于 char 类型的变量,其对齐没有任何限制,因为无论其存放在哪里,都可以在1个存储周期内取得数据。
  2. 对于 short 类型变量,其变量地址必须模2为0,与32位字长的情况相同。
  3. 对于 int 类型变量,其变量地址必须模4为0,以确保能在一个存储周期内从存储器取得数据,也与32位字长的情况相同。
  4. 对于 double 类型变量,其占用8个字节,因此变量地址必须模8为0,以保证在1个存储周期内获得数据。模8余0条件更为严苛,但不会浪费存储空间,因为 double 类型变量本身就占用8个字节,而不像 shortint 可能只占用2或4个字节。
边界对齐64位.drawio
2.:star:结构体变量的对齐规则
  1. 整个结构体变量的对齐方式与其中对齐方式最严格的成员相同。
  2. 结构体成员在满足其对齐方式的条件下,取最小可用位置作为成员在结构体中的偏移量。 (见S1的变量b起始位置是2而不是3)
  3. 结构体大小应当为对齐边界长度的整数倍(结构体的大小必须是其最大成员大小的倍数,以保证结构体在数组中也是对齐排放。)
  4. 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
结构体对齐.drawio

【注意】:64位整型变量long long在不同计算机中的对齐规则不同,在windows中按照8字节对齐,在32位linux上按照4B对齐,在64位linux中按8B对齐。

//练习1
struct S1{
	char c1;
	int i;
	char c2;
};
printf("%d\n", printf(struct S1);//12

//练习2 
struct S2 {
    char c1;    
    char c2;    
    int i; 
 }; 
 printf("%d\n", sizeof(struct S2));//8

//练习3 
struct S3 {
    double d;    
    char c;    
    int i; 
}; 
printf("%d\n", sizeof(struct S3));//16

//练习4-结构体嵌套问题 
struct S4 {
    char c1;    
    struct S3 s3;    
    double d1; 
}; 
printf("%d\n", sizeof(struct S4));//32   

:star:上面计算结构体大小,是在64位机器上计算的,double按照8B对齐;在32位系统中,double按照4B对齐。408考试一般按照32位机器,也就是说double按照4B对齐,切记看清题目!!

struct S4.drawio struct .drawio struct 32位机器.drawio

2.5 运算方法和运算电路

image-20230829231935420

其他画法:

与门或门非门与非门或非门异或门同或门
image-20230829232214356image-20230829232305443image-20230829232337589image-20230829232437574image-20230829232500527image-20230829232521559image-20230829232555355

【补充】

  1. 异或运算的妙用:n bit 进行异或,若有奇数个 1,则异或结果为 1;若有偶数个 1,则异或结果为 0;image-20230829232825528

  2. 同或运算的妙用:n bit 进行同或,若有奇数个 1,则同或结果为 0;若有偶数个 1,则同或结果为 1.image-20230829232904683

  3. 门电路的变形画法

image-20230829233035973

2.5.1 **补码加减运算器及标志位的生成和使用 **

这部分属于2022年新大纲新加的内容,这里面的ALU和各种标志位的使用也要注意一下,可能会在大题中与指令系统结合起来考察。

image.png

重要认识1:计算机中所有运算都基于加法器实现!

重要认识2:加法器不知道所运算的是带符号数还是无符号数。

重要认识3:加法器不判定对错,总是取低n位作为结果,并生成标志信息。

image-20230830124116135
  1. 指针、地址等通常被说明为无符号整数,因而在进行指针或地址 运算时,需要进行无符号整数的加、减运算
  2. 无符号整数和带符号整数的加、减运算电路完全一样,这个运算电路称为整数加减运算部件基于带标志加法器****实现
  3. 计算机中的加法器,因为只有n位,所以是一种模img运算系统!
  4. 数值数据和逻辑数据在形式上没有差别,计算机区分数值数据和逻辑数据的主要方法是:用不同的指令操作码来区分本指令处理的是哪种数据。(如果是逻辑运算(与或非),CPU就把它们当成逻辑数据,如果是算术运算(加减乘除),CPU就把他们当作数值数据)
  5. 由于计算机中有符号数和无符号数公用一个ALU,且它们对应的机器数(二进制位)进行 加减法时的规则完全相同,所以不管是无符号数还是有符号数,其运算后标志寄存器内都会产生这4个标志位。举个例子,无符号数运算产生的SF和OF标志的含义是:将运算数和运算结果对应的机器数(二进制序列)解释为有符号数时,运算结果的符号和溢出情况。
  1. 算术逻辑单元 ALUALU

ALUALU的核心部件是带标志加法器,能进行多种算术运算和逻辑运算。如图,XXYY 是两个 nn 位操作数输入端、 CinCin 是进位输入端、ALUopALU_{op} 是操作控制端,用来决定ALUALU 所执行的功能。ALUopALU_{op}的位数决定了操作的种类,例如,当位数为3时,ALUALU最多只有8种操作

  1. 多路选择器MUXMUX

有多路输入,通过控制信号来选择让其中的某条输入通路被打开;控制信号:nn 位,取决于有多少路输入,nn 位控制信号,对应 2n2^n 路。

  1. 工作原理

n bitn\ bit 补码 X+YX+Y,按位相加即可,MUXMUX 选择00 的那条通路打开,subsub 控制信号输入为00, 运算结果只保留 nn 位,高位直接丢弃;

n bitn\ bit 补码 XYX-Y,将减数 YY 全部按位取反,末位 +1, 得到[Y][-Y]_补,然后减法变加法;具体来说,当YY 输入时,MUXMUX 选择让11 的那条通路打开,经过非门的按位取反以后,再加上来自 subsub的减法控制信号11,通过这一系列操作就让减法变成加法了。

  1. 标志位生成

问题:为什么要生成并保存条件标志?

为了在分支指令(条件转移指令)中被用作是否转移执行的条件!

:one:零标志位 ZFZF(Zero Flag)

ZF=1ZF=1 表示运算结果为 00, 不管把操作数当作有符号数还是无符号数,ZFZF 都有意义。

:two:溢出标志OFOF(Overflow Flag)

OF=1OF=1 表示有符号数的加减运算发生了溢出,对于无符号整数的运算,OFOF 位没有意义。

  • 硬件的计算方法:由上面的右图中的带标志加法器内部结构可以看到,判断溢出可以由最高位进位Cout(Cn)Cout(C_n) 和次高位进位 Cn1C_{n-1} 求异或得到。这与我们之前提到的判断补码相加是否溢出的方法一致:判断最高位进位和次高位进位是否相同)。

  • **对于无符号数 OF=1OF=1不能用作溢出判断的解释:**对于两个无符号数而言,如果两个高位为0的数相加,得到的结果高位为1,在无符号数中这种情况是可以成立的,而对于有符号数则不成立,因为两个正数相加是不会得到负数的,所以有符号数可以判断溢出,而对于无符号数则不行。

:three:符号标志位SFSF(Signal Flag)

表示有符号数加减运算结果的符号位,因此直接取结果的最高位作为SFSF

【注意】:对于无符号数的加减运算无意义,因为无符号数加减法的最高位没有特殊意义,而且无符号数也没有负数。

:four:进/借位标志CFCF(Carry Flag)

加法操作时,CFCF为进位标志;减法操作时,CFCF为借位标志。仅对无符号数的加减运算有意义。

  • 硬件计算方法:加法时,若CF=1CF=1 表示无符号数相加有溢出,因此此时CFCF应该等于最高位产生的进位CoutCout;:star::star:减法时,若CF=1CF=1 表示无符号数相减最高位有借位,因此此时CFCF应该等于最高位产生的进位CoutC_{out}取反;因此,综合起来,CF=Sub CoutCF=Sub\ \oplus Cout;(SubSub为加/减法控制信号,Sub=1Sub=1表示减法,Sub=0Sub=0表示加法;有时没有SubSub控制信号,则CF=Cin CoutCF=Cin \ \oplus CoutCinCin 为来自低位的进位信号)

  • 对于有符号数的加减运算无意义!

  • 虽然无符号数加减法的溢出可以通过CFCF位来体现,但是无符号数不经常分析溢出,也没有像OFOF这样的专门溢出标志位。其原因主要是:对于 nn 位无符号数,其算术运算实际上是一种以 2n2^n 为模的回绕算术。例如无符号数表示的地址,当计算得到的高地址溢出时会回绕到低地址,结果仍然在 2n2^n 域内。

【例题】:

已知无符号整数ABA、B 的机器数分别为BCH71HBCH、71H。 如果在 8 位加法器中计算ABA-B ,那么加法器的低位进位输入CinCin以及运算后进/借位标志CFCF、最高位进位CoutCout 分别是()

A:1、0、1 B:0、1、1 C:1、1、1 D:0、0、0

解:执行减法运算,这里的低位进位输入CinCin相当于SubSub,所以是11; AB=[A][B]=[A]+[B]=BCH+8FH=0100 1011BA-B=[A]_补-[B]_补=[A]_补+[-B]_补=BCH+8FH=0100\ 1011B,最高位进位Cout=1Cout=1CFCF 通过CinCinCoutCout 求异或得00或者无符号减法运算时,CFCF就等于最高位进位的取反),所以选A。

  1. :star::star:标志位的使用统考中喜欢考选择题或者将它和指令系统的转移指令之类的结合起来在大题中考察,例如2013年真题)

(1):无符号数比较大小:假设两操作数的机器数为ABA、B ,执行 ABA-B ,其比较大小的结果可以通过ZFCFZF、CF 标志位来反映。

  • A=BA=B,则 AB=0A-B=0 ,所以 ZF=1ZF=1
  • ABA\ne B,则 AB0A-B\ne 0 ,所以 ZF=0ZF=0
  • A<BA<B,则ABA-B会产生借位,所以CF=1CF=1
  • A>BA>B, 则ABA-B不会产生借位,结果不为0,所以CF=0CF=0并且 ZF=0ZF=0
  • ABA\le B,综合小于和等于情况下的标志位,所以CF=1CF=1或者ZF=1ZF=1
  • ABA\ge B, 则ABA-B不会产生借位,所以CF=0CF=0

(2):有符号数比较大小

有符号数比较大小的结果可以通过ZFSFOFZF、SF、OF 标志位来反映。核心思想是通过考察实际运算结果的正负(SFSF)的同时考察有无溢出(OFOF) 来比较有符号数的大小。假设两操作数的机器数为ABA、B ,执行 ABA-B ,有以下几种结果:

  • A=BA=B,则 AB=0A-B=0 ,所以 ZF=1ZF=1

  • ABA\ne B,则 AB0A-B\ne 0 ,所以 ZF=0ZF=0

  • A<BA<B,则SFOF=1SF\oplus OF=1

    • 情况1:SF=1,OF=0SF=1,OF=0,这种情况下没有溢出,SFSF代表运算后真正的符号,SF=1SF=1 表示真实运算结果为负,所以 A<BA<B
    • 情况2:SF=0,OF=1SF=0,OF=1,发生溢出,若SF=0SF=0 则一定是负数减去正数(负数加负数)发生溢出导致结果为正,所以 A<BA<B
  • A>BA>B, 则SFOF=0SF\oplus OF=0 并且 ZF=0ZF=0

    • 情况1:SF=0,OF=0,ZF=0SF=0,OF=0,ZF=0,在没有溢出的情况下,即OF=0OF=0,此时SF=0SF=0代表实际结果非负,若ZF=0ZF=0,则说明结果为正,则A>BA>B;
    • 情况2:SF=1,OF=1,ZF=0SF=1,OF=1,ZF=0,在有溢出的情况下,即OF=1OF=1 ,此时SF=1SF=1表示一定是正数减去负数(正正相加)发生溢出导致结果为负,则 A>BA>B
  • ABA\ge B,综合大于和等于情况下的标志位,有SFOF=0SF\oplus OF=0 或者 ZF=1ZF=1

  • ABA\le B,综合小于和等于情况下的标志位,有SFOF=1SF\oplus OF=1 或者 ZF=1ZF=1


2.5.2 乘除法运算的基本原理

_85221ccbb57bf7b3870b8ad480eb5981_269398695_Screenshot_2023-08-26-19-29-59-112_com.jideos.jnotes

只要知道通过 控制逻辑 循环地执行 加法和移位操作就可以实现乘/除法指令的功能就足够了,具体原理不必深究。

  1. 运算器的基本组成
image-20230830121707023

乘法运算由累加和右移操作来实现

  1. 原码乘法运算

    image-20230830121959444image-20230830122149999

  2. 补码乘法运算

    image-20230830122732364image-20230830123039796

  3. 原/补码除法运算

image-20230830125326776image-20230830125420109


2.5.3 乘法电路和除法电路的基本结构

  1. 无符号数乘法电路:

    image-20230830151923830

如图,是实现32位无符号数乘法运算的逻辑结构图。

  • 被乘数寄存器 XX 用于存放被乘数;

  • 乘积寄存器 PP 开始时置初始部分积 P0=0P_0=0, 结束时存放的是6464位乘积的高3232位;

  • 乘数寄存器YY开始时置乘数,结束时存放的是6464位乘积的低3232位;

  • 进位触发器CC保存加法器的进位信号;

  • 计数器CnCn存放循环次数,初值是3232,每循环一次,CnCn减1,当Cn=0Cn=0时,乘法运算结束;

  • ALUALU 是乘法核心部件,在控制逻辑控制下,对乘积寄存器PP和被乘数寄存器XX的内容进行“加”运算,在“写使能”控制下运算结果被送回乘积寄存器PP,进位位存放在CC中。

每次循环都要对进位位CC、乘积寄存器PP和乘数寄存器YY实现同步“逻辑右移”,此时,进位信号CC移入寄存器PP的最高位,寄存器PP的最低位移出到寄存器YY的最高位,寄存器YY的最低位移出,00移入进位位CC中。从最低位YnY_n开始,逐次把乘数的各个数位YniY_{n-i}移到寄存器Y的最低位上。因此,寄存器YY的最低位被送到控制逻辑以决定被乘数是否“加”到部分积上。

无符号整数乘法运算的溢出判断:【2020年考察】

  • nn 位乘以 nn 位,若用 2n2n 位保存乘积,则不会溢出;
  • nn 位乘以 nn 位,若用 2n2n 位保存中间结果,最后截取末尾 nn 位作为最终乘积,可能会溢出
    • 手算判溢出:代入十进制计算乘法结果,判断该结果是否超出了 nn 位无符号数所能表示的范围[0,2n1][0,2^n-1],若超出,则溢出;
    • 机器判溢出若乘积高 nn 位为全0,则不溢出。(与低 nn 位是啥无关,因为低nn位即使全1也没有超出无符号数的表示范围);
  1. 有符号数(补码)乘法电路
image-20230830125753208

如图是实现 3232 位补码一位乘法(BoothBooth) 的逻辑结构图。

  • 乘数寄存器YY 后面增添一位附加位(辅助位Y1=0Y_{-1}=0);

  • 控制逻辑每次根据寄存器YY 的最后两位来执行加法、移位运算,并将结果写回寄存器中;

    【2020年真题】:控制逻辑的作用是什么:控制加法操作和移位操作的次数,并且发出控制信号来执行判断、加法、移位等操作。

  • 计数器CnC_n 用来控制循环次数,即该算法会执行 nn

【2020年真题】:若计算机的指令系统中没有乘法指令,但是有加法、减法和位移等指令,则在计算机上也能实现乘法运算,为什么?

答:由BoothBooth 乘法的原理可知,乘法运算可以转换为加法运算和移位运算。所以在计算机中,若没有乘法指令,可以通过不断执行比较、加法和移位等指令来实现乘法指令的功能。

有符号数(补码)乘法的溢出判断【2020年考察】

  • nn 位乘以 nn 位,若用 2n2n 位保存乘积,则不会溢出;
  • nn 位乘以 nn 位,若用 2n2n 位保存中间结果,最后截取末尾 nn 位作为最终乘积,可能会溢出
    • 手算判溢出:代入十进制计算乘法结果,判断该结果是否超出了 nn 位有符号数所能表示的范围,如补码[2n1,2n11][-2^{n-1},2^{n-1}-1],若超出,则溢出;
    • 机器判溢出:仅当高 n+1n+1 位全1或全0时,不溢出。:star: (对于 nn 位有符号整数乘法指令,其乘积机器数包含11位符号位,若乘积为正数,则当 2n2n 位乘积的高 n+1n+1 位全为 0时,截取nn位后真值不变,即结果无溢出;若乘积为负数,则当2n2n 位乘积的高 n+1n+1 位全为 1时,截取nn位后真值不变,即结果无溢出)
  1. 除法电路
image-20230830150436181
  • ALUALU 执行除数寄存器 YY 与余数寄存器 RR 的加减运算;
  • 控制逻辑根据运算结果来确定上商的数值,然后寄存器 RQR、Q 同步左移,空出的低位用来上商;
  • 计数器用来控制循环次数。

该除法电路通过循环执行加法、左移来实现除法运算。