【问题标题】:-Ofast produces incorrect code while using long double-Ofast 在使用 long double 时产生不正确的代码
【发布时间】:2020-09-22 05:05:09
【问题描述】:
#include <cstdio>

int main(void)
{
    int val = 500;
    printf("%d\n", (int)((long double)val / 500));
    printf("%d\n", (int)((long double)500 / 500));
}

显然它应该输出1 1。但是如果你用-Ofast编译,就会输出0 1,为什么?

如果你把500改成其他值(比如400),用-Ofast编译,还是会输出1 1

带有-Ofast的编译器资源管理器:https://gcc.godbolt.org/z/YkX7fB

似乎这条线导致了问题。

【问题讨论】:

  • val 更改为小于 500 会得到 0。但我不确定您是否假设 (int)(500./500) 显然是 1。
  • 在第一个中,也许是快速优化导致编译器生成乘以0.001999999999 而不是除法的代码。在第二个中,它可能根本不会在运行时计算。
  • @cigien 这不是重点。关键是printf 语句应该输出相同的值,因为它们本质上是(int)(500./500)
  • 不必必须输出相同的值。第二个允许编译器生成puts("1");
  • 只是一个猜测,但我认为启用-ffast-math 的编译器可以将/500 计算为*(1.0/500)。如果这个值被四舍五入,那么乘以(long double)500可能会略低于1,并且转换为int总是会截断,最终会产生0

标签: c++ c compilation g++ compiler-optimization


【解决方案1】:

-Ofast

无视严格的标准合规性。 -Ofast 启用所有 -O3 优化。它还支持并非对所有符合标准的程序都有效的优化。它打开了-ffast-math-fallow-store-data-races 和 Fortran 特定的 [...]

-ffast-math

设置选项-fno-math-errno-funsafe-math-optimizations-ffinite-math-only-fno-rounding-math-fno-signaling-nans-fcx-limited-range-fexcess-precision=fast

此选项导致定义预处理器宏__FAST_MATH__

-Ofast 之外的任何-O 选项都不会启用此选项,因为它可能导致依赖于数学函数的IEEE 或ISO 规则/规范的精确实现的程序的错误输出。但是,对于不需要这些规范保证的程序,它可能会产生更快的代码。

结论:不要使用-ffast-math,除非你愿意像现在这样得到惊喜。

【讨论】:

    【解决方案2】:

    使用-Ofast,启用-ffast-math,这可能会导致以不同且更快的方式计算某些操作。在您的情况下,(long double)val / 500) 可以计算为(long double)val * (1.0L / 500))。当您比较 -O2-Ofast 的以下功能时,可以在生成的程序集中看到这一点:

    long double f(long double a)
    {
        return a / 500.0L;
    }
    

    -O2生成的程序集涉及fdiv指令,-Ofast生成的程序集涉及fmul指令,见https://gcc.godbolt.org/z/58VHxb

    接下来,1/500,即 0.002,不能完全用 long double 表示。因此,会发生一些舍入,并且在您的情况下,这种舍入似乎是向下的。这可以通过以下表达式进行检查:

    500.0L * (1.0L / 500.0L) < 1.0L
    

    评估为true:https://gcc.godbolt.org/z/zMcjxJ。因此,精确存储的乘数是 0.002 - 一些非常小的增量

    最后,乘法的结果是 500 * (0.002 - delta) = 1 - 某个小值。而当这个值转换成int时,它会被截断,因此int的结果是0。

    【讨论】:

      【解决方案3】:

      即使显示的程序 sn-p 有一个“问题”,它仍然是使用浮点数的错误方法。

      您或多或少地询问程序浮点数是否具有“精确值”——在本例中为“1”。好的,更准确地说 - 如果值是 '= 1' 对于“大约” 1 的值 - 那么正好在两个答案的划分限制附近。但是正如其他人已经写过的那样(或者很容易在维基百科中找到,......)浮点数的精度有限。所以这样的偏差可以而且将会发生。

      因此,得出一个结论:在进行浮点到整数的转换时,您应该始终使用舍入,即 '(int) round (floating_point_value)'。

      附言。与其他人可能会说或推荐的相反 - 我认为 -ffast-math 计算没有任何问题。唯一的“问题”是在让某个程序在不同计算机上运行后(按位)比较某个程序的结果。

      我使用 -ffast-math(实际上是 -Ofast)进行所有(科学)计算。但到目前为止,这从来都不是问题——因为我预计浮点数会有一些舍入错误(这是真的,无论是否使用 -ffast-math)——但据我所知,仅此而已。由于我通常使用 64 位浮点数(双精度),这意味着,计算精确到大约 15 到 17 位十进制数字 - 最后(少数)是由这些不准确造成的 - 仍然给我很多“准确”数字 - 比如说- 超过 13 个,取决于我的计算有多复杂。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-18
        • 2015-06-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多