浮点数运算误差与输入输出设备的时间特性
1. 浮点数运算误差
1.1 常见算术误差
浮点数运算中,常见的算术误差是舍入误差,其产生原因主要有两点:一是存储可用的位数有限;二是小数部分在所有数制中不能精确表示。与定点表示不同的是,浮点运算中 CPU 硬件会移动算术结果的有效位并相应调整指数,导致位丢失,而整数运算中的位移动在程序中是显式的。
1.2 示例分析
以下是一个简单的浮点数加法程序示例:
$ ./addFloats
Enter a number: 123.4
Enter a number: 567.8
123.400002 + 567.799988 = 691.200012
结果看起来不准确,使用调试器分析:
(gdb) b 53
Breakpoint 1 at 0x11e4: file addFloats.s, line 53.
(gdb) r
Starting program: /home/bob/progs/chapter_19/addFloats_asm/addFloats
Enter a number: 123.4
Enter a number: 567.8
Breakpoint 1, main () at addFloats.s:53
53 call printf@plt
查看变量存储的十六进制值:
(gdb) x/3xw 0x7fffffffdecc
0x7fffffffdecc: 0x42f6cccd 0x440df333 0x442ccccd
以变量
x
为例,从 IEEE 754 格式可知,指数存储为 85₁₆ = 133₁₀,即 e = 6。所以
x
存储为 1.11101101100110011001101 × 2⁶ = 1111011.01100110011001101 = 123.40000152587890625。
使用调试器查看寄存器中的值:
(gdb) p $xmm0.v2_double
$1 = {123.40000152587891, 0}
(gdb) p $xmm1.v2_double
$2 = {567.79998779296875, 0}
(gdb) p $xmm2.v2_double
$3 = {691.20001220703125, 0}
可以看出所有数字都存在舍入误差。
1.3 其他误差类型
- 吸收(Absorption) :当两个数量级相差很大的数相加(或相减)时,较小数的值在计算中会丢失。例如:
(gdb) run
Enter a number: 16777215.0
Enter a number: 0.1
Starting program: /home/bob/progs/chapter_19/addFloats_asm/addFloats
Breakpoint 1, main () at addFloats.s:53
53 call printf@plt
分析存储的十六进制值可知,小数 0.1 在浮点加法中被吸收。
-
抵消(Cancellation)
:当两个相差很小的数相减时会发生抵消误差。如果其中一个数被舍入,其低位部分不准确,会导致结果出错。例如:
$ ./addFloats
Enter a number: 1677721.5
Enter a number: -1677721.4
1677721.500000 + -1677721.375000 = 0.125000
相对误差为 (0.125 – 0.1) / 0.1 = 0.25 = 25%。
-
结合律问题(Associativity)
:浮点加法不满足结合律,即存在某些浮点数
x
、
y
和
z
,使得 (x + y) + z 不等于 x + (y + z)。以下是一个简单的 C 程序测试结合律:
/* threeFloats.c
* Associativity of floats.
*/
#include <stdio.h>
int main()
{
float x, y, z, sum1, sum2;
printf("Enter a number: ");
scanf("%f", &x);
printf("Enter a number: ");
scanf("%f", &y);
printf("Enter a number: ");
scanf("%f", &z);
sum1 = x + y;
sum1 += z; /* sum1 = (x + y) + z */
sum2 = y + z;
sum2 += x; /* sum2 = x + (y + z) */
if (sum1 == sum2)
printf("%f is the same as %f\n", sum1, sum2);
else
printf("%f is not the same as %f\n", sum1, sum2);
return 0;
}
运行程序:
$ ./threeFloats
Enter a number: 1.1
Enter a number: 1.2
Enter a number: 1.3
3.600000 is not the same as 3.600000
使用调试器分析可知,中间结果的舍入误差导致了结合律不成立。
1.4 数值准确性建议
为提高数值准确性,可考虑以下几点:
- 尝试缩放数据,以便使用整数运算。
- 使用双精度数(double)代替单精度数(float),可提高准确性并可能提高执行速度。
- 安排计算顺序,使大小相近的数进行加减运算。
- 避免复杂的算术语句,以免掩盖错误的中间结果。
- 选择能测试算法的测试数据,若程序处理小数值,应包含无精确二进制等价的数据。
2. 输入输出设备的时间特性
2.1 输入输出子系统概述
程序通过输入输出(I/O)子系统与外部世界(CPU 和内存之外的设备)进行通信。常见的输入设备有键盘、鼠标等,输出设备有显示屏、打印机等,磁盘、固态硬盘、USB 闪存等也是 I/O 设备。
2.2 时间特性考虑
2.2.1 内存时间特性
内存的时间特性相对均匀,不依赖外部事件,其时间处理由硬件完成,程序员无需关注。常见的两种内存中,静态随机存取存储器(SRAM)的访问时间比动态随机存取存储器(DRAM)快 5 - 10 倍,但 SRAM 成本高且占用物理空间大。通常 DRAM 用于主内存,SRAM 用于较小的缓存内存。不过,CPU 速度仍比内存快,尤其是 DRAM,访问内存通常是减慢程序执行的重要因素。
2.2.2 I/O 设备时间特性
几乎所有 I/O 设备都比内存慢。以键盘为例,每分钟输入 120 个单词,相当于每秒输入 10 个字符,即每个字符间隔 100 毫秒,而运行在 2 GHz 的 CPU 在这段时间内可执行约 2 亿条指令,且按键时间间隔不一致。即使是固态硬盘,与内存相比也较慢,例如典型 SSD 的数据传输速率约为 500 MBps,而 DDR4 内存(常用于主内存)的传输速率约为 20 GBps,快约 40 倍。
以下是内存和 I/O 设备时间特性对比表格:
| 设备类型 | 传输速率 | 与 CPU 速度对比 |
| ---- | ---- | ---- |
| SRAM | 访问时间快 | 比 CPU 慢,但差距相对小 |
| DRAM | 访问时间较慢 | 比 CPU 慢,是影响程序执行速度的重要因素 |
| 键盘 | 约 10 字符/秒 | 远慢于 CPU |
| 固态硬盘(SSD) | 约 500 MBps | 远慢于 DDR4 内存 |
| DDR4 内存 | 约 20 GBps | 比 I/O 设备快很多 |
mermaid 格式流程图展示 CPU 与内存、I/O 设备的关系:
graph LR
A[CPU] -->|数据传输| B[内存]
A -->|数据传输| C[I/O 设备]
B <-->|数据交互| C
2.3 CPU 与 I/O 设备的接口
由于 CPU 访问内存和 I/O 设备使用相同的总线,虽然可以使用
mov
指令在 CPU 和特定 I/O 设备之间传输数据,但要使其正常工作,还需考虑其他问题,主要是内存和 I/O 设备的时间差异。
2.3.1 解决时间差异的方法
为了处理 CPU 与 I/O 设备之间的时间差异,通常采用以下几种方法:
-
程序控制 I/O
:CPU 不断查询 I/O 设备的状态,直到设备准备好进行数据传输。这种方法简单,但会浪费 CPU 时间,因为 CPU 在等待过程中不能做其他有意义的工作。
-
中断驱动 I/O
:当 I/O 设备准备好数据时,向 CPU 发送中断信号,CPU 暂停当前工作,处理 I/O 操作,处理完成后再返回原来的工作。这种方法提高了 CPU 的利用率。
-
直接内存访问(DMA)
:DMA 控制器负责在 I/O 设备和内存之间直接传输数据,无需 CPU 干预。在数据传输过程中,CPU 可以继续执行其他任务,大大提高了系统的效率。
以下是这三种方法的对比表格:
| 方法 | 优点 | 缺点 | 适用场景 |
| ---- | ---- | ---- | ---- |
| 程序控制 I/O | 实现简单 | CPU 利用率低 | 对实时性要求不高、I/O 操作较少的场景 |
| 中断驱动 I/O | 提高 CPU 利用率 | 中断处理有开销 | 大多数 I/O 操作场景 |
| 直接内存访问(DMA) | 高效,不占用 CPU 时间 | 硬件成本高 | 大量数据传输的场景 |
2.4 I/O 设备编程简介
编程 I/O 设备通常需要以下步骤:
1.
初始化设备
:设置设备的初始状态,如波特率、数据位、停止位等。
2.
配置中断(如果使用中断驱动 I/O)
:设置中断向量表,使 CPU 能够正确响应设备的中断信号。
3.
数据传输
:根据设备的类型和使用的方法(程序控制 I/O、中断驱动 I/O 或 DMA)进行数据的读写操作。
4.
错误处理
:处理可能出现的错误,如设备故障、数据传输错误等。
以下是一个简单的伪代码示例,展示如何使用程序控制 I/O 进行数据读取:
// 初始化设备
initialize_device();
// 等待设备准备好
while (!device_ready()) {
// 可以在这里做一些其他的低优先级任务
}
// 读取数据
data = read_data();
// 处理数据
process_data(data);
mermaid 格式流程图展示 I/O 设备编程的基本流程:
graph TD
A[初始化设备] --> B[等待设备准备好]
B -->|准备好| C[读取数据]
C --> D[处理数据]
B -->|未准备好| B
总结
浮点数运算中存在多种误差,如舍入误差、吸收误差、抵消误差和结合律问题等,为提高数值准确性,需要在算法设计和数据处理上进行合理安排。而在输入输出方面,I/O 设备与内存和 CPU 在时间特性上存在显著差异,为了实现高效的数据传输,需要采用合适的接口方法和编程技巧。了解这些知识对于编写高效、准确的程序至关重要。
在实际应用中,我们可以根据具体的需求和场景,选择合适的数值表示方法和 I/O 处理方式。例如,对于对精度要求较高的科学计算,应优先考虑使用双精度数并仔细分析算法;对于大量数据的输入输出,可采用 DMA 方式提高效率。同时,不断学习和掌握新的技术和方法,以适应不断发展的计算机系统和应用需求。
超级会员免费看


被折叠的 条评论
为什么被折叠?



