跳转至

第二章数据的表示和运算

【考频分析】

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

2.1 定点数的表示和运算

202404291041744.png

  1. 计算机内部所有信息都用二进制(即:0和1)进行编码,用二进制编码的原因:
  • 制造二个稳定态的物理器件容易(电位高/低,脉冲有/无,正/负极)
  • 二进制编码、计数、运算规则简单
  • 正好与逻辑命题真/假对应,便于逻辑运算
  • 可方便地用逻辑电路实现算术运算
  1. 机器数和真值:像\(-3、+5\)这样带“\(+\)”、“\(–\)”号的数称为真值,可以将其理解为真正的数,一般用十进制来表示,也可以用二进制来表示。将数值数据在计算机中编码表示后的数据称为机器数,一般采用补码表示,也可以用原码、反码、移码来表示(即用0和1编码的计算机内部的0/1序列)

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

  3. 定点整数:将小数点的位置固定在数据的最低位之后

6797f6479878426eae605c1d0b10dcde.png

  • 定点小数:将小数点的位置固定在数据的最高位之前

a6b79f61018e450ca43c2fbe3c2d6622.png

  1. 有符号定点整数的表示(原码、补码、反码和移码)

  2. 原码:最高位表示真值的符号(1 表示负,0 表示正),其余各位表示真值的绝对值

202405011247847.png

优点:表示方法简单直观

缺点

  1. 真值\(0\)在原码中有两种不同的表示,不利于程序员编程
  2. 原码加减法运算规则复杂,符号位不能参与运算,需要设计复杂的硬件电路。现代计算机不用原码表示整数,只用定点原码小数表示浮点数的尾数。
  3. 加减运算方式不统一
  4. 特别当 a\<b 时,实现a-b比较困难
  • 反码:正数的反码与原码一样,负数的反码其数值部分全部取反

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

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

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

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

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

  • 引入补码的原因:在原码的加减运算中,由于最高位表示的是符号位,若将其直接参与运算,其得到的结果是错误的,除非改造ALU,但是成本会很高。联想到【如果有一个时钟,指针开始时指向的是10,如果要让其指向7,那么有两种方法,要么逆时针旋转至7,相当于做减法;要么顺时针旋转至7,相当于做加法】,我们可以用加法实现减法运算,为此引入了补码表示法。其表示方法为正数仍为其原码,负数为模(\(n\)位补码的模为\(2^n\))与该负数绝对值之差。
  • 补码的 表示范围:\(n+1\)位补码的表示范围为 \(-2^n\)~\(2^n-1\),比原码多表示一个\(-2^n\),例如,8位补码的表示范围为-128 ~ 127。
  • 机器数0的表示:不同于原码和反码有正零和负零之分,补码的零只有一种表示方法,即数值位和符号位为全0.
  • 补码表示的一些常见二进制形式:\(n+1\)位补码的最小整数\(-2^n\)的二进制形式为\(1000\cdots00\); 表示的最大整数形式为\(0111\cdots11\);补码表示-1的形式为\(111\cdots111\)。
  • 目前在计算机中普遍采用补码表示有符号定点整数,例如C语言中char、 short、 int、 long型整数都是补码表示。
  • 为什么用补码表示带符号整数? (50年代以来,所有计算机都用补码来表示带符号整数)
  • 补码运算系统是模运算系统,加、减运算统一
  • 数0的表示唯一,方便使用
  • 比原码多表示一个最小负数
  1. 根据补码的设计思想和编码规则,以下技巧可以更快地分析负数补码的真值

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

202405061833038.png

  • 在非负半轴 上符号位为0,补码从000→011,真值从0→3不断增大。 在负半轴上,符号位为1,补码从100→111,真值从\(-4→-1\)不断增大; 所以可以得到如下结论:补码符号位不变时,其真值随着数值位的增大而增大。该结论可以帮助比较补码的真值大小,例如补码 \(F340H\<FF30H\<FF40H、7010H<7500H<7740H\)。
  • 若补码之间跨越了正负半轴,直接通过符号位比较正负,例如\(F300H<7070H\)
  • 若把两个互为相反数的补码相加,得到的真值为0,但是最高位进位为1。例如\((-3)+3:101+011=(1)000、(-2)+2=110+010=(1)000\)

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

  • 移码: 在真值的基础上加上一个偏置值,可以得到移码。\(n+1\)位移码的偏置值通常取\(2^n\)或\(2^n-1\)。移码只用于表示定点整数。
  • 当偏置值为\(2^n\)时,则在补码的基础上,对符号位取反即可; (在数轴上,移码所表示的范围对应于真值在数轴上的范围向轴的正方向移动\(2^n\)个单元)202405011503848.png
  • 当偏置值为\(2^n-1\)时,通常用于表示浮点数的指数部分(阶码)。

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

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

1714570685831-b81101bf-7a8c-466a-977f-20d626ece119.png

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

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

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

202404041917667.png

202405011521757.png

  1. 定点小数的原/反/补码表示:\ 其小数表示与整数表示的原理类似,例如,机器字长为8位,若\(x=+0.1101\),则\(x\)的原码,反码,补码的表示分别为\(0.1101000、0.1101000、0.1101000\);\ 对两个定点小数A,B进行加法/减法时,需要先转换成补码,从最低位开始,按位相加(符号位参与运算),并向更高位进位。\ 注意定点小数和定点整数的符号扩展位置不一样,定点小数在数值位后面扩展,定点整数在数值位之前扩展。

image-20230828154102385.png


2.2 整数表示与运算

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

  • 计算机中的数均放在寄存器或内存中,一般寄存器的位数与机器字长相等。无符号整数指整个机器字长的全部二进制位均为数值位,没有符号位,相当于数的绝对值。
  • 一般在全部是正数运算且不出现负值结果的场合下,可使用无符号数表示。例如,地址运算,编号表示,等等
  • 在C语言中,只需要在 int、short等关键字前加unsigned即可定义一个无符号数。当无符号数与有符号数一起运算时,编译器默认将有符号数转换为无符号数再参与运算,运算结果为无符号数。
  • 总是整数,所以很多时候就简称为“无符号数”

无符号数的加法:直接 \(N\) 位二进制按位相加即可;

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

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

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

2.2.2.1 移位运算

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

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

对于无符号整数的逻辑左移,如果最高位移出的 是1,发生溢出 。

  1. 算术移位:编译器是通过补码形式进行算术移位运算,其规则为:算术左移时,低位补0,高位移出;算术右移时,高位补符号位,低位移出。

Info

算术移位特别需要注意,原来的符号位一样移动。因为移位是宏观的变化,硬件在做移位的时候不允许任何元素保持不动。所以左移时,正数有可能变为负数,负数有可能变为正数。因为左移原来的符号位丢了,右边补的是0。而右移时不会改变符号性,因为右移是将数据减半,减半不可能减成相反的符号的。而左移可能溢出,如果补码算术左移使得符号位发生改变,则说明发生溢出,例如8位补码 \(1000\ 0011\) 和 \(0100\ 0011\) 算术左移后均发生溢出。

虽然C语言没有明确规定是采用算术移位还是逻辑移位,但大多数机器对无符号数采用逻辑移位的方式,对有符号数(补码)采用算术移位的方式。因此,编译器只要根据移位数的类型就能选择是采用算术移位还是逻辑移位。

  • 表达式\(x<\<k\) 表示对数 \(x\)左移\(k\)位。事实上,对于左移来说,逻辑移位和算术移位的结果都是一样的,都是丢弃\(k\)个最高位,并在低位补上\(k\) 个0。 每左移一位,相当于对真值扩大一倍,所以左移可能会发生溢出。左移\(k\) 位,相当于\(×2^k\) 。
  • 表达式 \(x >> k\)表示对数 \(x\)右移\(k\)位。 每右移一位,如果移出的是0,则相当于把真值缩小一半,右移\(k\)位,相当于数值\(÷2^k\) 。若移出的是非 0,则说明不能整除\(2^k\) 。
移位寄存器的基本构造和操作
  1. 移位寄存器的结构:移位寄存器通常包含一串触发器(例如D触发器),每个触发器存储一个位。数据可以从一端输入,随着时钟信号的每一次跳变,数据位会从一个触发器传递到下一个。
  2. 右移操作:
    1. 在执行右移操作时,每个触发器的内容向右移动到下一个位置,最左边的位置会变空,而最右边的位则被移出寄存器。
    2. 对于逻辑右移,通常左端会补零。
    3. 对于算术右移,左端通常会补上移位前的最高位(符号位)。
  1. 实现算术右移的方法:符号位复制:在算术右移时,符号位(最左边的位)会被复制并填充到移位后左侧空出的位。这可以通过多种方法实现:
    • 控制逻辑:可以在移位寄存器的设计中加入逻辑门(如与门、或门等),使得在执行右移操作时,符号位能够被自动复制到左侧空出的位置。
    • 条件式硬件连接:在更高级的设计中,可能使用可编程逻辑或条件式控制信号,根据移位操作的类型(逻辑右移还是算术右移),动态选择是补零还是补符号位。
    • 移位控制:控制单元将基于操作的指定(如算术右移指令)来设置移位寄存器的行为,确保正确的位被复制到正确的位置。

通过这种方式,移位寄存器能够在硬件级别上快速高效地执行算术右移操作,同时确保数值的符号位保持一致,从而维持数值的正确性。

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

answer

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

\(x/4\) 相当于将公式算术右移两位, \(x\)为正数,右移高位补 \(0\),\(x\) 的机器数为 \(0011\ 0110B\), 所以\(x/4\)的机器数为 \(0000\ 1101\);\(2z\)相当于\(z\)算术左移1位,\(z\)的机器数为\(1101\ 0101\),所以\(2z\)为\(1010\ 1010\),二者相加结果为\(1011\ 0111B\),两个异号的数相加,不会溢出,所以\(OF=0\)

2.2.2.2 补码加减运算

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

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

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

2.2.2.3 溢出概念及其判别方法

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

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

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

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

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

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

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

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

推导:由于补码减法运算也是转化为加法运算,因此溢出只会发生在以下两种情况

  1. 正数+正数:原来的两个加数的符号位(最高位)都为\(0\),如果两个加数相加完之后,结果的符号位变为\(1\),说明溢出(因为正数相加不可能为负数),此时来自次高位的进位一定为\(1\),而符号位的进位为\(0\);
  2. 负数+负数:原来的两个加数的符号位(最高位)都为\(1\),如果两个加数相加完之后,结果的符号位变为\(0\),说明溢出(因为负数相加不可能为正数),此时来自次高位的进位一定为\(0\),而符号位的进位为\(1\);
  1. 采用双符号位判断

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

符号位\(K_1K_2\)的各种情况如下:

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

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

2.2.3 无符号整数和有符号整数的位权重点

  1. 无符号整数的位权

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

例如\([1011]_{unsigned}=1*2^0+1*2^1+0*2^2+1*2^3=11\)

1714548960421-4a6d8944-0bb2-4d8f-8c58-16886e44633a.png

如图,对于向量的第\(i\)位,用一个长度为2的\(i\)次方的条状图表示。每个位向量对应的值,就等于所有值为 1 的位,所对应的条状图的长度之和。 例如编码0101,就是长度为4(2的2次方)的条状图加上长度为1(2的0次方)的条状图的长度之和。

我们发现这种条状图只能表示非负数,为此引入下面的有符号数表示。

  1. 有符号数的位权

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

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

\([1011]_{补}=1*2^0+1*2^1+0*2^2+1*(-2^3)=-5\)

\([0011]_{补}=1*2^0+1*2^1+0*2^2+0*(-2^3)=3\)

1714549464661-11d7583a-ebe1-428f-884c-30716452cf31.png

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

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

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

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

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

answer

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

【扩展】:原码和反码的位权:

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

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


2.3 浮点数的表示和运算

2.3.1 浮点数的表示

  1. 表示格式: image-20230828203450821.png

  2. 阶码 \(E\):常用 补码 或 移码 表示的定点整数,其位数反映了浮点数的表示范围,阶码的真值反映了小数点的实际位置。

  3. 尾数 \(M\) :常用 原码 或 补码 表示的定点小数, 其位数 \(n\) 反映了浮点数的精度 ,其数符表示浮点数的正负。
  4. 浮点数的真值: \(N=2^E \* M\)

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

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

解:阶码 \(0,01\)对应真值为 \(+1\), 尾数\(1.1001\)转为原码表示为\(1.0111\),真值为\(-0.0111\),因此 \(a\) 的真值为\(2^1\*(-0.0111)=-0.111\)

  1. 浮点数尾数的规格化

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

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

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

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

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

右规示例:image-20230828210854686.png

image-20230828211819383.png

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

这两点必须记住!!!

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

  1. 表示范围

image-20230828212956516.png

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


2.3.2 \(IEEE\ 754\) 标准

image-20230828213817993.png

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

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

image-20230828214126161.png

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

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

  1. 必须熟练掌握十进制数与\(IEEE\ 754\)单精度浮点数\(float\)的相互转换

  2. \(float\)转十进制(真值)(2013、2014、2020、2023年考察)

例:IEEE 754的单精度浮点数 CO A0 00 00H 的值时多少。

answer

C0 A0 00 00H→1100 0000 1010 0000 0000 0000 0000 0000

数符=1→是个负数

尾数部分=.0100..._(隐含最高位1)→尾数真值=(1.01)

移码=10000001,若看作无符号数=129D

单精度浮点型偏移量=127D

阶码真值=移码-偏移量公式

浮点数真值公式

  • 十进制用\(float\)表示(2011、2022年考过)

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

answer

解:将\(y\) 用二进制小数表示为 \(y=-100100.101\),将\(y\) 右移,使得小数点前面只保留一位“\(1\)”,得\(y=-1.00100101*2^5\)

数符 \(= 1\),尾数部分 =公式(隐含最高位1)

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

组合得\(1\ 10000100\ 00100101000000000000000=C212\ 8000H\)

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

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

image-20230828220645290.png

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

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

【注意】:最大负数绝对值最小,最小负数绝对值最大,别混淆喽!其答案分别为\(8080\ 0000H\) 和 \(FF7F\ FFFF\).

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

关于\(double\) 历年还未考过,要注意一下!

  1. \(float\)的特殊表示

\(ieee\ 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位浮点数的取值范围是

v2-2aa6357d0b54287ab43ac3a4365a2794_b.jpg

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

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

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

链接:https://zhuanlan.zhihu.com/p/343049681\ 来源:知乎

C9594A819AA816E9685CE0E2C2A8328F.png

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

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

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

  • 当阶码全0,尾数全0时,表示真值\(\pm 0\)
  • 当阶码全1,尾数全0时,表示无穷大\(\pm \infty\)

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

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

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

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

1786124507563-5f011f66-09bc-439d-85b9-eef956e3f2f4.png

规格数的最小值是:

1786124507628-76ecdbd5-943b-4ff7-b084-533263fd43ae.png

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

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

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

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

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

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

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

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

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

值的类型 符号位(1位) 阶码(8位) 尾数(23位) 真值 符号位(1位) 阶码(11位) 尾数(52位) 真值
正零 \(0\) \(0000\ 0000\) 全 \(0\) \(+0\) \(0\) 全\(0\) 全\(0\) \(+0\)
负零 \(1\) \(0000\ 0000\) 全 \(0\) \(-0\) \(1\) 全\(0\) 全\(0\) \(-0\)
正无穷大 \(0\) \( 1111\ 1111 \)\( (255) \) 全\( 0 \) \( +\infty \) \( 0 \) \( 2047(全1) \) 全\( 0 \) \( +\infty \)
负无穷大 \(1\) \( 1111\ 1111 \)\( (255) \) 全\( 0 \) \( -\infty \) \( 1 \) \( 2047(全1) \) 全\( 0 \) \( -\infty \)
非规格化小数 \(0\) 或 \(1\) \(0000\ 0000\) 不全为 \(0\) \(\pm (0.\times \times\dots\times )_2\times 2^{-126}\) \(0\) 或 \(1\) 全\(0\) 不全为 \(0\) \(\pm (0.\times \times\dots\times )_2\times 2^{-1022}\)
非数 \(0\) 或 \(1\) \(1111\ 1111\) 不全为 \(0\) \(NAN\) \(0\) 或 \(1\) \(2047(全1)\) 不全为 \(0\) \(NAN\)
最大正数 \(0\) \(1111\ 1110\) 全\(1\) \(2^{127}\times(2-2^{-23})=2^{128}-2^{104}\) \(0\) \(111\ 1111\ 1110\) 全\(1\) \(2^{1023}\times(2-2^{-52})=2^{1024}-2^{971}\)
最小正数 \(0\) \(0000\ 0001\) 全 \(0\) \(1.0\times2^{-126}=2^{-126}\) \(0\) \(000\ 0000\ 0001\) 全 \(0\) \(1.0\times2^{-1022}=2^{-1022}\)
最大负数 \(1\) \(0000\ 0001\) 全 \(0\) \(-1.0\times2^{-126}=-2^{-126}\) \(1\) \(000\ 0000\ 0001\) 全 \(0\) \(-1.0\times2^{-1022}=-2^{-1022}\)
最小负数 \(1\) \(1111\ 1110\) 全\(1\) \((-1)\times2^{127}\times(2-2^{-23})=2^{104}-2^{128}\) \(1\) \(111\ 1111\ 1110\) 全\(1\) \((-1)\times2^{1023}\times(2-2^{-52})=2^{971}-2^{1024}\)

2.3.3 浮点数的加减运算

1721784982068-33fd0c69-b58e-4833-9888-5f2d0b1eeb0f.png

操作数的检查

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

image-20230829001318199.png

比较阶码大小完成对阶操作

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

image-20230829001423410.png

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

尾数加减

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

image-20230829004529515.png

结果规格化

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

image-20230829114630862.png

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

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

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

舍入处理

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

Info

  • “0” 舍 “1” 入法:类似于十进制中的“四舍五入法”,即在尾数右移时,被移去的最高数值位为0,则舍去;被移去的最高数值位为1,则在尾数的末尾加1,若破坏规格化结果,则还需要再次进行规格化处理。
  • 末位恒置“1”法:尾数右移时,不论丢掉的最高数值位是“1”还是“0”,都使右移后的尾数末尾恒置为“1”。(损失一位精度,但误差累积较小)
  • 截断法:直接截取所需位数,丢弃后面的所有位,这种舍入处理最简单。(会导致误差累积)

IEEE754 标准中的 4 种舍入模式

  1. 就近舍入(向偶数舍入):即十进制下的 “四舍五入”
  2. (1)例如有效数字超出规定数位的多余数字是 1001,它大于超出规定最低位的一半(即 0.5),故最低位进 1;
  3. (2)如果多余数字是 0111,它小于最低位的一半,则舍掉多余数字(截断尾数、截尾)即可;
  4. (3)对于多余数字是 1000、正好是最低位一半的特殊情况,最低位为 0 则舍掉多余位,最低位为 1 则进位 1,使得最低位仍为 0(偶数)。
  5. 朝 0 舍入(正数向下舍入、负数向上舍入):即朝数轴零点方向舍入,即直接截尾。
  6. 朝正无穷舍入(向上舍入):对正数而言,只要多余位不全为 0 则向最低有效位进 1;负数则直接截尾。
  7. 朝负无穷舍入(向下舍入):对负数而言,向最低有效位进 1;正数若多余位不全部为 0 则简单截尾。

判断溢出(2015年考察)

在尾数规格化和尾数舍入过程中,可能需要对结果的真实指数进行加 1 或减 1,因此需要检查指数是否越界。下面用 e 表示真实指数,用 E 表示 IEEE 754 中存储的指数域。

  1. IEEE 754 的指数范围:以 IEEE 754 单精度为例,

  2. 指数域 E = 1 到 254:表示规格化数;

  3. 规格化数的真实指数范围为:\(-126 \le e \le 127\)
  4. 指数域 E = 0:表示 0 或非规格化数;
  5. 指数域 E = 255:表示无穷大或 NaN。

双精度类似:\(-1022 \le e \le 1023\)

这里的 -126 和 -1022 是最小的正规格化指数,并不是所有非零浮点数的最小指数。

  1. 指数上溢:如果结果的真实指数超过最大正规格化指数。单精度:e > 127;双精度:e > 1023;则发生指数上溢。典型情况是:尾数舍入产生进位,使尾数右规,并使指数加 1。例如:
尾数舍入前:1.111...111
舍入后:    10.000...000

此时需要右规,指数加 1。

如果右规前指数域为:

1111 1110

加 1 后会变为:

1111 1111

而全 1 的指数域不再表示正常数,因此发生指数上溢。实现时不能让指数域发生模运算,从全 1 变为全 0。在通常的舍入到最近模式下,溢出结果通常表示为 +∞ 或 -∞; IEEE 754 中,具体结果还与舍入模式有关。

  1. 指数下溢与非规格化数,当规格化过程中得到:

  2. 单精度:e < -126;

  3. 双精度:e < -1022;

结果不能继续表示为规格化数,应转入非规格化数处理。这表示结果离开了正规格化数范围,但不等于立即变为 0。单精度非规格化数的表示形式为:\((-1)^s\times(0.f)_2\times2^{-126}\),其中最小正非零非规格化数为:\((0.00\cdots001)_2\times2^{-126}=2^{-149}\)

双精度最小正非零非规格化数为:\(2^{-1074}\)

因此:

  • e < -126(单精度)表示不能继续用规格化数表示,应进入非规格化处理;
  • 只有当结果经舍入后小于 2^-149(双精度为 2^-1074)时,才可能变为 +0 或 -0。

严格按照 IEEE 754,下溢异常通常要求结果同时满足:

1. 结果很小,即进入非规格化范围或接近 0;
2. 结果不精确,即发生了舍入。

因此,非规格化结果不一定是 0,也不一定一定触发下溢异常。

  1. 左规时的判断:左规时,尾数逐位左移,真实指数逐次减 1。

  2. 如果指数仍在正规格化范围内,则继续左规;

  3. 如果指数将小于 -126(单精度)或 -1022(双精度),则应停止按规格化数处理,转为非规格化数;
  4. 此时不能简单地因为指数域 E = 0 就直接判定结果为 0。只有舍入后小于最小正非零数时,结果才可能变为 0。

  5. 尾数溢出与浮点数上溢的关系:尾数在加法或舍入时产生进位,并不等于浮点数最终发生上溢。

尾数溢出通常可以通过右规进行纠正:

尾数右移一位,指数加 1

只有当右规后的真实指数超过最大正规格化指数时,才发生指数上溢。

因此:

  • 尾数溢出不一定导致浮点数上溢;
  • 大数的最终上溢主要由指数超过上限判断;
  • 小数的下溢则需要按照非规格化数和舍入规则单独判断。

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

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

解:\(x=0.5=0.100\cdots 0B=(1.00\cdots 0)_2\times2^{-1}\)

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

image-20230829123648765.png

image-20230829123728128.png

image-20230829135730704.pngimage-20230829231030235.png


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

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

2.4.1 整数类型强制转换

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

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

【例1】:求 \(y\) 的值

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

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

【例2】:求 \(si\) 的值 (2019年真题改编)

unsigned short usi = 62080;
short si = usi;
answer

解:\(usi\) 的机器数为\(1111\ 0010\ 1000\ 0000B\) ,改变解释方式为补码,将其转变为原码为\(1000\ 1101\ 1000\ 0000B\), 即 \(si\) 的值为 \(-3456\);

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

举例如下:3位机器数按\(000→111\)排列,若为补码编码,真值为分别为\(0、1、2、3、-4、-3、-2、-1;\)按无符号数解释真值为\(0、1、2、3、4、5、6、7\),显然对于同一码字100所表示的两个数\(-4、4,|-4|+|4|=2^3=8\),其余码字同理。

该结论可以利用位权进行简单证明,\(n\)位机器数解释为补码si和无符号数\(usi\),位权展开为\(|si|= |-x_{n-1}·2^{n-1}+x_{n-2} .2^{n-2}+..+x_0 ·2^0|\);\(|usi|= |x_{n-1}·2^{n-1}+x_{n-2} .2^{n-2}+..+x_0 ·2^0|\)

若 \(si\) 为正数,显然\(usi = si\);若 \(si\)为负数,有\(|si|= x_{n-1}·2^{n-1}-x_{n-2} .2^{n-2}+..-x_0 ·2^0\),所以

\(|usi|+|si|= 2·2^{n-1}=2^n\)。考试中大多考查 \(si\)为负数的情形。

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

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

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

【例3】:求 \(y\) 的值

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

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

【例4】:求 \(y\) 的值

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

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

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

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

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

解:\(x\) 的机器数为\(FFFAH\),由于\(x\)是无符号数,所以高位补0,得到 \(y\) 的机器数为 \(0000\ FFFAH\)

【例6】:求 \(y\) 的值

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

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

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


【例7】:求\(i\)的值

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

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


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

1715167947121-22fa136f-3000-485d-b66e-5a2a3f891396.png

类型 \(int\) \(float\) \(double\)
\(int\) (4B) 比较大的数无法精确表示,会丢失精度 完美转换
\(float\) (4B) + 小数部分向0截断,精度丢失;
+ 大数可能溢出
完美转换
\(double\)(8B) + 小数部分向0截断,精度丢失;
+ 大数可能溢出
大数可能会溢出; 高精度发生舍入
  1. \(int\)转换为\(float\)

Info

由于 \(float\) 表示范围比 \(int\) 大,因此不会溢出;但是 float 有 24 位二进制有效精度(含隐藏位)。规格化后若有效位超过 24 位,较低位需要按舍入规则处理,可能造成精度损失。

1786124096361-f7ca0493-8712-43ec-8b86-5b3ad0b543bf.png

  1. \(int\)或\(float\)转换为\(double\)

Info

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

  1. \(double\) 转换为 \(float\)和\(int\)

Info

由于  \(double\) 的尾数部分有53位,表示范围更大,有效位更多,因此大数转换时可能发生溢出,高精度数转换时,会发生舍入。

  1. \(float\) 或 \(double\) 转换为 \(int\)

Info

由于 \(int\) 的表示范围小于 \(float\) 和 \(double\) ,因此当\(float\) 或 \(double\)数值较大时转为\(int\)可能会发生溢出;

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

【例题】

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

①x == (int)(float)x    ② x == (int)(double)x   ③ d == (double)(float)d        ④f == (float)(double)f⑤f == -(-f)    ⑥ d * d >= 0     ⑦(f+d)-f==d     ⑧(d+f)-d==f

answer

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

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

  1. 假定变量 \(i、f、d\) 的类型分别为\(int、float、double\),已知 \(i=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

answer

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

  1. 1715169324126-7b8c19b4-f98e-47cc-a9e5-57ff03bfc053.png

2.4.3 数据的存储与排布

数据类型 C声明 无符号版本 (32位)字节数 (64位)字节数
字符型 char unsigned char 1 1
短整型 short unsigned short 2 2
整型 int unsigned 4 4
长整型 long unsigned long 4 8
32位整数 int32_t uint32_t 4 4
64位整数 int64_t uint64_t 8 8
字符指针 char * 4 8
浮点型 float 4 4
双精度浮点型 double 8 8

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

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

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

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

2.4.3.1大小端方式

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

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

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

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

大自然,小机器

当采用小端法时:

0001H 0002H 0003H 0004H
67H 45H 23H 01H

当采用大端法时:

0001H 0002H 0003H 0004H
01H 23H 45H 67H

例题:以下是一个由反汇编器生成的一行针对IA-32处理器的机器 级代码表示文本:

80483d2: 89 85 a0 fe ff ff mov %eax, 0xfffffea0(%ebp)

其中,80483d2是十六进制表示的指令地址, 89 85 a0 fe ff ff 是机器指令, mov %eax, 0xfffffea0(%ebp) 是对应的汇编指令 ,0xfffffea0是立即数 。请问:立即数 0xfffffea0 的值和所存放地址分别是多少? IA-32是大端还是小端方式?

answer

立即数0xfffffea0所存放的地址为0x80483d4;

立即数0xfffffea0的值为-352;

IA-32采用的是小端方式!

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不行:
  3. 模4余3的存放方式首先排除 这种存放方式会导致取用该short类型变量需要2个时钟周期。
  4. 模4余1貌似可行,但是如果该short类型变量后再跟随一个short变量呢?
  5. 对于int类型变量,其变量地址必须模4为0,这样才能保证能在一个存储周期内从存储器取得数据。
  6. 对于double类型变量。其本身就占用8个字节,从存储器中取用该变量至少需要两个存储周期。在变量地址满足模4余0的情况下,就可以在存储周期内获得数据。模8余0条件更为严苛,会浪费存储空间,我们不予采用。

32位机器

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

  1. 对于 char 类型的变量,其对齐没有任何限制,因为无论其存放在哪里,都可以在1个存储周期内取得数据。
  2. 对于 short 类型变量,其变量地址必须模2为0,与32位字长的情况相同。
  3. 对于 int 类型变量,其变量地址必须模4为0,以确保能在一个存储周期内从存储器取得数据,也与32位字长的情况相同。
  4. 对于 double 类型变量,其占用8个字节,因此变量地址必须模8为0,以保证在1个存储周期内获得数据。模8余0条件更为严苛,但不会浪费存储空间,因为 double 类型变量本身就占用8个字节,而不像 short 或 int 可能只占用2或4个字节。

64位机器

2.重点结构体变量的对齐规则
  1. 整个结构体变量的对齐方式与其中对齐方式最严格的成员相同。
  2. 结构体成员在满足其对齐方式的条件下,取最小可用位置作为成员在结构体中的偏移量。 (见S1的变量b起始位置是2而不是3)
  3. 结构体大小应当为对齐边界长度的整数倍(结构体的大小必须是其最大成员大小的倍数,以保证结构体在数组中也是对齐排放。)
  4. 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

202404281431605.png

【注意】: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

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

64位机器

64位机器

32位机器结构体嵌套

例题: 在按字节编址,采用 小端存储 方式的32位计算机中,按 边界对齐方式 为以下的C语言结构体变量a分配 存储空间。

struct node{
 short a;
 int b;
}

若a的首地址是2020 FE00H,a的成员变量b的机器数为1234 0000H,则其中34H所在的存储单元地址是 ()

  • A. 2020 FE03H
  • B. 2020 FE04H
  • C. 2020 FE05H
  • D. 2020 FE06H
answer

D

题图


2.5 运算方法和运算电路

image-20230829231935420.png

其他画法:

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

【补充】

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

image-20230829232825528.png

  1. 同或运算的妙用:n bit进行同或,若有奇数个 0,则同或结果为 0;若有偶数个 0,则同或结果为 1.
  2. 门电路的变形画法

image-20230829233035973.png

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

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

1714573480962-0a793d5f-a647-4e88-9363-ce2436e73a4d.png

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

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

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

image-20230830124116135.png

  1. 指针、地址等通常被说明为无符号整数,因而在进行指针或地址 运算时,需要进行无符号整数的加、减运算
  2. 无符号整数和带符号整数的加、减运算电路完全一样,这个运算电路称为 整数加减运算部件 , 基于 带标志加法器 实现
  3. 计算机中的加法器,因为只有n位,所以是一种模\(2^n\)运算系统!
  4. 数值数据和逻辑数据在形式上没有差别,计算机区分数值数据和逻辑数据的主要方法是:用不同的指令操作码来区分本指令处理的是哪种数据。(如果是逻辑运算(与或非),CPU就把它们当成逻辑数据,如果是算术运算(加减乘除),CPU就把他们当作数值数据)
  5. 由于计算机中有符号数和无符号数公用一个ALU,且它们对应的机器数(二进制位)进行 加减法时的规则完全相同,所以不管是无符号数还是有符号数,其运算后标志寄存器内都会产生这4个标志位。举个例子,无符号数运算产生的SF和OF标志的含义是:将运算数和运算结果对应的机器数(二进制序列)解释为有符号数时,运算结果的符号和溢出情况。
  6. 指令执行可能会引起ALU状态标志位的修改,很多计算机会将这些标志位暂存在一个状态寄存器中,为后续指令提供执行依据,如x86的EFLAGS寄存器,x86中的条件分支指令会根据标志位的不同进行不同的操作。但也有一些计算机中没有状态寄存器,如MIPS、RISC-V,其条件分支指令直接根据ALU当前状态标志位执行不同的分支。不论哪种结构,ALU都会产生这些标志,只是不同计算机利用这些标志的方法和时机不同而已。
  1. 算术逻辑单元\(ALU\)

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

  1. 多路选择器\(MUX\)

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

  1. 工作原理

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

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

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

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

1零标志位\(ZF\)(Zero Flag)

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

2溢出标志\(OF\)(Overflow Flag)

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

  • 硬件的计算方法:由上面的右图中的带标志加法器内部结构可以看到,判断溢出可以由最高位进位\(Cout(C_n)\)和次高位进位\(C_{n-1}\)求异或得到。这与我们之前提到的判断补码相加是否溢出的方法一致:判断最高位进位和次高位进位是否相同)。
  • 对于无符号数\(OF=1\)不能用作溢出判断的解释:对于两个无符号数而言,如果两个高位为0的数相加,得到的结果高位为1,在无符号数中这种情况是可以成立的,而对于有符号数则不成立,因为两个正数相加是不会得到负数的,所以有符号数可以判断溢出,而对于无符号数则不行。

3符号标志位\(SF\)(Signal Flag)

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

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

4进/借位标志\(CF\)(Carry Flag)

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

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

解释:

  1. 当无符号数相加时,如果最高位产生进位(即两个数相加的结果大于无符号数能表示的最大值),CF位被设置为1。如果没有发生进位,CF位设置为0。举例来说,假设我们用8位无符号数进行计算:例如,255 + 1(即二进制的 11111111 + 00000001),结果是 100000000(9位),但由于只能表示8位,结果为 00000000,因此此时进位标志CF为1;
  2. 当无符号数相减时,ALU 通常不会直接计算减法;而是利用加法和补码的原理来实现减法,也就是对减数取反+1,然后执行加法。如果最高位没有产生进位,CF位被设置为1。如果发生进位,CF位设置为0。例如,00000001 - 00000010(即十进制的1减2)可以转换为 00000001 + 11111110。结果是 11111111(即十进制的255,对于8位无符号二进制,这表示-1的补码表示,但在无符号中表示255,说明发生了借位),此时最高位进位是0,sub位是1,因此CF借位标志为1。
  • 对于有符号数的加减运算无意义!
  • 虽然无符号数加减法的溢出可以通过\(CF\)位来体现,但是无符号数不经常分析溢出,也没有像\(OF\)这样的专门溢出标志位。其原因主要是:对于 \(n\) 位无符号数,其算术运算实际上是一种以 \(2^n\) 为模的回绕算术。例如无符号数表示的地址,当计算得到的高地址溢出时会回绕到低地址,结果仍然在 \(2^n\) 域内。

【例题】:

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

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

answer

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

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

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

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

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

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

  • 若\(A=B\),则 \(A-B=0\) ,所以 \(ZF=1\)
  • 若\(A\ne B\),则 \(A-B\ne 0\) ,所以 \(ZF=0\)
  • 若\(A\<B\),则\(SF\oplus OF=1\)
  • 情况1:\(SF=1,OF=0\),这种情况下没有溢出,\(SF\)代表运算后真正的符号,\(SF=1\) 表示真实运算结果为负,所以 \(A\<B\);
  • 情况2:\(SF=0,OF=1\),发生溢出,若\(SF=0\) 则一定是负数减去正数(负数加负数)发生溢出导致结果为正,所以 \(A\<B\);
  • 若\(A>B\), 则\(SF\oplus OF=0\) 并且 \(ZF=0\)
  • 情况1:\(SF=0,OF=0,ZF=0\),在没有溢出的情况下,即\(OF=0\),此时\(SF=0\)代表实际结果非负,若\(ZF=0\),则说明结果为正,则\(A>B\);
  • 情况2:\(SF=1,OF=1,ZF=0\),在有溢出的情况下,即\(OF=1\) ,此时\(SF=1\)表示一定是正数减去负数(正正相加)发生溢出导致结果为负,则 \(A>B\)
  • 若\(A\ge B\),综合大于和等于情况下的标志位,有\(SF\oplus OF=0\) 或者 \(ZF=1\)
  • 若\(A\le B\),综合小于和等于情况下的标志位,有\(SF\oplus OF=1\) 或者 \(ZF=1\)

2.5.2  乘除法运算的基本原理

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

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

  1. 运算器的基本组成

image-20230830121707023.png

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

  1. 原码乘法运算\ image-20230830121959444.pngimage-20230830122149999.png
  2. 补码乘法运算\ image-20230830122732364.pngimage-20230830123039796.png
  3. 原/补码除法运算

image-20230830125326776.pngimage-20230830125420109.png


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

  1. 无符号数乘法电路: image-20230830151923830.png

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

  • 被乘数寄存器 \(X\) 用于存放被乘数;
  • 乘积寄存器 \(P\) 开始时置初始部分积 \(P_0=0\), 结束时存放的是\(64\)位乘积的高\(32\)位;
  • 乘数寄存器\(Y\)开始时置乘数,结束时存放的是\(64\)位乘积的低\(32\)位;
  • 进位触发器\(C\)保存加法器的进位信号;
  • 计数器\(Cn\)存放循环次数,初值是\(32\),每循环一次,\(Cn\)减1,当\(Cn=0\)时,乘法运算结束;
  • ALU是乘法核心部件,在控制逻辑控制下,对乘积寄存器 P和被乘数寄存器X的内容进行“加”运算,在“写使能”控制下运算结果被送回乘积寄存器P,进位位存放在C中。

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

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

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

image-20230830125753208.png

如图是实现 \(32\) 位补码一位乘法(\(Booth\)) 的逻辑结构图。

  • 乘数寄存器\(Y\) 后面增添一位附加位(辅助位\(Y_{-1}=0\));
  • 控制逻辑每次根据寄存器\(Y\) 的最后两位来执行加法、移位运算,并将结果写回寄存器中;

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

  • 计数器\(C_n\) 用来控制循环次数,即该算法会执行 \(n\) 次

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

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

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

  • \(n\) 位乘以 \(n\) 位,若用 \(2n\) 位保存乘积,则不会溢出;
  • \(n\) 位乘以 \(n\) 位,若用 \(2n\) 位保存中间结果,最后截取末尾 \(n\) 位作为最终乘积,可能会溢出
  • 手算判溢出:代入十进制计算乘法结果,判断该结果是否超出了 \(n\) 位有符号数所能表示的范围,如补码\([-2^{n-1},2^{n-1}-1]\),若超出,则溢出;

  • 机器判溢出:仅当高\(n+1\)位全1或全0时,不溢出。重点 (对于 \(n\) 位有符号整数乘法指令,其乘积机器数包含\(1\)位符号位,若乘积为正数,则当 \(2n\) 位乘积的高 \(n+1\) 位全为 0时,截取\(n\)位后真值不变,即结果无溢出;若乘积为负数,则当\(2n\) 位乘积的高 \(n+1\) 位全为 1时,截取\(n\)位后真值不变,即结果无溢出)

  1. 除法电路:

image-20230830150436181.png

  • \(ALU\) 执行除数寄存器 \(Y\) 与余数寄存器 \(R\)  的加减运算;
  • 控制逻辑根据运算结果来确定上商的数值,然后寄存器 \(R、Q\) 同步左移,空出的低位用来上商;
  • 计数器用来控制循环次数。

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