gcc编译器优化影响浮点比较的结果 解决方法更新

问题

在使用自动CI测试时,我发现了一些代码,如果将gcc的优化设置为-O2,则会破坏代码。如果双精度值在任一方向都超过阈值,则代码应增加一个计数器。

解决方法

转到-O1或使用-ffloat-store选项可以解决此问题。

示例

这是一个显示相同问题的小例子。 每当update()的序列超过阈值0.03时,*pNextState * 1e-6函数应返回true。 我使用了按引用调用,因为这些值是完整代码中大型结构的一部分。

使用<>=的想法是,如果序列恰好命中了该值,则函数这次应返回1,而在下一个周期返回0。

test.h:

extern int update(double * pState,double * pNextState);

test.c:

#include "test.h"

int update(double * pState,double * pNextState_scaled) {
    static double threshold = 0.03;
    double oldState = *pState;
    *pState = *pNextState_scaled * 1e-6;

    return oldState < threshold && *pState >= threshold;
}

main.c:

#include <stdio.h>
#include <stdlib.h>

#include "test.h"

int main(void) {

    double state = 0.01;
    double nextState1 = 20000.0;
    double nextState2 = 30000.0;
    double nextState3 = 40000.0;

    printf("%d\n",update(&state,&nextState1));
    printf("%d\n",&nextState2));
    printf("%d\n",&nextState3));

    return EXIT_SUCCESS;
}

使用gcc至少为-O2的输出为:

0
0
0

将gcc与-O1,-O0或-ffloat-store一起使用会产生所需的输出

0
1
0

据我了解,调试产生的问题是,如果编译器优化了堆栈上的局部变量oldstate,而是与浮点寄存器中的中间结果进行比较,则浮点寄存器的精度更高(80位),值为*pState比阈值小一点。 如果用于比较的值以64位精度存储,则逻辑不会错过阈值。由于乘以1e-6,结果可能存储在浮点寄存器中。

您会认为这是gcc错误吗? 铛不显示问题。

我在Intel Core i5,Windows和msys2上使用gcc版本9.2.0。

更新

对我来说很明显浮点比较并不精确,我认为以下结果是有效的:

0
0
1

这个想法是,如果(*pState >= threshold) == false在一个周期内,那么在后续调用oldstate = *pState中将相同的值((*pState < threshold))与相同的阈值进行比较就必须为真。

sonybmw2007 回答:gcc编译器优化影响浮点比较的结果 解决方法更新

[免责声明:这是一个通用的,从船上射击的答案。浮点问题可能很微妙,我没有仔细分析这一问题。有时,像这样的 看起来可疑的代码毕竟可以移植并可靠地工作,并且按照公认的答案,似乎就是这种情况。尽管如此,通用答案仍然是一般情况。]

我认为这是测试用例中的错误,而不是gcc中的错误。这听起来像是一个经典的代码示例,相对于精确的浮点数相等,它不必要地脆弱。

我建议之一:

  • 重写测试用例,或者也许
  • 删除测试用例。

推荐:

  • 通过切换编译器来解决它
  • 使用不同的优化级别或其他编译器选项来解决它
  • 提交编译器错误报告[尽管在这种情况下,它似乎是一个 编译器错误,尽管由于已经提交,因此无需提交]
,

我已经分析了您的代码,我认为这是符合标准的声音,但是您受到gcc bug 323的打击,您可能会在gcc FAQ中找到有关此信息的更多信息。

在存在gcc bug的情况下修改函数并使之稳定的一种方法是存储以下事实:存储先前状态低于阈值的事实,而不是(或另外)存储该状态。像这样:

int update(int* pWasBelow,double* pNextState_scaled) {
    static double const threshold = 0.03;
    double const nextState = *pNextState_scaled * 1e-6;
    int const wasBelow = *pWasBelow;
    *pWasBelow = nextState < threshold;
    return wasBelow && !*pWasBelow;
}

请注意,这不保证可复制性。您可能会在一种设置中获得0 1 0,而在另一种设置中获得0 0 1,但迟早会发现转换。

,

我将其作为答案,因为我认为我无法在评论中编写真实的代码,但@SteveSummit应该得到赞誉-如果没有他们的上述评论,我将不可能找到它。

一般建议是:不要对浮点值进行精确比较,这似乎就是这样做的结果。如果计算值几乎完全是0.03,但是由于内部表示或优化的原因,它的值与实际值相差很小,而又不是完全,那么它将看起来像是阈值交叉。

因此,可以通过添加一个epsilon来解决此问题,该ε可以使阈值接近阈值而无需考虑跨越阈值。

int update(double * pState,double * pNextState_scaled) {
    static const double threshold    = 0.03;
    static const double close_enough = 0.0000001f; // or whatever
    double oldState = *pState;
    *pState = *pNextState_scaled * 1e-6;

    // if either value is too close to the threshold,it's not a crossing
    if (fabs(oldState - threshold) < close_enough) return 0;
    if (fabs(*pState  - threshold) < close_enough) return 0;

    return oldState < threshold && *pState >= threshold;
}

我想您必须了解您的应用程序才能知道如何适当地调整此值,但是coupla比您要比较的值小几个数量级。

本文链接:https://www.f2er.com/3151125.html

大家都在问