エンジニアや科学者は、浮動小数点値が値の範囲を少しずつ進む反復プログラムを頻繁に作成します。
たとえば、「時間」変数を tMin の下限から の上限に のtMax
ステップで変更する必要があるとしますdeltaT
。ここで、これらの変数はすべて double です。
明らかだが間違ったアプローチは次のとおりです。
`for( time = tMin; time <= tMax; time += deltaT ) {
// Use the time variable in the loop
}
`
では、なぜこれが間違っているのでしょうか。
deltaT が小さい、および/または範囲が大きい (またはその両方) 場合、ループは何千回も反復して実行される可能性があります。
つまり、ループの終わりまでに、time
数千回の加算操作の合計によって計算されています。
0.01 のように 10 進数では「正確」に見える数値は、コンピュータが 2 進数で格納すると正確ではありません。つまり、使用される値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
}
`
一般に、範囲内のステップ実行を指定するために使用できる値は 4 つあります。範囲の下限、範囲の上限、実行するステップ数、および各ステップで実行する増分です。そのうちの 3 つがわかれば、4 つ目を計算できます。
正しいループでは、整数カウンターを使用して所定の回数ループを完了し、示されているように範囲の下限とインクリメントを使用して、ループの各反復の開始時に浮動小数点ループ変数を計算する必要があります。では、なぜそれが良いのでしょうか?
ループが実行される回数は整数によって制御されるようになりました。これは、増分時に丸め誤差を持たないため、累積された丸めのために反復が多すぎたり少なすぎたりする可能性はありません。
時間変数は、1 回の乗算と 1 回の加算から計算されるようになりました。これにより、丸め誤差が発生する可能性がありますが、数千回の加算よりもはるかに少なくなります。その+1はどこから来たのですか?
範囲の両端を含めるには、+1 が必要です。が 20 でtMintMax
が 10 で、deltaT
2 だったとします。
望ましい時間は 10、12、14、16、18、20 であり、これは 5 ではなく合計 6 つの時間値です
( 20 - 10 ) / 2
。余分な 1 を追加して、正しい回数の 6 を取得します。
これを別の方法で見ると、nTimes が範囲内のデータ ポイントの数である場合、nTimes はデータ ポイントnTimes - 1
間のギャップの数になります。
例: interpolate.c は、ループ内で浮動小数点数を補間する手っ取り早い例で、クラスで 10 分で作成されました。これは良いコードの例ではありませんが、簡単な小さなプログラムを使用してテストしたり、遊んだり、この場合は新しい概念やなじみのない概念を実証したりする方法の例です。
この例では、次の3 つの方法を使用して、からf( x ) = x^3
の範囲で のステップで関数を内挿します。-1.0
4.0
0.5
定数 - エンドポイントでの入力の平均を取り、f(平均入力) を評価し、関数が範囲全体で一定であると仮定します。
線形 - エンドポイントで関数を評価し、その間のエンドポイント関数値の線形補間を使用します。
非線形 - 範囲にわたって関数入力を線形補間し、各評価点で、補間された入力の関数を評価します。