6

以下の一見リスクのないプログラムのように、フロートをループカウンターとして使用し、反復ごとに小数ずつインクリメント/デクリメントすることは安全ですか?もちろん、フロートを==演算子のオペランドとして使用するのはばかげていることは知っていますしかし、「通常の」目的で他の比較演算のオペランドとして浮動小数点数を使用することの何が問題なのですか? 「通常」とは、フロートは数値の正確な数値表現ではないかもしれませんが、0.000000001無関係のようなバリエーションではなく、ほとんどの場合無視できることを意味しますか? (たとえば、次のプログラムでは明らかではありません)

しかし、ここに私の懸念があります。表現が正確ではなく、5.0が実際には4.999999であるとします。したがって、反復ごとに0.5ずつ減分し続けると、最後の0との比較が偽になり、ループが終了する可能性があります。0.000001の差があり、現在の出力の最後の行は表示されません。あなたが私のドリフトを理解していることを願っています.私はどれほど間違っていますか?

#include<stdio.h>

int main(void)
{
float f;

for(f=5.0;f>=0;f-=0.5)
printf("%f\n",f);
}

出力:

5.000000
4.500000
4.000000
3.500000
3.000000
2.500000
2.000000
1.500000
1.000000
0.500000
0.000000
4

5 に答える 5

12

いいえ、あなたの質問にある理由から、安全ではありません。このことを考慮:

#include<stdio.h>

int main(void) {
  float f = 1.0;

  for(;f>0;f-=0.1)
     printf("%f\n",f);
  return 0;
}

この例は、によって初期化された場合、問題なく動作するようです。しかし、これを 3.0 に変更すると、すぐにもっと面白くなり始めます。f1.0

2.600000
2.500000
2.400001
...
0.000001

...悪名高い「オフバイワン」の失敗につながります。


>=の代わりに>?を使用しても安全だと思います。もう一度考えてみてください:

float f = 5.0;
for(;f>=1;f-=0.4)
  printf("%f\n",f);

...
3.400000
3.000000
2.599999
2.199999
1.799999
1.399999

...そして、1つずつオフにします(0.999991未満です)。

于 2013-05-16T19:30:26.460 に答える
10

開始値、デクリメント量、およびすべてのデクリメントの結果が、浮動小数点型によって提供される精度内でエラーなく表現できる限り、安全に使用できます。ここでの「エラーなし」とは、絶対エラーが 0 であることを意味し、非常に小さなエラーはエラーと見なされます。

あなたの場合、開始値5.0と減少量0.5はエラーなしで表すことができ4.54.03.5、 ...0.0も の 23 ビット精度内でエラーなしで表すことができますfloat。あなたの場合は安全です。

開始値が4000000.0で、減分量が0.00390625(2 -8float ) の場合、減分結果を 23 ビット精度の型でエラーなく表すことができないため、問題が発生します。金額を正確に表すことができます。

ただし、そのような場合に整数型の方が信頼性が高い場合、浮動小数点を使用しても意味がありません。上記の条件に該当するかどうかを調べるために、脳細胞を無駄にする必要はありません。

于 2013-05-16T19:43:19.900 に答える
6

浮動小数点表現の問題があるため、可能な限り浮動小数点よりも整数値を優先してください。

ループ制御として浮動小数点数を使用する代わりに、ロジックを修正して整数を使用します。

カウンターを減らす必要があります.5か? 開始値を 2 倍にして 1 減らします。

float f = 5.0;
int i = f * 2;

for(; i >= 0; i--)
    printf("%f\n", i / 2.0);

で減分する必要があります.1か?

float f = 5.0;
int i = f * 10;

for(; i >= 0; i--)
    printf("%f\n", i / 10.0);

これは、質問の例の簡単なアプローチです。確かに、唯一のアプローチや最も正しいアプローチではありません。より複雑な例では、ロジックを少し変更する必要がある場合があります。状況に合うものは何でも。

私のポイントは、表現によるエラーの導入を減らすために、可能な限り最後の瞬間まで実際の浮動小数点値の操作を延期することです。

于 2013-05-16T20:10:51.040 に答える
0

エンジニアや科学者は、浮動小数点値が値の範囲を少しずつ進む反復プログラムを頻繁に作成します。

たとえば、「時間」変数を 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 で、deltaT2 だったとします。

望ましい時間は 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.04.00.5

定数 - エンドポイントでの入力の平均を取り、f(平均入力) を評価し、関数が範囲全体で一定であると仮定します。

線形 - エンドポイントで関数を評価し、その間のエンドポイント関数値の線形補間を使用します。

非線形 - 範囲にわたって関数入力を線形補間し、各評価点で、補間された入力の関数を評価します。

于 2018-09-04T10:24:28.140 に答える