计算机数值表示与转换全解析
立即解锁
发布时间: 2025-08-22 01:32:29 阅读量: 3 订阅数: 8 


编写出色代码:理解机器
# 计算机数值表示与转换全解析
## 1. 数值进制转换基础
在计算机领域,数值的进制转换是一项基础且重要的技能。例如,将二进制数转换为十六进制时,以二进制数 001011001010 为例,首先要把它按每四位一组进行划分,得到 0010_1100_1010 。然后,在对应的转换表中查找这些二进制值,替换为合适的十六进制数字,最终结果是 $2CA 。这相较于十进制与二进制、十进制与十六进制之间的转换,要相对简单一些。
### 1.1 八进制数制系统
八进制(以 8 为基数)表示法在早期计算机系统中较为常见,如今偶尔仍能见到有人使用。八进制对于 12 位和 36 位计算机系统(或任何位数是 3 的倍数的系统)很适用,但对于位数是 2 的幂次方的计算机系统(如 8 位、16 位、32 位和 64 位系统),其适用性就没那么强了。不过,一些编程语言仍支持用八进制表示数值,部分较旧的 Unix 应用程序也会使用八进制。
#### 1.1.1 编程语言中的八进制表示
不同的编程语言对八进制数的表示方法有所不同:
- **C 语言及其衍生语言(如 C++ 和 Java)**:在数字字符串前加零来指定八进制基数。例如,“0123” 表示的十进制值是 83 ,而不是 123 。
- **MASM**:使用 “Q” 或 “q” 后缀来表示八进制数。这可能是因为 “Q” 看起来像 “O” ,而避免使用 “O” 或 “o” 是为了防止与零混淆。
- **Visual Basic**:使用 “&O”(字母 O ,不是零)前缀来表示八进制值。例如,“&O123” 表示十进制值 83 。
#### 1.1.2 八进制与二进制表示的转换
八进制与二进制的转换和二进制与十六进制的转换类似,只是这里按每三位一组进行处理。具体转换方法如下:
- **八进制转二进制**:将八进制数中的每个数字替换为对应的三位二进制数。例如,将 123q 转换为二进制,结果是 %0_0101_0011 。
| 八进制数字 | 对应二进制 |
| ---- | ---- |
| 1 | 001 |
| 2 | 010 |
| 3 | 011 |
- **二进制转八进制**:把二进制字符串按每三位一组划分(必要时补零),然后在转换表中查找每组对应的八进制数字。
- **八进制转十六进制**:先将八进制数转换为二进制,再将二进制转换为十六进制。
### 1.2 数值与字符串的转换
大多数编程语言(或其库)会自动进行数值与字符串的转换,这使得很多初学者可能没有意识到这种转换的存在。下面我们来看看两种常见的转换:字符串转数值和数值转字符串。
#### 1.2.1 字符串转数值
在不同语言中,将字符串转换为数值很容易实现。例如:
```cpp
cin >> i; // C++
```
```pascal
readln( i ); // Pascal
```
```basic
input i // BASIC
```
```hla
stdin.get(i); // HLA
```
这些语句虽然使用方便,但程序员往往没有考虑到它们在程序中的执行成本。在性能关键的场景下,这些转换可能会对程序产生较大影响。为了简化讨论,我们忽略非法字符和数值溢出的可能性,使用以下算法将十进制数字字符串转换为整数值:
1. 初始化一个变量为零,用于存储最终结果。
2. 如果字符串中没有更多数字,算法结束,该变量即为数值。
3. 从字符串中从左到右取出下一个数字。
4. 将变量乘以 10 ,然后加上取出的数字。
5. 重复步骤 2 。
#### 1.2.2 数值转字符串
将整数值转换为字符串则需要更多的操作。其算法如下:
1. 初始化一个空字符串。
2. 如果整数值为零,输出 0 ,算法结束。
3. 将当前整数值除以 10 ,计算余数和商。
4. 将余数(范围在 0 到 9 之间)转换为字符,并添加到字符串末尾。
5. 如果商不为零,将其作为新值,重复步骤 3 - 5 。
6. 按与插入顺序相反的顺序输出字符串中的字符。
需要注意的是,这些算法仅适用于无符号整数。有符号整数的处理稍微复杂一些,而浮点数在字符串和数值形式之间的转换则更加困难。在编写使用浮点运算的代码时,要牢记这一点。
## 2. 计算机内部数值表示
现代计算机系统大多是二进制系统,使用内部二进制格式来表示数值和其他对象。但大多数系统只能有效地表示特定大小的二进制值。因此,编写优秀代码时,要确保程序使用的是计算机能够有效表示的数据对象。
### 2.1 位(Bit)
二进制计算机中最小的数据单位是单个位。虽然一个位只能表示两种不同的值(通常是 0 或 1 ),但它可以表示无穷多的二项组合。例如:
- 0 或 1
- 假(False)或真(True)
- 关(Off)或开(On)
- 男(Male)或女(Female)
- 错(Wrong)或对(Right)
甚至可以用一个位表示任意两个不同的项目,如数字 723 和 1245 、颜色红色和蓝色,或者一个位的 0 表示红色, 1 表示数字 3256 。不过,单个位只能表示两个不同的值,对于大多数计算需求来说是不够的。为了克服这个限制,我们将多个位组合成位串。
### 2.2 位串
通过将位组合成序列,我们可以形成与其他数值表示(如十六进制和八进制)等效的二进制表示。但大多数计算机系统不允许随意组合任意数量的位,而是要求使用具有特定固定长度的位串。以下是一些常见的位串长度及其名称:
- **半字节(Nibble)**:由四个位组成。大多数计算机系统在内存中对其访问效率不高,但一个半字节恰好可以表示一个十六进制数字。
- **字节(Byte)**:由八个位组成,是许多 CPU 中最小的可寻址数据项。这意味着 CPU 可以高效地从内存的 8 位边界检索数据。因此,许多语言支持的最小数据类型通常占用一个字节的内存,即使该数据类型实际所需的位数可能更少。为了表示字节中的各个位,我们使用位编号。通常,字节中的位编号如图 1 所示,位 0 是低位(LO)或最低有效位,位 7 是高位(HO)或最高有效位,其他位按编号引用。
```mermaid
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(位 7):::process --> B(位 6):::process --> C(位 5):::process --> D(位 4):::process --> E(位 3):::process --> F(位 2):::process --> G(位 1):::process --> H(位 0):::process
```
图 1:字节中的位编号
- **字(Word)**:“字” 的含义因 CPU 而异。在本文中,采用 80x86 术语,将字定义为 16 位的数量。和字节一样,字中的位也从 0 开始编号(低位),直到 15 (高位)。一个字正好包含两个字节,位 0 到 7 构成低位字节,位 8 到 15 构成高位字节。
```mermaid
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(位 15):::process --> B(位 14):::process --> C(位 13):::process --> D(位 12):::process --> E(位 11):::process --> F(位 10):::process --> G(位 9):::process --> H(位 8):::process --> I(位 7):::process --> J(位 6):::process --> K(位 5):::process --> L(位 4):::process --> M(位 3):::process --> N(位 2):::process --> O(位 1):::process --> P(位 0):::process
```
图 2:字中的位编号
- **双字(Double Word)**:正如其名,双字是一对字,即 32 位长。一个双字包含一对字和四个字节。
```mermaid
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(位 31):::process --> B(位 30):::process --> ... --> Z(位 0):::process
```
图 3:双字中的位布局
- **四字(Quad Word)**:为了描述 64 位数据类型,我们引入 “四字” 这个术语。因为一些编程语言支持 64 位整数,大多数语言也支持 64 位浮点数。
- **长字(Long Word)**:用于表示 128 位的值。虽然现在支持 128 位值的语言不多,但这为未来的发展预留了空间。
一般来说,一个 n 位的位串最多可以表示 2^n 种不同的值。以下是不同长度位串可表示的可能对象数量:
| 位串大小(位) | 可能的组合数(2^n) |
| ---- | ---- |
| 4 | 16 |
| 8 | 256 |
| 16 | 65,536 |
| 32 | 4,294,967,296 |
| 64 | 18,446,744,073,709,551,616 |
| 128 | 340,282,366,920,938,463,463,374,607,431,768,211,456 |
### 2.3 有符号和无符号数
二进制数 0…00000 表示零, 0…00001 表示一, 0…00010 表示二,依此类推。但负数该如何表示呢?大多数计算机系统使用补码数制来表示有符号值。有符号数的表示对这些数值有一些基本限制,因此理解计算机系统中有符号和无符号数表示的差异,对于高效使用它们至关重要。
#### 2.3.1 补码数制
使用 n 位,我们只能表示 2^n 种不同的对象。由于负数也是独立的对象,我们需要将这 2^n 种组合在负数和非负数之间进行划分。例如,一个字节可以表示 -128 到 -1 的负数和 0 到 127 的非负数;一个 16 位的字可以表示 -32,768 到 +32,767 的有符号值;一个 32 位的双字可以表示 -2,147,483,648 到 +2,147,483,647 的值。一般来说, n 位可以表示 -2^(n - 1) 到 +2^(n - 1) - 1 的有符号值。
补码系统使用最高位作为符号位。如果最高位是 0 ,则该数为非负数;如果最高位是 1 ,则该数为负数。以下是一些 16 位数字的例子:
- $8000 (%1000_0000_0000_0000) 是负数,因为最高位是 1 。
- $100 (%0000_0001_0000_0000) 是非负数,因为最高位是 0 。
- $7FFF (%0111_1111_1111_1111) 是非负数。
- $FFFF (%1111_1111_1111_1111) 是负数。
- $FFF (%0000_1111_1111_1111) 是非负数。
#### 2.3.2 补码数的取反
要对补码数取反,可以使用以下算法:
1. 反转数字中的所有位,即把所有 0 变为 1 ,所有 1 变为 0 。
2. 对反转后的结果加 1 (忽略任何溢出)。
例如,计算十进制值 -5 的 8 位补码表示:
- 5 的二进制表示:%0000_0101
- 反转所有位:%1111_1010
- 加 1 得到 -5 的补码表示:%1111_1011
如果对 -5 取反,结果是 5 (%0000_0101),符合预期:
- -5 的补码表示:%1111_1011
- 反转所有位:%0000_0100
- 加 1 得到 5 的二进制表示:%0000_0101
不过,在补码数制中,最小的负数无法取反。例如, -32,768 的 16 位十六进制表示是 $8000 ,对其取反后还是 $8000 。这是因为 16 位补码数制无法表示 +32,768 这个值。
### 2.4 二进制数的一些有用属性
了解二进制数的一些有趣特性,在编程中可能会很有用:
1. 如果二进制(整数)值的第 0 位为 1 ,则该数为奇数;如果为 0 ,则该数为偶数。
2. 如果二进制数的低 n 位都为 0 ,则该数能被 2^n 整除。
3. 如果一个二进制值只有第 n 位为 1 ,其余位都为 0 ,则该数等于 2^n 。
4. 如果一个二进制值从第 0 位到第 n 位(不包括第 n 位)都是 1 ,其余位为 0 ,则该值等于 2^n - 1 。
5. 将一个数的所有位向左移动一位,相当于将该二进制值乘以 2 。
6. 将无符号二进制数的所有位向右移动一位,相当于将该数除以 2 (不适用于有符号整数值),奇数会向下取整。
7. 两个 n 位二进制值相乘,结果可能需要多达 2 * n 位来存储。
8. 两个 n 位二进制值相加或相减,结果最多需要 n + 1 位来存储。
9. 反转二进制数的所有位(即把所有 0 变为 1 ,所有 1 变为 0 ),等同于对该值取反并减 1 。
10. 对给定位数的最大无符号二进制值加 1 ,结果总是为 0 。
11. 对 0 减 1 ,结果总是给定位数的最大无符号二进制值。
12. 一个 n 位的值提供 2^n 种唯一的位组合。
13. 值 2^n - 1 包含 n 个 1 。
建议记住从 2^0 到 2^16 的所有 2 的幂次方值,因为它们在程序中经常出现。以下是这些值的列表:
| n | 2^n |
| ---- | ---- |
| 0 | 1 |
| 1 | 2 |
| 2 | 4 |
| 3 | 8 |
| 4 | 16 |
| 5 | 32 |
| 6 | 64 |
| 7 | 128 |
| 8 | 256 |
| 9 | 512 |
| 10 | 1,024 |
| 11 | 2,048 |
| 12 | 4,096 |
| 13 | 8,192 |
| 14 | 16,384 |
| 15 | 32,768 |
| 16 | 65,536 |
### 2.5 符号扩展、零扩展和收缩
许多现代高级编程语言允许在表达式中使用不同大小的整数对象。当表达式中的两个操作数大小不同时,不同的语言处理方式不同:有些语言会报错,有些则会自动将操作数转换为通用格式。但这种转换并非没有代价,因此编写代码时,要了解编译器如何处理这类表达式。
#### 2.5.1 符号扩展(Sign Extension)
在补码系统中,一个负数的表示会因表示大小的不同而不同。例如, -64 的 8 位补码表示是 $C0 , 16 位表示是 $FFC0 。显然,负数和非负数的扩展方式不同。
符号扩展是将一个值从较少的位数扩展到较多的位数,方法很简单:只需将符号位复制到新格式的额外高位中。例如,将 8 位数字扩展为 16 位数字,只需将 8 位数字的第 7 位复制到 16 位数字的第 8 到 15 位;将 16 位数字扩展为双字,只需将第 15 位复制到双字的第 16 到 31 位。
在处理不同长度的有符号值时,必须使用符号扩展。例如,将一个字节量与一个字量相加时,需要先将字节扩展为 16 位,再进行加法运算。其他操作可能需要扩展到 32 位。
#### 2.5.2 零扩展(Zero Extension)
处理无符号二进制数时,零扩展可将小的无符号值转换为大的无符号值。零扩展很简单,只需在较大操作数的高位字节中存储零。例如,将 8 位值 $82 零扩展为 16 位,只需在高位字节插入零,得到 $0082 。
许多高级语言编译器会自动处理符号扩展和零扩展。以下是 C 语言中的示例:
```c
signed char sbyte; // C 语言中的字符类型是字节值
short int sword; // C 语言中的短整型通常是 16 位值
long int sdword; // C 语言中的长整型通常是 32 位值
sword = sbyte; // 自动将 8 位值符号扩展为 16 位
sdword = sbyte; // 自动将 8 位值符号扩展为 32 位
sdword = sword; // 自动将 16 位值符号扩展为 32 位
```
有些语言(如 Ada)可能需要显式地进行类型转换。这种要求显式转换的语言的优点是,编译器不会在背后自动插入转换操作。如果程序员没有自行提供转换,编译器会发出诊断消息,提醒程序需要进行额外的工作。
#### 2.5.3 符号收缩(Sign Contraction)
符号收缩是将一个具有较多位数的值转换为位数较少的值,这稍微麻烦一些。符号扩展总是能成功,给定一个 m 位的有符号值,总是可以使用符号扩展将其转换为 n 位(n > m)的数。但给定一个 n 位的数,不一定能将其转换为 m 位(m < n)的数。
例如, -448 的 16 位十六进制表示是 $FE40 ,由于其绝对值太大,无法收缩为 8 位。
要正确地进行符号收缩,需要查看要舍弃的高位字节。首先,高位字节必须全为零或 $FF ,否则无法进行收缩。其次,结果值的最高位必须与从原数中移除的每一位相匹配。以下是一些将 16 位值转换为 8 位值的例子:
- $FF80 (%1111_1111_1000_0000) 可以收缩为 $80 (%1000_0000) 。
- $0040 (%0000_0000_0100_0000) 可以收缩为 $40 (%0100_0000) 。
- $FE40 (%1111_1110_0100_0000) 不能收缩为 8 位。
- $0100 (%0000_0001_0000_0000) 不能收缩为 8 位。
在高级语言中,符号收缩有些困难。像 C 语言,可能只是将表达式的低位部分存储到较小的变量中,丢弃高位部分(最多,C 编译器可能会在编译时给出关于可能丢失精度的警告)。通常,可以使用以下代码在 C 语言中进行符号收缩:
```c
signed char sbyte; // C 语言中的字符类型是字节值
short int sword; // C 语言中的短整型通常是 16 位值
long int sdword; // C 语言中的长整型通常是 32 位值
sbyte = (signed char) sword;
sbyte = (signed char) sdword;
sword = (short int) sdword;
```
在 C 语言中,唯一安全的解决方案是在将值存储到较小变量之前,将表达式的结果与上下限进行比较。但如果经常需要这样做,代码会变得很繁琐。
总之,符号扩展和零扩展并非总是免费的操作,将较小的整数赋值给较大的整数可能比在两个相同大小的整数变量之间移动数据需要更多的机器指令,执行时间也更长。因此,在同一个算术表达式或赋值语句中混合使用不同大小的变量时,要格外小心。
## 3. 总结与实践建议
### 3.1 数值表示与转换的重要性
在计算机编程中,数值的表示和转换是基础且关键的知识。不同的进制表示(如二进制、八进制、十六进制)在不同场景下有着各自的优势。例如,二进制是计算机内部的基本表示方式,而十六进制则更便于人类阅读和书写较长的二进制数。八进制虽然在现代计算机系统中使用较少,但在一些特定的编程环境和旧的应用程序中仍有其用武之地。
数值与字符串的转换也是编程中常见的操作。尽管大多数编程语言提供了自动转换的功能,但了解其背后的算法和成本,可以帮助我们在性能关键的场景下做出更合理的选择。
### 3.2 位操作的应用
位操作在很多领域都有广泛的应用,比如在嵌入式系统编程、数据压缩、图像处理等方面。利用二进制数的特性,我们可以高效地实现一些复杂的算法。例如,通过位左移和右移操作可以快速实现乘法和除法运算,通过判断二进制数的某一位是否为 1 可以快速判断一个数的奇偶性。
### 3.3 实践建议
- **合理选择数据类型**:在编程时,要根据实际需求选择合适的数据类型,避免不必要的内存浪费和性能损失。例如,如果只需要表示 0 - 255 之间的整数,使用一个字节的数据类型就足够了。
- **注意数据类型的转换**:在进行不同大小的数据类型转换时,要清楚符号扩展、零扩展和收缩的规则,避免因转换不当导致的数据错误。同时,要注意转换操作的成本,尽量减少不必要的转换。
- **利用二进制数的特性**:在编程中,充分利用二进制数的特性可以提高代码的效率。例如,通过位操作替代一些复杂的算术运算,或者利用二进制数的特性进行条件判断。
### 3.4 常见问题及解决方法
#### 3.4.1 数值溢出问题
在进行数值运算时,尤其是使用固定大小的数据类型时,可能会出现数值溢出的问题。例如,两个 8 位的无符号整数相加,结果可能会超过 255 ,导致溢出。为了避免这种情况,可以使用更大的数据类型进行运算,或者在运算前进行范围检查。
#### 3.4.2 符号扩展和收缩错误
在进行符号扩展和收缩时,如果不遵循规则,可能会导致数据错误。例如,在进行符号收缩时,如果高位字节不符合要求,强行收缩会得到错误的结果。因此,在进行这些操作时,要仔细检查数据的范围和符号位。
#### 3.4.3 数值与字符串转换的性能问题
数值与字符串的转换可能会带来一定的性能开销,尤其是在频繁进行转换的场景下。为了提高性能,可以尽量减少不必要的转换,或者使用更高效的转换算法。
### 3.5 未来发展趋势
随着计算机技术的不断发展,对数值表示和处理的要求也在不断提高。例如,随着人工智能和大数据的兴起,对高精度数值计算的需求越来越大。未来,可能会出现更多支持更高精度数值表示的数据类型和算法。
同时,随着量子计算机的发展,传统的二进制数值表示方式可能会面临挑战。量子计算机使用量子比特(qubit)来表示信息,其表示和处理方式与传统计算机有很大的不同。这将为数值表示和转换带来新的研究方向。
### 3.6 总结
本文详细介绍了计算机中数值的表示和转换方法,包括不同进制之间的转换、数值与字符串的转换、计算机内部的数值表示、有符号和无符号数的处理、二进制数的特性以及符号扩展、零扩展和收缩等操作。通过了解这些知识,我们可以更好地理解计算机的工作原理,编写更高效、更安全的代码。
在实际编程中,要根据具体需求合理选择数据类型和转换方法,充分利用二进制数的特性,避免常见的错误和性能问题。同时,要关注计算机技术的发展趋势,不断学习和掌握新的知识和技能。
以下是一个简单的流程图,总结了数值表示和转换的主要流程:
```mermaid
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
A(数值输入):::process --> B{选择进制}:::process
B -->|二进制| C(二进制处理):::process
B -->|八进制| D(八进制处理):::process
B -->|十六进制| E(十六进制处理):::process
B -->|十进制| F(十进制处理):::process
C --> G{是否转换}:::process
D --> G
E --> G
F --> G
G -->|是| H(转换操作):::process
G -->|否| I(直接使用):::process
H --> J(输出结果):::process
I --> J
```
最后,为了方便大家复习和查阅,以下是一个总结表格,列出了本文的主要知识点:
| 知识点 | 详细内容 |
| ---- | ---- |
| 进制转换 | 二进制转十六进制、八进制与二进制的转换、数值与字符串的转换算法 |
| 计算机内部数值表示 | 位、位串(半字节、字节、字、双字、四字、长字)的概念和特点 |
| 有符号和无符号数 | 补码数制、补码数的取反、二进制数的有用属性 |
| 符号扩展、零扩展和收缩 | 符号扩展和零扩展的规则、符号收缩的条件和方法 |
| 实践建议 | 合理选择数据类型、注意数据类型转换、利用二进制数特性 |
| 常见问题及解决方法 | 数值溢出、符号扩展和收缩错误、数值与字符串转换的性能问题 |
| 未来发展趋势 | 高精度数值计算、量子计算机对数值表示的影响 |
希望通过本文的介绍,大家对计算机中数值的表示和转换有了更深入的理解,能够在编程中更好地运用这些知识。
0
0
复制全文
相关推荐









