5

「同じ値」の長さだけデルタ、たとえば1e-8だけずれている、0dから上への最初のdoubleを知りたいです。私はここで失敗しています。念のため、私は通常管理言語を使用していますが、これをCで実行しようとしています。助けてください。


#include <stdio.h>
#include <limits.h>
#define DELTA 1e-8

int main() {
    double d = 0; // checked, the literal is fine
    long i;
    for (i = 0L; i < LONG_MAX; i++) {
         d=i; // gcc does the cast right, i checked
         if (d-i > DELTA || d-i < -DELTA) {
              printf("%f", d);
              break;
         }
    }
}

問題は、diがiをdoubleにキャストするため、d == iであり、その差は常に0であるということだと思います。他にこれを正しく検出するにはどうすればよいですか?文字列を比較するよりも楽しいCキャストをお勧めします。永遠に取る。

答え:私たちが期待した通りです。2 ^ 53 + 1 = 9007199254740993は、標準のC / UNIX/POSIXツールによる最初の相違点です。彼のプログラムを提供してくれたpaxに感謝します。そして、私は数学が再び勝つと思います。

4

4 に答える 4

13

IEE754 の double の精度は 52 ビットです。これは、(少なくとも) 2 51までの数値を正確に格納できることを意味します。

long が 32 ビットの場合、0 から 2 31までの (正の) 範囲しか持たないため、double として正確に表現できない 32 ビット long はありません。64 ビットの長さの場合、(おおよそ) 2 52になるので、ゼロではなく、そのあたりから開始します。

次のプログラムを使用して、障害が発生し始めた場所を検出できます。以前のバージョンでは、連続して 2 倍になる数の最後の桁は {2,4,8,6} のシーケンスに従うという事実に依存していました。しかし、最終的には(bc)、最後の桁だけでなく数字全体をチェックするために、既知の信頼できるツールを使用することにしました。

これは double の実際の精度ではなく、アクションの影響を受ける可能性があることに注意してください (2 143sprintf()までの特定の数値で問題がなかったので、個人的にはそうは思いません)。

これはプログラムです:

#include <stdio.h>
#include <string.h>

int main() {
    FILE *fin;
    double d = 1.0; // 2^n-1 to avoid exact powers of 2.
    int i = 1;
    char ds[1000];
    char tst[1000];

    // Loop forever, rely on break to finish.
    while (1) {
        // Get C version of the double.
        sprintf (ds, "%.0f", d);

        // Get bc version of the double.
        sprintf (tst, "echo '2^%d - 1' | bc >tmpfile", i);
        system(tst);
        fin = fopen ("tmpfile", "r");
        fgets (tst, sizeof (tst), fin);
        fclose (fin);
        tst[strlen (tst) - 1] = '\0';

        // Check them.
        if (strcmp (ds, tst) != 0) {
            printf( "2^%d - 1 <-- bc failure\n", i);
            printf( "   got       [%s]\n", ds);
            printf( "   expected  [%s]\n", tst);
            break;
        }

        // Output for status then move to next.
        printf( "2^%d - 1 = %s\n", i, ds);
        d = (d + 1) * 2 - 1;  // Again, 2^n - 1.
        i++;
    }
}

これは次の時点まで続きます。

2^51 - 1 = 2251799813685247
2^52 - 1 = 4503599627370495
2^53 - 1 = 9007199254740991
2^54 - 1 <-- bc failure
   got       [18014398509481984]
   expected  [18014398509481983]

これは、失敗すると予想していた場所です。

余談ですが、私はもともと 2 nの形式の数値を使用していましたが、次のようになりました。

2^136 = 87112285931760246646623899502532662132736
2^137 = 174224571863520493293247799005065324265472
2^138 = 348449143727040986586495598010130648530944
2^139 = 696898287454081973172991196020261297061888
2^140 = 1393796574908163946345982392040522594123776
2^141 = 2787593149816327892691964784081045188247552
2^142 = 5575186299632655785383929568162090376495104
2^143 <-- bc failure
   got       [11150372599265311570767859136324180752990210]
   expected  [11150372599265311570767859136324180752990208]

double のサイズは 8 バイトです ( でチェックsizeof)。"1000..."これらの数値は、2 進数ではるかに長く表現できる2 進数形式であることが判明しました。そこで、より良いビット パターン (すべて 1 ビット) を得るために2 n -1の使用に切り替えました。

于 2009-04-09T03:26:41.667 に答える
2

ダブルにキャストされたときに最初に「間違った」ロングは1e-8だけずれず、1ずれます。ダブルが仮数にロングを収めることができる限り、それは正確に表現されます。

ダブルが精度とオフセットのビット数を正確に忘れていますが、それはそれが表すことができる最大サイズを教えてくれます。間違っている最初の長いものは、バイナリ形式10000 ...である必要があります。したがって、1から始めて左シフトすることで、はるかに速く見つけることができます。

ウィキペディアによると、仮数の52ビットは、暗黙の開始1をカウントしていません。つまり、別の値にキャストされる最初の長さは2^53です。

于 2009-04-09T02:59:49.000 に答える
1

この議論で Fortran 95 とその後継について言及するのをためらっていますが、Fortran は 1990 年の標準以降、特定の REAL について表現可能な REAL 間の違いを示す SPACING 組み込み関数を提供していることに言及します。SPACING(X) > DELTA のときに停止して、これに対してバイナリ検索を実行できます。関心のあるもの (IEEE754 標準である可能性が高い) と同じ浮動小数点モデルを使用するコンパイラの場合、同じ結果が得られるはずです。

于 2009-05-17T02:10:18.513 に答える
0

一方で、doubleは(範囲内の)すべての整数を正確に表すことができると思いました。

そうでない場合は、iとdの両方をどちらよりも正確なものにキャストする必要があります。おそらく、長いダブルが機能します。

于 2009-04-09T02:58:28.613 に答える