11

Cで自然対数を計算するアルゴリズムを実装していました.

double taylor_ln(int z) {
    double sum = 0.0;
    double tmp = 1.0;

    int i = 1;
    while(tmp != 0.0) {
        tmp = (1.0 / i) * (pow(((z - 1.0) / (z + 1.0)), i));
        printf("(1.0 / %d) * (pow(((%d - 1.0) / (%d + 1.0)), %d)) = %f\n", i, z, z, i, tmp);
        sum += tmp;
        i += 2;
    }

    return sum * 2;
}

print ステートメントで示されているように、tmp は最終的に 0.0 になりますが、ループは継続します。何が原因でしょうか?

私は Fedora 14 amd64 を使用しており、次のようにコンパイルしています。

clang -lm -o taylor_ln taylor_ln.c

例:

$ ./taylor_ln 2
(1.0 / 1) * (pow(((2 - 1.0) / (2 + 1.0)), 1)) = 0.333333
(1.0 / 3) * (pow(((2 - 1.0) / (2 + 1.0)), 3)) = 0.012346
(1.0 / 5) * (pow(((2 - 1.0) / (2 + 1.0)), 5)) = 0.000823
(1.0 / 7) * (pow(((2 - 1.0) / (2 + 1.0)), 7)) = 0.000065
(1.0 / 9) * (pow(((2 - 1.0) / (2 + 1.0)), 9)) = 0.000006
(1.0 / 11) * (pow(((2 - 1.0) / (2 + 1.0)), 11)) = 0.000001
(1.0 / 13) * (pow(((2 - 1.0) / (2 + 1.0)), 13)) = 0.000000
(1.0 / 15) * (pow(((2 - 1.0) / (2 + 1.0)), 15)) = 0.000000
(1.0 / 17) * (pow(((2 - 1.0) / (2 + 1.0)), 17)) = 0.000000
(1.0 / 19) * (pow(((2 - 1.0) / (2 + 1.0)), 19)) = 0.000000
(1.0 / 21) * (pow(((2 - 1.0) / (2 + 1.0)), 21)) = 0.000000
and so on...
4

5 に答える 5

12

浮動小数点比較は正確であるため10^-10、 と同じではありません0.0

10^-7基本的に、たとえば、書き出す小数点以下の桁数に基づいて、許容可能な違いと比較する必要があります。これは次のように実行できます。

while(fabs(tmp) > 10e-7)
于 2011-02-01T03:21:57.547 に答える
2

浮動小数点数を扱うときは、正確な等価演算を使用しないでください。あなたの番号は のように見えるかもしれませんが0おそらく のようなものです0.00000000000000000000001

フォーマット文字列%.50fの代わりに使用すると、これが表示されます。%f後者は小数点以下の桁数に賢明なデフォルトを使用しますが(あなたの場合は6)、前者はあなたがたくさん欲しいと明示的に述べています。

安全のために、デルタを使用して、次のように十分に近いかどうかを確認します。

if (fabs (val) < 0.0001) {
    // close enough.
}

明らかに、デルタは完全にニーズに依存します。お金の話なら、10 -5で十分かもしれません。あなたが物理学者なら、おそらくより小さい値を選ぶべきです。

もちろん、あなたが数学者なら、不正確さは十分に小さいものではありません:-)

于 2011-02-01T03:21:46.163 に答える
0

原因については多くの議論がありますが、別の解決策を次に示します。

double taylor_ln(int z)
{
    double sum = 0.0;
    double tmp, old_sum;
    int i = 1;
    do 
    {
        old_sum = sum;
        tmp = (1.0 / i) * (pow(((z - 1.0) / (z + 1.0)), i));
        printf("(1.0 / %d) * (pow(((%d - 1.0) / (%d + 1.0)), %d)) = %f\n",
               i, z, z, i, tmp);
        sum += tmp;
        i += 2;
    } while (sum != old_sum);
    return sum * 2;
 }

このアプローチは、tmp の値が減少するたびに合計すると具体的な違いが生じるかどうかに焦点を当てています。tmp が重要ではなくなり、おそらく結果を変更せずに早期に終了する 0 からのしきい値を計算するよりも簡単です。

比較的大きな数値と比較的小さな数値を合計すると、結果の有効桁数によって精度が制限されることに注意してください。対照的に、いくつかの小さなものを合計してから大きなものに追加すると、大きなものを少し上げるのに十分な場合があります. あなたのアルゴリズムでは、小さな tmp 値はとにかく互いに合計されていないため、それぞれが実際に合計に影響しない限り、累積はありません。したがって、上記のアプローチは、精度をさらに損なうことなく機能します。

于 2011-02-01T03:56:00.787 に答える
0

数値が「0.000000」と表示されているからといって、それが 0.0 に等しいとは限りません。数値の 10 進数表示は、double が格納できる精度よりも精度が低くなります。

アルゴリズムが 0 に非常に近い点に達している可能性がありますが、次のステップはほとんど動かないため、前と同じ値に丸められます。したがって、0 に近づくことはありません (単に無限ループ)。

一般に、浮動小数点数を==および と比較すべきではありません!=。それらが特定の小さな範囲 (通常イプシロンと呼ばれる) 内にあるかどうかを常に確認する必要があります。例えば:

while(fabs(tmp) >= 0.0001)

その後、0 にかなり近づくと停止します。

于 2011-02-01T03:22:37.097 に答える
0

print ステートメントは丸められた値を表示しており、可能な限り高い精度で出力していません。したがって、ループはまだゼロに達していません。

(そして、他の人が言及したように、丸めの問題により、実際には到達しない可能性があります。したがって、値を小さな制限と比較することは、0.0 と等しいかどうかを比較するよりも堅牢です。)

于 2011-02-01T03:30:13.573 に答える