无限循环heisenbug:如果我添加打印输出它会退出

Ano*_*use 8 c++ floating-point infinite-loop

这是我的源代码:

#include <iostream>
#include <cmath>

using namespace std;

double up = 19.0 + (61.0/125.0);
double down = -32.0 - (2.0/3.0);
double rectangle = (up - down) * 8.0;

double f(double x) {
    return (pow(x, 4.0)/500.0) - (pow(x, 2.0)/200.0) - 0.012;
}

double g(double x) {
    return -(pow(x, 3.0)/30.0) + (x/20.0) + (1.0/6.0);
}

double area_upper(double x, double step) {
    return (((up - f(x)) + (up - f(x + step))) * step) / 2.0;
}

double area_lower(double x, double step) {
    return (((g(x) - down) + (g(x + step) - down)) * step) / 2.0;
}

double area(double x, double step) {
    return area_upper(x, step) + area_lower(x, step);
}

int main() {
    double current = 0, last = 0, step = 1.0;

    do {
        last = current;
        step /= 10.0;
        current = 0;

        for(double x = 2.0; x < 10.0; x += step) current += area(x, step);

        current = rectangle - current;
        current = round(current * 1000.0) / 1000.0;
        //cout << current << endl;
    } while(current != last);

    cout << current << endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

它的作用是计算曲线之间的面积.main()中有一个循环 - 其目的是在3个小数位内尽可能精确地计算值.

它没用.为了调试,我添加了唯一注释的行.我想知道循环内部发生了什么.

//cout << current << endl;
Run Code Online (Sandbox Code Playgroud)

当线路在那里 - 当我取消注释它 - 一切正常.当它不是 - 循环似乎是无限的.

神圣的编译器,为什么?

这不是我所知道的不精确浮点数的问题.我在其中输出当前值,一切都完成了4次循环内容的重复.

Jos*_*ley 22

@ Skizz的评论给出了可能的问题,但要详细说明:

浮点数学很棘手,特别是经常出现舍入误差.诸如1/1000.0(您的round调用结果)之类的数字无法以浮点精确表示.

更复杂的是,一方面速度与另一方面一致,直观的结果之间存在权衡.例如,Intel处理器的FPU以80位扩展精度格式存储值,而C/C++ double通常为64位.为了提高性能,编译器可能会将值保留在FPU中,作为80位临时值,即使这会产生与将其截断为64位时所得到的结果不同的结果.

启用调试语句后,current可能会将其存储到内存中,将其截断为64位,这样可以直接与之比较last.

禁用调试语句后,current很可能存储在FPU寄存器中的80位值,因此它永远不会相等last,只要last是64位值并且它们都试图存储不精确的浮点表示x/1000.0.

解决方案是使用浮点比较和一些允许的错误(因为直接检查与浮点的相等性几乎不是一个好主意).

补充说明:我没有查看汇编输出以验证是否是这种情况; 如果你愿意,你可以自己做.如果我启用优化,我只能重现问题.您可以通过调整编译器标志来选择一致性而不是速度来"修复"该错误,但正确的解决方案是使用不精确的比较而不是直接检查相等性.