【问题标题】:Decimal Precision Lost after 15th Digit - Bad PI第 15 位后十进制精度丢失 - 错误 PI
【发布时间】:2014-06-30 08:26:20
【问题描述】:

尝试打印超过 15 个小数位的 PI 会导致小数点后 15 位打印不正确。尽管分配了 30 个正确的十进制值,并且尽管使用 long double 来保存该值,但仍然如此。下面的测试用例清楚地显示了错误。

这是出乎意料的。如果数字中有任何错误,我不希望在耗尽 IEEE-754 有效位后的第 25 位之前看到任何错误。这里有什么规则可以解释我无法打印我刚刚分配给下面 sPI 的 30 位数字。这也会影响打印math.h 中包含的M_PI 表示的能力。

#include <stdio.h>

int
main (void) {

    // static PI approximation (glibc man 1.17)
    long double sPI = 3.14159265358979323846264338327;
    char strPI[]   = "3.14159265358979323846264338327";

    printf ("\n %s (strPI - string - correct)\n", strPI);
    printf (" %.29Lf (sPI - long double - INCORRECT)\n\n", sPI);

    return (0);
}

输出:

3.14159265358979323846264338327 (strPI - string - correct)
3.14159265358979311599796346854 (sPI - long double - INCORRECT)
                 ^^^^^^^^^^^^^^

大概,这个十进制错误将适用于任何十进制精度大于 16 的十进制数。当打印为字符串时,PI 打印得很好(显然),但是当打印为双精度时——小数点精度在小数点后 15 位后分解。是什么原因造成的?

非常有趣,正如建议在浮点文字末尾添加 L 确实有帮助:

 3.14159265358979323846264338327 (strPI - string - correct)
 3.14159265358979323851280895941 (sPI - long double - INCORRECT)

这提供了 3 个额外的小数精度。为清楚起见,这是在旧的 AMD Phenom X4 9850 上运行在 Linux 3.14.1 内核和 gcc 4.8.2 上的。(基于 AMD Turion 的笔记本电脑和 Intel P4 给出相同的结果)

尝试将quadmath.h__float128 类型分配给sPI,结果与long double 相同。还有几个数字可用,但精度仍然在第 19 位出现故障:

3.14159265358979323846264338327 (strPI - string - correct)
3.1415926535897932385128089594061862 (sPI - long double - INCORRECT)

【问题讨论】:

  • 看起来 long double 仍然是 64 位双精度。并非所有编译器都支持 80 位扩展精度,支持的编译器可能需要编译器开关。
  • 你还没有说你使用的是什么编译器。许多编译平台提供 80 位 long double(Linux/x86 上的 GCC,Linux/x86 上的 Clang,……),但更多的编译平台将 long double 定义为与 double 相同的类型(Visual Studio)。注意:即使long double 是 80 位扩展精度浮点格式,你也不会得到 30 个 π 小数位数,更像是 19-21。
  • 您意识到将doublelong double 常量分配给任何更精确的东西不会让您满意,对吧?使用 l 足以满足 long double 并从字符串中解析常量,如果您要过去 long double

标签: c math double-precision


【解决方案1】:

您没有将“long double”值存储到变量中,而是将默认的double 存储到变量中。编译器读取浮点值,将其存储为默认类型,然后仅将其转换为 long double “之后”。当您将其值与“通常”分配的 double 进行比较时,您可以看到这一点。

要提示编译器将常量存储为long double,请在浮点常量的末尾添加修饰符后缀Ll

例子:

#include <stdio.h>

int main (void)
{
    // static PI approximation (glibc man 1.17)
    double sPI_d     = 3.14159265358979323846264338327;
    long double sPI  = 3.14159265358979323846264338327;
    long double sPI_L= 3.14159265358979323846264338327L;
    char strPI[]     ="3.14159265358979323846264338327";

    printf ("\n %s (strPI - string - correct)\n", strPI);
    printf (" %.29f (sPI - double)\n", sPI_d);
    printf (" %.29Lf (sPI - long double - INCORRECT)\n", sPI);
    printf (" %.29Lf (sPI - long double - BETTER)\n", sPI_L);

    return 0;
}

输出:

 3.14159265358979323846264338327 (strPI - string - correct)
 3.14159265358979311599796346854 (sPI - double)
 3.14159265358979311599796346854 (sPI - long double - INCORRECT)
 3.14159265358979323851280895941 (sPI - long double - BETTER)

另请参阅What is the precision of long double in C++?——long double 的有效数字不能超过 18 位左右。

【讨论】:

  • 感谢 Johnware - 我还将调查 quadmath.h__float128 以查看是否可以使用 80 位浮点获得另一个或两个位置。
  • quadmath.h 看起来很有趣!沼泽标准math.h 定义了很多数字,但似乎铸造(long double)M_PI 来得太晚了,因为我仍然得到它的双重价值。
【解决方案2】:

双精度浮点值表示为 64 位值,具有 15-16 位十进制数字的有限精度。因此,long double 似乎在您的系统上实现为 64 位双精度。

如果您不熟悉这些问题,我强烈推荐 David Goldberg 的永恒文章What Every Computer Scientist Should Know About Floating-Point Arithmetic

【讨论】:

  • 谢谢大卫。那是我在搜索中找不到的文件。我被困在阅读 FXT、MPFR、GnuMP 的附录,并试图找到解释它的页面,但唉。所以我们在现实世界中得到 15 位数字,除非使用多精度库。
  • 有些系统的long double 可能是 80 位,而其他系统是 128 位。其他的,就像你的一样,有 64 位 long double。但是像long double 这样的类型总是有有限的精度。要获得更高的精度,您需要专门的库。
  • 这也解释了为什么在将PI 的数字计算到数百万个位置的算法中输出数字的唯一方法是一次一个数字。起初这也令人困惑。我有一点 sn-p 代码可以正确地将PI 打印为所需的任何十进制数字——但它一次只打印一个。
猜你喜欢
  • 2012-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多