数制与编码

在计算机系统内部,所有信息都是用二进制进行编码的,这样做的原因有以下几点。

  1. 二进制只有两种状态,使用有两个稳定状态的物理器件就可以表示二进制数的每一位,制造成本比较低
  2. 二进制位 1 和 0 正好与逻辑值“真”和“假”相对应,为计算机实现逻辑运算和程序中的逻辑判断提供了便利条件。
  3. 二进制的编码和运算规则很简单,通过逻辑门电路能方便地实现算术运算。

Tip

在计算机内部,数值数据的表示方法有以下两大类:

  1. 直接用二进制数表示,分为有符号数和无符号数。

    有符号数又分为定点数表示和浮点数表示。无符号数用来表示无符号整数(如地址等信息)。

  2. 二进制编码的十进制数,一般采用 BCD 码表示,用来表示整数。

所以,计算机中的数值数据虽然都用二进制表示,但不全是二进制,也有用十进制表示的(在用户界面层或者编程语言中,程序员或用户可以使用十进制或其他进制来输入、显示或读取数据,尽管这些数据在计算机内部仍然是以二进制形式存储和运算)。例如在指令类型中,就分别有二进制加法指令和十进制加法指令(专门用于对 BCD 进行加法操作)。

进位计数

可以用后缀字母标识一个数的进位计数制

  • 用 B 表示二进制;
  • 用 O 表示八进制;
  • 用 D 表示十进制(通常直接省略);
  • 用 H 表示十六进制,有时也用前缀 0x 表示十六进制数;

进制转换

Tip

在计算机中,小数和整数不一样,整数可以连续表示,但小数是离散的,所以并不是每个十进制小数都可以准确地用二进制表示。但任意一个二进制小数都可以用十进制小数表示。

真值和机器数

种带 +- 符号的数称为真值,真值是机器数所代表的实际值。

在计算机中,通常将数的符号和数值部分一起编码,将数据的符号数字化,通常用“0”表示“正”,用“1”表示“负”。这种把符号“数字化”的数称为机器数。

BCD

定点数

原码、反码、补码

原码表示的优点:与真值的对应关系简单、直观,与真值的转换简单;用原码实现乘除运算比较简便。

原码表示的缺点:0 的表示不唯一,有 ±0 两个编码;原码加减运算比较复杂。

反码表示存在以下几个方面的不足:0 的表示不唯一,有 ±0 两个编码;表示范围比补码少一个最小负数。

反码在计算机中很少使用,通常用作数码变换的中间表示形式。

移码

移码就是在真值 X(用补码表示)上加上一个常数(偏置值),相当于 X 在数轴上向正方向偏移了若干单位,这就是“移码”一词的由来。

移码的定义:[x]= 2^n^+x(-2^n^≤x < 2^n^,其中机器字长为 n+1)

假设字长为 8 位,则正数 x = +10101 的移码表示为 2^7^+10101 = 1000 0000 + 10101 = 1001 0101;x = -10101 的移码表示为 2^7^ + (-10101) = 0110 1011。

移码具有以下特点:

  1. 移码中零的表示唯一
  2. 一个真值的移码和补码仅差一个符号位
  3. 移码全 0 时,对应真值的最小值-2^n^;移码全 1 时,对应真值的最大值 2^n^-1。
  4. 移码保持了数据原有的大小顺序,移码大真值就大,移码小真值就小。

机器数的定点表示

根据小数点的位置是否固定,在计算机中有两种数据格式:定点表示和浮点表示。

在现代计算机中,通常用补码整数表示整数,用原码小数表示浮点数的尾数部分,用移码表示浮点数的阶码部分。

因此,在定点数的编码和运算中不用考虑对应的定点数是小数还是整数,而只需关心它们的符号位和数值位即可。

定点数的编码表示法主要有 4 种:原码、补码、反码和移码。

无符号整数

当一个编码的全部二进制位均为数值位而没有符号位时,该编码表示就是无符号整数,简称无符号数

此时,默认数的符号为正。因为无符号整数省略了一位符号位,所以在字长相同的情况下,它能表示的最大数比有符号整数能表示的大。一般在全部是正数运算且不出现负值结果的场合下,使用无符号整数表示,例如在计算机中,通常使用无符号数表示主存地址。

无符号整数的“溢出”:对于无符号定点整数来说,若寄存器位数不够,则计算机运算过程中一般保留低 n 位,舍弃高位。这样,会产生以下两种结果:

  1. 保留的低 n 位数不能正确表示运算结果。在这种情况下,意味着运算的结果超出了计算机所能表达的范围,有效数值进到了第 n+1 位,称此时发生了“溢出”现象。
  2. 保留的低 n 位数能正确表达计算结果,即高位的舍去并不影响其运算结果。

有符号整数

将符号数值化,并将符号位放在有效数字的前面,就组成了有符号整数。

补码表示有符号整数有其明显的优势:

  1. 与原码和反码相比,0 的补码表示唯一。
  2. 与原码和移码相比,补码运算规则比较简单,且符号位可以和数值位一起参加运算。
  3. 与原码和反码相比,补码比原码和反码多表示一个最小负数。

计算机中的有符号整数都用补码表示。

C 语言中的整数类型

C 语言中的整型数据就是定点整数,根据位数的不同,可分为

  • 字符型(char,8 位)
  • 短整型(shortshort int,16 位)
  • 整型(int,32 位)
  • 长整型(longlong int,在 32 位机器中为 32 位,在 64 位机器中为 64 位)。

char 是整型数据中比较特殊的一种,其他如 short int / long 等不指定 signed / unsigned 时都默认是有符号整数,但 char 默认是无符号整数

无符号整数(unsigned short/int/long)的全部二进制位均为数值位,没有符号位,相当于数的绝对值

signed / unsigned 整型数据都是按补码形式存储的

signed 型的最高位代表符号位,而在 unsigned 型中表示数值位,因此这两者所表示的数据范围也有所不同。

C 语言中的类型转换

有符号数和无符号数的转换

// short 型强制转换为 unsigned short
short x = -4321;
unsigned short y = (unsigned short)x; // 61225

short x 在存储中以补码形式存储,存储内容为1,110 1111 0001 1111。当转换成 unsigned short 时,存储的二进制数据不变,因此 y 的二进制也是 1,110 1111 0001 1111,转换成十进制就是 61225。

因此,有符号数转换为等长的无符号数时,符号位解释为数值的一部分,负数转换为无符号数时数值将发生变化。

Tip

强制类型转换的结果是保持位值不变,仅改变了解释这些位的方式。

// unsigned short 型转换到 short 型
unsigned short x = 65535; // 1111 1111 1111 1111
short y = (short)x; // -1

同理,无符号数转换为有符号数时最高位解释为符号位,也可能发生数值的变化。

不同字长整数之间的转换

大字长变量向小字长变量强制类型转换时,系统把多余的高位部分直接截断,低位部分直接赋值

int x = 165537; // int 型占用 4B x = 0x000286a1
short y = (short)x;// short 型占用 2B y = -31071 = 0x86a1
 
int u = -34991; // x = 0xffff7751
short v = (short)u; // v = 30545 = 0x7751

小字长到大字长的转换时,不仅要使相应的位值相等,还要对高位部分进行扩展

  • 若原数字是无符号整数,则进行零扩展,扩展后的高位部分用 0 填充

  • 若原数字不是无符号整数,则进行符号扩展,扩展后的高位部分用原数字符号位填充

    例如,补码的扩充只需使用符号位补足即可,也就是说正数补码的扩充只要补 0,负数补码的扩充只需补 1。

short x = -4321;// x = 0xef1f
int y = x;// y = -4321 = 0xffffef1f
 
unsigned short u = (unsigned short)x; // u = 0xef1f
unsigned int v = u;// v = 61215 = 0x0000ef1f

Tip

char 型为 8 位无符号整数,其在转换为 int 型时高位补 0 即可。

运算方法和运算电路

基本运算部件

加法器

一位全加器

串行加法器

并行加法器

并行进位加法器

带标志位的加法器

  • 零标志 ZF:ZF = 1 表示结果 F 为 0。对于无符号数和有符号数的运算,ZF 都有意义。

  • 溢出标志 OF判断有符号数运算是否溢出,它是符号位进位与最高数位进位的异或结果。对于无符号数运算,OF 没有意义,通俗地说就是无法根据 OF 判断无符号数运算是否溢出。例如,无符号数加法 010+011 = 101,此时 0F = 1,但结果未溢出。

  • 符号标志 SF表示结果的符号,即 F 的最高位。对于无符号数运算,SF 没有意义。

  • 进/借位标志 CF:表示无符号数运算时的进位/借位,判断无符号数是否发生溢出。加法时,CF = 1 表示结果溢出,因此 CF 等于进位输出 Cout。减法时,CF = 1 表示有借位,即不够减,故 CF 等于进位输出 Cout取反。例如,无符号数加法 110+011 最高位产生进位,无符号数减法 000-111 最高位产生借位,结果均发生溢出(即 CF = 1)。

    对于有符号数运算,CF 没有意义,也就是说根据 CF 无法判断有符号数运算是否溢出。但这不代表有符号数运算时 CF 没有值,因为 ALU 运算时不会管运算对象是有符号数还是无符号数。

Tip

假设给定两个数 x 和 y 和对应的 n 位补码 x 和 y,执行 x op y

  • 计算 OF:判断 x op y 的值是否在 n 位补码所能表示的范围,如果不在则发生溢出,OF = 1。
  • 计算 CF:将 x 和 y 转成对应的补码 x 和 y(如果给出了补码则跳过),然后直接将 x 和 y 当成无符号整数进行比较或计算。x - y 时,x<y 则说明 CF = 1。x + y 时,x+y 的值溢出则 CF = 1。

ALU

ALU 是一种功能较强的组合逻辑电路,它能进行多种算术运算逻辑运算。ALU 的核心是带标志加法器

逻辑运算

移位运算

当计算机中没有乘/除法运算电路时,可以通过加法和移位相结合的方法来实现乘/除法运算。

对于任意二进制整数,

  • 左移一位,若不产生溢出,相当于乘以 2(与十进制数的左移一位相当于乘以 10 类似);
  • 右移一位,若不考虑因移出而舍去的末位尾数,相当于除以 2

算术移位

算术移位需要考虑符号位的问题,即将操作数视为有符号整数

计算机中的有符号整数都是用补码表示的,因此对于有符号整数的移位操作应采用补码算术移位方式。

补码算术移位的规则:

  • 左移时,高位移出,低位补 0,若移出的高位不同于移位后的符号位,即左移前后的符号位不同,则发生溢出
  • 右移时,低位移出,高位补符号位,若低位的 1 移出,则影响精度

逻辑移位

无符号数逻辑左移时,若最高位移出的是 1,则发生溢出。

溢出判断

计算机发生溢出的的根本原因计算机的字长有限,不能表示超过一定范围的数据

仅当两个符号相同的数相加两个符号相异的数相减才可能产生溢出,如两个正数相加,而结果的符号位却为 1;一个负数减去一个正数,结果的符号位却为 0(结果为正)。

采用一位符号位

采用一位符号位根据数值位进位情况

采用双符号位

采用双符号位时,第一符号位表示最终结果的符号第二符号位表示运算结果是否溢出

  • 若第二位和第一位符号相同,则未溢出。
  • 若第二位和第一位符号不同,则溢出。
    • 若发生正溢出,则双符号位为 01
    • 若发生负溢出,则双符号位为 10

Tip

模 4 补码具有模 2 补码的全部优点且更易检查加减运算中的溢出问题

Note

变形补码,即用两个二进制位来表示数字的符号位,其余与补码相同。

存储模 4 补码仅需一个符号位,因为任何一个正确的数值,模 4 补码的两个符号位总是相同的。只在把两个模 4 补码的数送往 ALU 完成加减运算时,才把每个数的符号位的值同时送到 ALU 的双符号位中,即只在 ALU 中采用双符号位

定点数的加减运算

原码的加减运算

在原码加减运算中,将符号位和数值位分开处理,具体的规则如下:

  • 加法规则:遵循“同号求和,异号求差”的原则,先判断两个操作数的符号位。

    具体来说,

    • 符号位相同,则数值位相加,结果符号位不变,若最高数值位相加产生进位,则发生溢出;
    • 符号位不同,则做减法,绝对值大的数减去绝对值小的数,结果符号位与绝对值大的数相同。
  • 减法规则:先将减数的符号取反,然后将被减数与符号取反后的减数按原码加法进行运算。

Tip

原码的加减运算规则比较复杂,因此 计算机采用的大多是补码加减运算

补码的加减运算

补码运算的特点:

  1. 按二进制运算规则运算,逢二进一。
  2. 若做加法,两个数的补码直接相加;若做减法,则将被减数与减数的负数补码相加。
  3. 符号位与数值位一起参与运算,加、减运算结果的符号位也在运算中直接得出。
  4. 最终运算结果的高位丢弃,保留 n+1 位,运算结果亦为补码

补码加减运算器

运算器本身 无法识别所处理的二进制串是有符号数还是无符号数,但我们可以 通过标志信息来区分有符号整数运算结果和无符号整数运算结果

无符号数大小的比较

对于无符号数的运算,零标志 ZF、进/借位标志 CF 才有意义。

假设有两个无符号数 A 和 B,下面以执行 A-B 为例来说明 ZF、CF 标志的几种可能情况:

  1. 若 A = B,如 A - B = 011 - 011 = 000,此时结果为零 ZF = 1,无借位 CF = 0。
  2. 若 A > B,如 A - B = 010 - 001 = 001,此时结果非零 ZF = 0,无借位 CF = 0。
  3. 若 A < B,如 A - B = 000 - 001 = (1)000 - 001 = 111,此时 ZF = 0,有借位 CF = 1。

可以得出结论:

  1. 当 ZF = 1 时,说明 A = B。
  2. 当 ZF = 0 且 CF = 0 时,说明 A > B。
  3. 当 CF = 1 时,说明 A < B。

有符号数大小的比较

对于有符号数的运算,零标志 ZF、溢出标志 OF、符号标志 SF 才有意义。

假设两个有符号数 A 和 B,用补码表示,以执行 [A] - [B]为例来说明 ZF、OF、SF 标志的几种可能情况。

  1. 若 A = B,如 [A] - [B] = 011 - 011 = [A] + [-B] = 011 + 101 = (1)000,此时结果为零 ZF = 1,最高位进位与次高位进位的异或结果 OF = 0,结果的最高位 SF = 0。
  2. 若 A > B,如 [A] - [B] = 010 - 001 = 010 + 111 = (1)001,此时 ZF = 0,OF = 0,SF = 0;又如 [A] - [B] = 011 - 101 = 011 + 011 = 110,此时 ZF = 0,OF = 1,SF = 1。
  3. 若 A < B,如 [A] - [B] = 000 - 001 = 000 + 111 = 111,此时 ZF = 0,OF = 0,SF = 1。又如 [A] - [B] = 101 - 011 = 101 + 101 = (1)010,此时 ZF = 0,OF = 1,SF = 0。

可以得出结论:

  1. 当 ZF = 1 时,说明 A = B。
  2. 当 ZF = 0 时,且
    • 未发生溢出时,即 OF = 0 时,
      1. 若 SF = 0,则表示结果非负,说明 A > B。
      2. 若 SF = 1,则表示结果为负,说明 A < B;
    • 发生溢出时,即 OF = 1 时,
      1. 若 SF = 1,则必然是 正数减去负数 发生 溢出导致结果为负,因此,当 OF = SF(或 OF ⊕ SF = O)且 ZF = O 时,说明 A > B。
      2. 若 SF = 0,则必然是 负数减去正数 发生 溢出导致结果为正,因此,当 OF ≠ SF(或 OF ⊕ SF = 1)且 ZF = 0 时,说明 A < B。

定点数的乘除运算

手算乘法运算

原码乘法运算

原码乘法的特点是符号位与数值位是分开求的

原码乘法运算分为两步:

  1. 乘积的符号位由**两个乘数的符号位“异或”**得到。
  2. 乘积的数值位是两个乘数的绝对值之积。两个定点数的数值部分之积可视为两个无符号数的乘积。

Tip

在原码一位乘法中,符号位不参与运算,符号位单独处理。

补码乘法运算

原码除法运算

恢复余数法

加减交替法(不恢复余数法)

补码除法运算

浮点数

定点数可表示的数字范围有限,但我们不能无限制地增加数据的长度。

格式

浮点数表示法是指以适当的形式将比例因子表示在数据中,让小数点的位置根据需要而浮动。这样,在位数有限的情况下,既扩大了数的表示范围,又保持了数的有效精度。

  • S:取值 0 或 1,用来决定浮点数的符号;
  • M尾数,二进制定点小数,一般用定点原码小数表示
  • E阶码 或指数,二进制定点整数,用移码表示
  • R基数(隐含),可以约定为 2、4、16 等。

可见 浮点数由符号、尾数和阶码三部分组成

其中,

  • 第 0 位为符号 S;
  • 第 1 ~ 7 位为移码表示的阶码 E(偏置值为 64);
  • 第 8 ~ 31 位为 24 位二进制原码小数表示的尾数 M;
  • 基数 R 为 2。

Note

  1. 阶码的值反映浮点数的小数点的实际位置
  2. 阶码的位数反映浮点数的表示范围
  3. 尾数的位数反映浮点数的精度在浮点数总位数不变的情况下,阶码位数越多,尾数位数越少;即表示的数的范围越大,精度越差(数变稀疏)

Note

基数是浮点数的进制,决定了阶码变化的权重。基数越大,阶码每变化一位,尾数小数点需要移动的位数越多,表示的数的绝对值就越大,范围就越大

但是,在浮点数的总位数不变的情况下,能表示的不同状态个数是一定的。若范围增大,则意味着浮点数的离散程度增大,相邻两个浮点数之间的间隔就越大,精度就越低

例如,假设符号为 S、尾数为 M、阶码为 E,则基数为 2 时的浮点数表示形式为 ,基数为 4 时的浮点数表示形式为 ,显然基数为 4 时的表示范围大,但数据的离散程度也增大,精度降低。

用移码表示浮点数的阶码的好处

  1. 浮点数进行加减运算时,要比较阶码的大小,移码比较大小更方便
  2. 检验移码的特殊值(0 和 max)时比较容易

表示范围

原码是关于原点对称的,故浮点数的范围也是关于原点对称的。

运算结果大于最大正数时称为正上溢,小于绝对值最大负数时称为负上溢,正上溢和负上溢统称上溢。数据一旦产生上溢,计算机必须中断运算操作,进行溢出处理

当运算结果在 0 至最小正数之间时称为正下溢,在 0 至绝对值最小负数之间时称为负下溢,正下溢和负下溢统称下溢。数据下溢时,浮点数值趋于零,计算机将其当作机器零处理

规格化

为了在浮点数运算过程中尽可能多地保留有效数字的位数使有效数字尽量占满尾数数位,必须在运算过程中对浮点数进行规格化操作。

规格化操作,是指通过调整一个非规格化浮点数的尾数和阶码的大小,使非零浮点数在尾数的最高数位上保证是一个有效值

Tip

基数不同,浮点数的规格化形式也不同

  • 当基数为 2 时,原码规格化数的尾数最高位一定是 1
  • 当基数为 4 时,原码规格化数的尾数最高两位不全为 0

Tip

规格化浮点数的尾数小数点后的第一位一定是个非零数。

因此,对于原码编码的尾数来说,只要看尾数的第一位是否为 1 就行;对于补码表示的尾数,只要看符号位和尾数最高位是否相反。

需要注意的是,IEEE754 标准的浮点数尾数是用原码编码的。

IEEE 754 标准

格式

基数隐含为 2;

对于规格化的二进制浮点数,尾数的最高位总是 1,为了能使尾数多表示一位有效位,将这个 1 隐藏,称为隐藏位。因此 23 位尾数实际表示了 24 位有效数字。

IEEE 754 规定隐藏位 1 的位置在小数点之前,例如,(12)10=(1100)2,将它规格化后结果为 1.1×2^3^,其中整数部分的“1”将不存储在 23 位尾数内。

Tip

单精度与双精度浮点数都采用隐藏尾数最高位的方法,因而使浮点数的精度更高。

在 IEEE754 标准中,指数用移码表示,但偏置值并不是通常 n 位移码所用的 2^n-1^,而是 2^n-1^-1。因此,单精度和双精度浮点数的偏置值分别为 127 和 1023

在存储浮点数阶码之前,偏置值要先加到阶码真值上

表示范围

Note

对于位数相同的定点数和浮点数,可表示的浮点数个数和定点数个数应该一样多(有时可能因为一个值有两个或多个编码对应,编码个数会有少量差异)。

因为可表示的数据个数取决于编码所采用的位数。编码位数一定,编码出来的数据个数就是一定的。n 位编码只能表示 2^n^个数,所以对于相同位数的定点数和浮点数来说,可表示的数据个数应该一样多。

阶码全 1 或全 0 的意义

引入无穷大数的目的是,在计算过程出现异常的情况下使得程序能继续进行下去。

规规格化数的特点是阶码为全 0,尾数高位有一个或几个连续的 0,但不全为 0。因此,非规格化数的隐藏位为 0,且单精度和双精度浮点数的指数分别为-126 或-1022。非规格化数可以用于处理阶码下溢

定点和浮点表示的区别

  1. 数值的表示范围

    若定点数和浮点数的字长相同,则浮点表示法所能表示的数值范围远大于定点表示法。(浮点表示法的数值范围更大

  2. 精度

    对于字长相同 的定点数和浮点数来说,浮点数虽然扩大了数的表示范围,但精度降低了。(浮点数精度低

  3. 数的运算

    浮点数包括阶码和尾数两部分,运算时不仅要做尾数的运算,还要做阶码的运算,而且运算结果要求规格化,所以浮点运算比定点运算复杂。(浮点运算更复杂

  4. 溢出问题

    在定点运算中,当运算结果超出数的表示范围时,发生溢出;在浮点运算中,运算结果超出尾数表示范围却不一定溢出,只有规格化后阶码超出所能表示的范围时,才发生溢出

浮点数运算步骤

Tip

现代计算机中的浮点数采用 IEEE754 标准,所以在进行两个浮点数的加减运算时,必须考虑原码的加减运算,因为 IEEE754 标准的浮点数尾数都采用原码表示。

原码的加减运算可以有以下两种实现方式:

  1. 转换为补码后,用补码加减法实现,结果再转换为原码。
  2. 直接用原码进行加减运算,符号位和数值位分开处理。

浮点数运算的特点是阶码运算和尾数运算分开进行

对阶

对阶的目的是使两个操作数的小数点位置对齐,即使得两个数的阶码相等

为此,先求阶码差,然后以小阶码向大阶码看齐的原则,将阶码小的尾数右移一位(基数为 2),阶码加 1,直到两个数的阶码相等为止。

尾数右移时,若舍弃有效位会产生误差,影响精度。为了保证运算的精度,尾数右移时,低位移出的位不要丢掉,应保留并参加尾数部分的运算

Tip

若采用大阶码向小阶码看齐的原则,则尾数需要左移,最高有效位被移出,导致结果出错。

尾数加减

将对阶后的尾数按定点原码小数的加(减)运算规则进行运算

因为 IEEE754 浮点数尾数中有一个隐藏位,因此在进行尾数加减时,必须将隐藏位还原到尾数部分。

运算后的尾数不一定是规格化的,因此,浮点数的加减运算需要进一步进行规格化处理。

尾数规格化

IEEE754 规格化尾数的形式为 。尾数相加减后会得到各种可能结果,例如:

  1. 右规:当结果为 时,需要进行右规。尾数右移一位,阶码加 1。尾数右移时,最高位 1 被移到小数点前一位作为隐藏位,最后一位移出时,要考虑舍入。
  2. 左规:当结果为 时,需要进行左规。尾数每左移一位,阶码减 1。可能需要左规多次,直到将第一位 1 移到小数点左边。

舍入

Tip

舍入是浮点数的概念,定点数没有舍入的概念。

舍入不一定产生误差,如向下舍入 11.00 到 11.0 时是没有误差的。

对阶尾数右规时,可能会对尾数进行右移,为保证运算精度,一般将移出的部分低位保留下来,参加中间过程的运算,最后再将运算结果进行舍入,还原表示成 IEEE754 格式。

IEEE754 提供了以下 4 种可选的舍入模式:

  1. 就近舍入

    舍入为最近的可表示数。

    • 当运算结果是两个可表示数的非中间值时,实际上是“0 舍 1 入”方式(类似于十进制的“四舍五入”法);
    • 当运算结果正好在两个可表示数的中间时,则选择结果为偶数。

    例如,计算 (假定科学记数法的精度保留两位小数)

    • 若只采用 2 位保留位,则结果是 ,这个结果在两个可表示数 的中间,采用就近舍入方式到偶数,则结果应该是
    • 若采用 3 位保留位,则结果是 ,这个结果就不在 的中间,而更接近于 ,采用就近舍入方式,则结果应该是
  2. 正向舍入

    朝数轴 方向舍入,即取右边最近的可表示数。

  3. 负向舍入

    朝数轴 方向舍入,即取左边最近的可表示数。

  4. 截断法

    直接截取所需位数,丢弃后面的所有位,这种舍入处理最简单。对正数或负数来说,都是取更接近原点的那个可表示数,是一种趋向原点的舍入。

溢出判断

判断规格化后的阶码是否超出所能表示的范围

若一个正指数超过了最大允许值(127 或 1023),则发生指数上溢,产生异常。若一个负指数超过了最小允许值(-149 或-1074),则发生指数下溢,通常把结果按机器零处理

  1. 右规和尾数舍入

    数值很大的尾数舍入时,可能因为末位加 1 而发生尾数溢出,此时需要通过右规来调整尾数和阶码。

    右规时阶码加 1,导致阶码增大,因此需要判断是否发生了指数上溢

  2. 左规

    左规时阶码减 1,导致阶码减小,因此需要判断是否发生了指数下溢

    其判断规则与指数上溢类似,左规一次,阶码减 1,然后判断阶码是否为全 0 来确定指数是否下溢。

由此可见,浮点数的溢出并不是以尾数溢出来判断的尾数溢出可以通过右规操作得到纠正运算结果是否溢出主要看结果的指数是否发生了上溢,因此是由指数上溢来判断的

Note

某些题目中可能会指定尾数或阶码采用补码表示。

通常可以采用双符号位,当尾数求和结果溢出(如尾数为 10.xxx 或 01.xxx)时,需右规一次;当结果出现 00.0xxx 或 11.1xxx 时,需要左规,直到尾数变为 00.1xxx 或 11.0×××。

C 语言中的浮点数类型

C 语言中的 float 型和 double 型分别对应于 IEEE754 单精度浮点数和双精度浮点数。long double 型对应于扩展双精度浮点数,但 long double 型的长度和格式随编译器和处理器类型的不同而有所不同。

在 C 程序中,等式的赋值和判断会导致强制类型转换,以 char→int→long→doublefloat→double 最为常见,从前到后范围和精度都从小到大,转换过程没有损失

不同类型数的混合运算时,遵循的原则是“类型提升”,即较低类型转换为较高类型。例如:

  • long 型与 int 型一起运算时,需先将 int 型转换为 long 型,然后进行运算,结果为 long 型。
  • float 型和 double 型一起运算,虽然两者同为浮点型,但精度不同,则仍需先将 float 型转换为 double 型后再进行运算,结果亦为 double 型。

所有这些转换都是系统自动进行的,这种转换称为隐式类型转换

Tip

  1. int 型转换为 float 型时,虽然不会发生溢出,但 float 型尾数连隐藏位共 24 位,当 int 型数的第 24 ~ 31 位非 0 时,无法精确转换成 24 位浮点数的尾数,需舍入处理,影响精度。
  2. int 型或 float 型转换为 double 型时,因 double 型的有效位数更多,因此能保留精确值。
  3. double 型转换为 float 型时,因 float 型的表示范围更小,因此大数转换时可能会发生溢出。此外,由于尾数有效位数变少,因此高精度数转换时会发生舍入。
  4. float 型或 double 型转换为 int 型时,因 int 型没有小数部分,因此数据会向 0 方向截断(仅保留整数部分),发生舍入。另外,因 int 型的表示范围更小,因此大数转换时可能会溢出。

数据的存储和排列

大小端模式

在存储数据时,数据从低位到高位可以按从左到右排列,也可以按从右到左排列。因此,无法用最左或最右来表征数据的最高位或最低位,通常用最低有效字节(LSB)最高有效字节(MSB) 来分别表示数据的低位和高位。

Tip

大端:低位放大地址

小端:低位放小地址

边界对齐

现代计算机都是按字节编址的,假设字长为 32 位,数据按边界对齐方式存放要求其存储地址是自身大小的整数倍,半字地址一定是 2 的整数倍,字地址一定是 4 的整数倍,这样无论所取的数据是字节、半字还是字,均可一次访存取出

当所存数据不满足上述要求时,可通过填充空白字节使其符合要求。这样做虽然会浪费一些存储空间,但可以提高存取数据的速度。

当数据不按边界对齐方式存储时,半字长或字长的数据可能在两个存储字中,此时需要两次访存,并对高低字节的位置进行调整后才能得到所需数据,从而影响了系统的效率。

结构体的小端、边界对齐存储

在 C 语言的 struct 类型中,“边界对齐”有两个重要要求:

  1. 每个成员按其类型的大小对齐,char 型的对齐值为 1,short 型的对齐值为 2,int 型的对齐值为 4,单位为字节;
  2. struct 的长度必须是成员中最大对齐值的整数倍(不够就补空字节)。

这样就能保证 struct 数组的每项都满足边界对齐的条件。

之所以出现上面的结果,是因为编译器要使结构体成员在空间上对齐必须满足

  1. 每个成员存储的“起始地址%该成员的长度 = 0”,而结构体中的成员都是按定义的先后顺序排放的
  2. 结构体的长度也必须是最大成员长度的整数倍,即结构体也要对齐排放。

边界对齐方式相对边界不对齐方式是一种空间换时间的思想。精简指令系统计算机 RISC 通常采用边界对齐方式,因为边界对齐方式取指令时间相同,因此能适应指令流水。