5

我想知道从 0d 向上的第一个双精度数,它偏离“相同值”的 long 某个增量,例如 1e-8。我在这里失败了。尽管我通常使用托管语言以防万一,但我正在尝试在 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 中的双精度为 52 位,这意味着它们可以准确地存储(至少)高达 2 51的数字。

如果您的 long 是 32 位的,它们将只有(正)范围 0 到 2 31,因此没有 32 位 long 不能完全表示为 double。对于 64 位长度,它将(大约)2 52所以我会从那里开始,而不是从零开始。

您可以使用以下程序来检测故障开始发生的位置。在早期的版本中,我依赖于这样一个事实,即连续翻倍的数字中的最后一位数字遵循序列 {2,4,8,6}。但是,我最终选择使用已知的可信工具(bc)来检查整个数字,而不仅仅是最后一个数字。

请记住,这可能会受到动作的影响,sprintf()而不是双打的真实准确性(我个人不这么认为,因为它在某些数字高达 2 143时没有问题)。

这是程序:

#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 n -1 来获得更好的位模式:全一位。

于 2009-04-09T03:26:41.667 回答
2

当转换为 double 时,第一个“错误”的 long 不会偏离 1e-8,而是偏离 1。只要 double 可以在其有效数字中容纳 long,它就会准确地表示它。

我完全忘记了双精度与偏移量有多少位,但这会告诉你它可以代表的最大大小。第一个 long 错误应该是二进制形式 10000...,所以你可以从 1 开始并左移更快地找到它。

维基百科说有效数字中有 52 位,不包括隐含的起始 1。这应该意味着要转换为不同值的第一个 long 是 2^53。

于 2009-04-09T02:59:49.000 回答
1

尽管在本次讨论中我不愿提及 Fortran 95 和后续版本,但我会提到自 1990 年标准以来的 Fortran 提供了一个 SPACING 内在函数,它告诉您可表示的 REAL 与给定的 REAL 之间的区别是什么。您可以对此进行二进制搜索,当 SPACING(X) > DELTA 时停止。对于使用与您感兴趣的相同浮点模型的编译器(可能是 IEEE754 标准),您应该得到相同的结果。

于 2009-05-17T02:10:18.513 回答
0

顺便说一句,我认为双精度数可以准确地表示所有整数(在它们的范围内)。

如果不是这种情况,那么您将希望将 i 和 d 都转换为比它们中的任何一个都更精确的东西。也许一个长的双倍会起作用。

于 2009-04-09T02:58:28.613 回答