这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

数据表示和计算

本章在选择题中考察,需要熟练掌握补码表示以及浮点数的IEEE标准。
# 数据的表示和运算

## 数制和编码

- 进位计数制及其数据之间的相互转换
- 定点数的编码表示

## 运算方法和运算电路

- 基本运算部件
- 加/减运算
- 乘/除运算

## 整数的表示和运算

- 无符号整数
- 带符号整数

## 浮点数的表示和运算

- IEEE 754标准
- 浮点数的加/减运算

1 - 整数的表示

需了解原码、补码的计算方法,为后续内容的基础。

BCD码

BCD(Binary-Coded Decimal)码是一种二进制编码方法,用于表示十进制数字。每个十进制数字(0-9)都使用四位二进制数字表示。

BCD码的基本思路是单独表示每个十进制数字的二进制值,而不是像传统的二进制数系统那样对整个数字进行编码。例如,在传统的二进制编码中,数字"19"会表示为10011,但在BCD中,它被表示为两个独立的数字:“0001”(对应于1)和“1001”(对应于9),因此整个表示为“0001 1001”。

0 - 0000    1 - 0001    2 - 0010
3 - 0011    4 - 0100    5 - 0101
6 - 0110    7 - 0111    8 - 1000
9 - 1001

BCD编码在某些应用中是很有用的,特别是在需要与十进制界面进行交互的地方,如数字显示或某些早期的计算机系统。尽管它不如纯二进制编码效率高,但它简化了与十进制数据的转换过程。

整数的表示

原码

原码(Sign-Magnitude)是一种简单的方式来表示二进制中的有符号整数。在这种表示法中,数字的最高位(最左边的位)用于表示符号,其余的位表示数字的大小。通常,符号位为0表示正数,而1表示负数。

表示方法

原码的表示方法:

  1. 正数的原码:最高位为0,其余位表示这个数的绝对值的二进制形式。
  2. 负数的原码:最高位为1,其余位表示这个数的绝对值的二进制形式。
  3. 零的原码:符号位可以是0(表示+0)或1(表示-0),但在实际应用中,通常只使用一个零,即符号位为0。

举例(以8位二进制数为例):

  • +5 的原码是:00000101
  • -5 的原码是:10000101

原码的缺点

  • 存在正零和负零的表示。
  • 在进行算术运算时,正数和负数需要不同的处理,这使得硬件设计变得复杂。

由于上述的缺点,原码并不是计算机中最常用的表示法。在现代计算机中,补码是更常用的方式来表示有符号整数,因为它简化了算术运算的处理,并且没有+0和-0的冗余表示。

补码

补码(Two’s complement)是计算机科学中用于表示整数的一种方法,特别是在现代计算机的算术和逻辑操作中。使用补码可以使得加法、减法和负数的表示变得简单和统一。

表示方法

原码的表示方法:

  1. 正数的原码:最高位为0,其余位表示这个数的绝对值的二进制形式。
  2. 负数的原码:最高位为1,其余位表示这个数的绝对值的二进制形式。
  3. 零的原码:符号位可以是0(表示+0)或1(表示-0),但在实际应用中,通常只使用一个零,即符号位为0。

以8位二进制数为例:

  1. 对于正数,例如 +5,其二进制表示为 00000101,补码也是 00000101。
  2. 对于负数,例如 -5,首先写出5的二进制表示:00000101。
    • 按位取反得到:11111010
    • 加1得到:11111011
    • 所以,-5的补码是 11111011。

补码的计算方式

以8位补码为例讲解一下补码是如何从二进制位与实际数字结合的:

第7位第6位第5位第4位第3位第2位第1位第0位
$-2^7 = -128$$2^6 = 64$$2^5 = 32$$2^4 = 16$$2^3 = 8$$2^2 = 4$$2^1 = 2$$2^0 = 1$

例子:

  • 10000001对应的值为 $-128 + 1 = -127$
  • 10001001对应的值为 $-128 + 8 + 1 = -119$
  • 01000001对应的值为 $64 + 1 = 65$

为什么要使用补码

补码的设计使得负数的加法变得简单。当使用补码表示时,可以直接将两个数相加,即使其中一个数是负数,而不需要进行任何特殊处理。这简化了计算机硬件的设计。

2 - 浮点数表示

需掌握32位和64位浮点数的IEEE表示,可能在选择题中考察。

实数的二进制表示

浮点数在计算机中的存储遵循IEEE标准,但在这里为了方便理解IEEE标准,这里首先阐明一般实数在计算机中是如何存储的。

在这里实数分为两个部分存储:整数部分 和 小数部分,两个部分在逻辑上用 · 隔开。

比如对于以下浮点数的二进制表示:

$$d_m d_{m-1} \cdots d_1 d_0 \cdot d_{-1} d_{-2} \cdots d_{-n}$$

其中每一位对应的数值如下表所示:

$d_m$$d_{m-1}$$\cdots$$d_1$$d_0$$d_{-1}$$d_{-2}$$\cdots$$d_{-n}$
$2^m$$2^{m-1}$$\cdots$$2$$1$$1/2$$1/4$$\cdots$$1/2^{n}$

上述二进制表示对应的数值为

$$b = \sum_{i=-n}^{m} {2^{i} \times b_{i}}$$

其中$b_i = 0$ 或 $b_i = 1$,表示第i为是0还是1。

举例说明如何使用上述表示法计算实数:

  • $101.11_{2}$ 对应的数值为 $1 \times 2^2 + 0 \times 2^1 + 1 \times 2^0 + 1 \times 2^{-1} + 1 \times 2^{-2} = 5.75$
  • $1011.1_{2}$ 对应的数值为 $8 + 0 + 2 + 1 + \frac{1}{2} = 11.5$

IEEE Floating-Point

上述浮点数存储方式的缺点在于如果要表示比较大的数,就需要比较多的二进制位数,比如对于$5 \times 2^{100}$ 就需要103位。IEEE表示就解决了这个问题,在IEEE表示中,浮点数 $V$ 被表示为 $V = (-1)^{s} \times M \times 2^{E}$,其中 $s, M, E$ 的含义如下:

  • $s$ 为符号位
  • $M$ 为乘法因子
  • $E$ 为浮点数的指数部分

IEEE 754标准是定义浮点数表示和算术的国际标准,它定义了多种不同精度的浮点数格式,但最常见的是单精度 single precesion(32位)和双精度 double precesion(64位),浮点数分为 s, exp, frac 三个部分存储:

s
exp
frac
s
exp
frac (51:32)
31
30
23
22
0
frac (31:0)
63
62
52
51
32
Single Precision
Double Precision
31
0
  • $1$ 位的 $s$ 与上述计算公式中的符号位 $s$ 相同,位于浮点数二进制表示的最高位,标志了浮点数的正负,如果 $s = 1$ 的话 $V$ 是负数,如果 $s = 0$ 的话 $V$ 为正数
  • $k$ 位的 $exp = e_{k-1} \cdots e_1 e_0$ 用于计算指数部分 $E$,使用原码表示,其中 $E = exp - Bias$,其中 $Bias = 2^{k-1} - 1$
    • 对于单精度浮点数,$k = 8$,$Bias = 2^{8-1} - 1 = 127$,$1 \le exp \le 254$,$-126 \le M \le 127$
    • 对于双精度浮点数,$k = 11$,$Bias = 2^{11-1} - 1 = 1023$, $1 \le exp \le 1022$,$-1022 \le M \le 1023$
  • $n$ 位的 $frac = 0 \cdot f_{n-1} \cdots f_1 f_0$ 用于计算因子 $M$,其中 $M = 1 + frac$,$frac$ 表示的小数计算方式见实数的二进制表示
    • 对于单精度浮点数,$n = 23$,$M$ 的最大值为 $2 - 2^{-23}$,$M$ 最小值为 $1$
    • 对于双精度浮点数,$n = 52$,$M$ 的最大值为 $2 - 2^{-52}$,$M$ 最小值为 $1$
s
s
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
s
s
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
≠ 0
≠ 0
s
s
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
1
frac
frac
s
s
≠ 0 and ≠ 255
≠ 0 and ≠ 255
frac
frac
Normalized
Normalized
Denormalized
Denormalized
Infinity
Infinity
NaN
NaN
Text is not SVG - cannot display

视频讲解

3 - 运算电路

包含数字电路的一部分内容,为部分其他内容的基础,了解即可,一般不会直接考察。

加法运算电路

最基本的加法单元是 半加器(Half Adder)。它有两个输入,一个是加数,一个是被加数,并有两个输出,一个是和,一个是进位。

half_adder_action

1-bitHalfAdderABCoutS

全加器(Full Adder)是半加器的扩展,它加上了前一位的进位作为第三个输入,并有两个输出,一个是和,一个是进位。全加器可以级联,用于多位加法。

full_adder_action

1-bitfulladderABCinCoutS

串联全加器:构成加法器

1-bitFullAdder1-bitFullAdder1-bitFullAdder1-bitFullAdderC3C2C1C0C4A3B3A2B2A1B1A0B0S3S2S1S0

为了进行多位数的加法,全加器可以串联起来形成一个串行进位加法器(ripple carry adder)。在这种安排中,每个全加器的进位输出连接到下一个全加器的进位输入。这样,一个 n 位的加法器可以通过串联 n 个全加器来实现。

减法运算电路

二进制中的减法可以通过加法来实现。减去一个数等同于加上它的补码。为了得到一个数的补码,你首先得到它的反码(每一位都取反)然后再加1。所以,一个简单的减法器可以通过使用加法器并对第二个输入(被减数)进行补码转换来构建。

乘法运算电路

二进制乘法与十进制乘法的手工计算方式类似:

       1011   (this is binary for decimal 11)
     × 1110   (this is binary for decimal 14)
     ======
       0000   (this is 1011 × 0)
      1011    (this is 1011 × 1, shifted one position to the left)
     1011     (this is 1011 × 1, shifted two positions to the left)
  + 1011      (this is 1011 × 1, shifted three positions to the left)
  =========
   10011010   (this is binary for decimal 154)

在计算机中实现乘法指令可以通过如下几种方式模拟以上过程:

1. 软件算法

如果没有专有硬件支持,可以通过一系列加法和位移操作来实现乘法。编译器将乘法运算转换为一个循环代码段,在循环代码段中通过比较、加法和移位等指令实现乘法运算。

// 如何使用加法和位移来实现两个整数的乘法(了解即可)
unsigned int multiply(unsigned int a, unsigned int b) {
    unsigned int result = 0;  // 结果初始化为0
    while (b > 0) {           // 当第二个操作数大于0时继续循环
        if (b & 1) {          // 检查b的最低位是否为1
            result += a;      // 如果是,将a加到结果上
        }
        a <<= 1;              // 将a左移一位,相当于a乘以2
        b >>= 1;              // 将b右移一位,去掉已经处理过的最低位
    }
    return result;            // 返回计算的乘积
}

2. 顺序乘法器

通过硬件的方式实现以上的软件算法,这是最基础的实现方式,类似于我们在纸上做乘法那样,一步一步进行。

3. 阵列乘法器

阵列乘法器(Array Multiplier)是一种用于执行乘法运算的硬件电路,它通过布置一系列全加器和半加器在一个二维网格或阵列中来实现,它能够并行处理多个位的乘法和累加,可以在一个或多个时钟周期内完成乘法操作。

除法运算电路

二进制除法在计算机中的实现与我们在十进制中所执行的传统长除法类似。与二进制乘法一样,我们将使用基本的二进制操作,如移位和减法。

   商(Quotient)
    0110
   =======
10 | 1101
-      10   (这是从 `110` 中减去的,因为它大于或等于 10)
   --------
        100 
    -   10   (这是从 `100` 中减去的,因为它大于或等于 10)
   --------
         10
     -   10   (这是从 `10` 中减去的,因为它大于或等于 10)
   --------
          0   (这是余数 Remainder)

4 - 类型转换

需了解整形和浮点型之间的类型转换,以及不同长度的类型之间的转换,可能在选择题考察,也会在大题中作为知识点考察。

有符号整数和无符号整数

这里需要了解有符号整数(int)和无符号整数(unsigned)这两个类型:

  • int:使用补码来存储数据。
  • unsigned:所有位,包括最高位,都用于表示数值,表示非负数。

以8位有符号整数和无符号整数说明一下

  • 8 位有符号整数
    • 表示的范围为 -128 ~ 127
    • 最大数为127,对应的二进制为0111 1111
    • 最小数为-128,对应的二进制为1000 0000
  • 8 位无符号整数
    • 表示的范围为 0 ~ 255
    • 最大数为255,对应的二进制为1111 1111
    • 最小数为0,对应的二进制为0000 0000

intunsigned进行类型转化时,位模式不变,可以理解成计算机存储单元中的二进制表示没有变化,只是从程序层面阐述该二进制数据的方式变了。

当我们将8位的有符号整数-128转化为无符号整数时,其值变成了128

整形和浮点数

  • floatint: 将浮点数转换为整数时,小数部分会被舍去。例如,将 3.9 转换为整数将得到 3。
  • intfloat: 将整数转换为浮点数时,结果会是一个等值的浮点数。例如,将整数 4 转换为浮点数将得到 4.0。

不同长度的类型转换

类型最小位数常见位数(32位系统)常见位数(64位系统)
signed char8位8位8位
short16位16位16位
int16位32位32位
long32位32位64位
long long64位64位64位
  • 较小的整数类型被转换为较大的整数类型时,高位会被自动填充为0
  • 较大的整数类型被转换为较小的整数类型时,高位会被自动截断

shortint的类型转化为例说明:

/* 将short转为int */
short s = 0x1234;
int i = s;
// 高16位被填充为0
// i = 0x00001234

/* 将int转为short */
int i = 0x12345678;
short s = i;
// 高16位被截断
// s = 0x5678

隐式和显式类型转换

  1. 隐式类型转换

你在一个表达式中混合使用 intfloat 时,C语言会自动进行类型转换以使得表达式可以正确计算。通常情况下,低精度类型会被提升到高精度类型,即 int 会被转换为 float

  1. 显式类型转换

也可以使用类型转换运算符显式地转换一个类型为另一个类型。

/* 隐式类型转换 */
int i = 5;
float f = 2.5;
float result = i + f;  // i 被隐式转换为5.0然后进行计算

/* 显式类型转换 */
int i = 5;
float f = 3.2;
int result = (int)f + i; // f 被显式转换为3,然后与i相加