【问题标题】:Calculations with long double in clang – Compiler bug?clang 中的 long double 计算 – 编译器错误?
【发布时间】:2013-06-27 16:08:18
【问题描述】:

这是 clang 的错误吗?

这会打印出最大的双精度值:

long double a = DBL_MAX;
printf("%Lf\n", a);

它是:

179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.000000 P>

这会打印出最大的 long double 值:

long double a = LDBL_MAX;
printf("%Lf\n", a);

它是:

/* … bigger, but not displayed here. For a good reason. ;-) */

这很清楚。

但是当我使用算术表达式,即编译时可计算的初始化器时,我得到了一个令人惊讶的结果:

long double a = 1.L + DBL_MAX + 1.L;
printf("%Lf\n", a); 

这仍然打印出 DBL_MAX 而不是 DBL_MAX + 2!?

如果在运行时进行计算也是一样的:

long double b = 2.L;
long double a = DBL_MAX;
printf("%Lf\n", a+b);

还是 DBL_MAX。

$ clang --version
Apple clang version 4.1 (tags/Apple/clang-421.11.66) (based on LLVM 3.1svn)
Target: x86_64-apple-darwin12.4.0
Thread model: posix

【问题讨论】:

  • 前16位左右后,余数无意义; double 类型根本不再存储任何数字。

标签: c clang


【解决方案1】:

不是错误。 clang/x86_64 中的long double 具有 64 位精度,并且结果会四舍五入以适应该格式。

如果我们使用十六进制而不是二进制,这一切都会更清楚。 DBL_MAX 是:

0xfffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

因此,1.L + DBL_MAX 的精确数学结果是:

0xfffffffffffff800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001

...但这不能表示为long double,因此计算结果四舍五入到最接近的可表示long double,即DBL_MAX;添加 1 不会(也不应该)更改值。

(它向下取整而不是向上取整,因为下一个更大的可表示数字是

0xfffffffffffff801000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

这比 DBL_MAX 与数学上精确的结果相差很多)。

【讨论】:

  • 如果clang中的long double只有64位,为什么LDBL_MAX比DBL_MAX大?
  • @AndrewW:64 位精度;它是 80 位类型。 (而 double 是 64 位类型,精度为 53 位)。
【解决方案2】:

IEE754 浮点 double 的尾数为 53 位宽(52 个物理位 + 1 个隐式位)。这意味着double 可以准确地表示-2^53...+2^53 范围内的连续整数(即从-9007199254740992+9007199254740992)。之后,该类型就不能再精确地表示连续的整数了。相反,该类型只能表示 even 整数值。根据某些特定于实现的规则,任何 odd 值都将四舍五入为相邻的偶数值。因此,完全可以预料,在double 内将1 添加到9007199254740992 可能由于四舍五入而不会产生任何结果。从该限制开始,您必须至少添加 2 才能看到值的变化(直到您达到添加 2 将不再有任何效果并且您必须添加至少 @ 987654331@等)。

如果long double 在您的平台上大于double,则相同的逻辑适用。在 x86 上,long double 可能指的是带有 64 位尾数的硬件 80 位浮点类型。这意味着即使使用这种类型,您精确表示连续整数的范围也仅限于 -2^64...+2^64

DBL_MAX 的值远、FAR、FAAAAR! 超出该范围。这意味着尝试将1 添加到DBL_MAX 不会对该值产生任何影响。添加2 也不会产生任何影响。 41024、甚至4294967296 也不会。您必须在2^960 区域(实际上是nextafter(2^959))中添加一些内容,以便对以80 位long double 格式存储的DBL_MAX 值产生影响。

【讨论】:

  • 是的,是的,是的,我笨。当然,我知道,但不记得了。 +1
【解决方案3】:

这是预期的行为。

long double a = 1.L + DBL_MAX + 1.L;

long double 类型是浮点数:它的精度有限。大多数操作的结果都四舍五入到最接近的可表示值。

What Every Programmer Should Know About Floating-Point Arithmetic

【讨论】:

  • 非常感谢。我只是忘记了这一点。 +1
【解决方案4】:

一个技术上不太正确的答案,希望能有所帮助:

数字由符号、指数和分数表示。

在此页面上,提供了有关 C 数据类型的信息 (https://en.wikipedia.org/wiki/C_data_types)。该图表声称 long double 不能保证是比 double “更大”的数据类型;但是,从 C99 开始,如果它存在于目标架构中,则可以保证这一点(附录 F IEC 60559 浮点算法)。您来自DBL_MAXLDBL_MAX 的结果表明,在您的实现中,它实际上使用了更多位。

这就是正在发生的事情:

您有以下格式的号码:

加倍是

总之,你有这个 80 位表示 (https://en.wikipedia.org/wiki/Extended_precision)

您可以将 double 类型放入 long double 类型中,这样不会导致任何问题。但是,请注意小数点是“浮动的”(因此得名),并非数字中的所有数字都被表示。计算机代表最高有效数字,然后是指数(例如,就像我写 1234567 E 234 一样,请注意我没有写该数字的所有 234 位数字)。当您尝试向其添加 1 时,该位的数字没有被表示(由于指数的大小),因此在四舍五入后将被忽略。

有关更多详细信息,请在此处阅读浮点 (https://en.wikipedia.org/wiki/Double_precision_floating-point_format)

【讨论】:

    猜你喜欢
    • 2013-09-27
    • 1970-01-01
    • 2010-12-26
    • 2017-10-17
    • 1970-01-01
    • 1970-01-01
    • 2018-03-24
    • 1970-01-01
    • 2016-11-26
    相关资源
    最近更新 更多