工程师和科学家经常编写迭代程序,其中浮点值以小增量逐步通过一系列值。
例如,假设“时间”变量需要以 t 的tMax
步长从 tMin 的低值变为 tMin 的高值deltaT
,其中所有这些变量都是双倍的。
明显但不正确的方法如下:
`for( time = tMin; time <= tMax; time += deltaT ) {
// Use the time variable in the loop
}
`
那么为什么这是错误的呢?
如果 deltaT 很小和/或范围很大(或两者兼有),则循环可能会执行数千次迭代。
这意味着到循环结束时,time
已经通过数千次加法运算的总和计算出来了。
以十进制形式对我们来说似乎“精确”的数字,例如 0.01,当计算机以二进制形式存储它们时并不精确,这意味着用于的值deltaT
实际上是精确值的近似值。
因此,每个加法步骤都会引入非常少量的舍入误差,当您将数千个这些误差相加时,总误差可能会很大。
如果您知道每次迭代的最小值和最大值以及所需的更改,则正确的方法如下:
`int nTimes = ( tMax - tMin ) / deltaT + 1;
for( int i = 0; i < nTimes; i++ ) {
time = tMin + i * deltaT;
}
// NOW use a more accurate time variable
// Or alternatively if you know the minimum, maximum, and number of desired iterations:
double deltaT = ( tMax - tMin ) / ( nTimes - 1 );
for( int i = 0; i < nTimes; i++ ) {
time = tMin + i * deltaT;
// NOW use a more accurate time variable
}
`
一般来说,有四个值可用于指定在一个范围内的步进——范围的低端、范围的高端、要采取的步数以及每一步要采取的增量——如果你知道其中任何三个,然后你可以计算第四个。
正确的循环应该使用整数计数器完成给定次数的循环,并使用范围的低端和如图所示的增量来计算循环每次迭代开始时的浮点循环变量。那为什么更好呢?
循环执行的次数现在由一个整数控制,它在递增时没有任何舍入误差,因此不会因为累积舍入而执行一次太多或一次太少的迭代。
时间变量现在是通过单次乘法和单次加法计算的,这仍然会引入一些舍入误差,但远小于数千次加法。+1 从何而来?
需要 +1 才能包括范围的两个端点。假设tMax
为 20,tMin 为 10,则为deltaT
2。
所需的时间是 10、12、14、16、18、20,总共是 6 个时间值,而不是 5。(如果你想这样看,五个间隔。)
( 20 - 10 ) / 2
产生 5,所以你必须添加额外的 1 以获得正确的次数 6。
另一种看待这个问题的方法是,如果 nTimes 是范围内的数据点数,那么nTimes - 1
就是数据点之间的间隙数。
示例: interpolate.c 是一个在循环中插入浮点数的简单粗暴的示例,它在课堂上 10 分钟内完成。这不是一个好的代码示例,但它是一个快速的小程序如何用于测试、玩游戏或在这种情况下演示一个新的或不熟悉的概念的示例。
此示例使用三种方法在f( x ) = x^3
从-1.0
到4.0
的步长范围内对函数进行插值:0.5
常数 - 取端点处输入的平均值,计算 f( 平均输入 ),并假设函数在该范围内是常数。
线性 - 评估端点处的函数,然后在其间使用端点函数值的线性插值。
非线性 - 在范围内线性插值函数输入,并在每个评估点评估插值输入的函数。