无限循环heisenbug:如果我添加打印输出,它就会退出
Infinite loop heisenbug: it exits if I add a printout
这是我的源代码:
#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;
}
它所做的是计算曲线之间的面积。 main() 中有一个循环 - 它的目的是计算值尽可能精确到小数点后 3 位。
没用。为了调试,我添加了唯一注释的行。我想知道循环内部发生了什么。
//cout << current << endl;
当该行出现时 - 当我取消注释时 - 一切正常。如果不是 - 循环似乎是无限的。
神圣的编译器,为什么?
这不是我知道的浮点数不精确的问题。一切都在循环内容的 4 次重复内完成 当 我在其中输出当前值时
@Skizz的评论给出了可能的问题,但要详细说明:
浮点运算很棘手,尤其是经常会出现舍入错误。诸如 1/1000.0(您的 round
调用的结果)之类的数字无法用浮点数精确表示。
更复杂的是,一方面需要在速度与另一方面一致、直观的结果之间进行权衡。例如,Intel 处理器的 FPU 以 80 位扩展精度格式存储值,而 C/C++ double
通常是 64 位。为了提高性能,编译器可能会将值作为 80 位临时值保留在 FPU 中,即使这可能会产生与将它们截断为 64 位时得到的结果不同的结果。
启用调试语句后,current
可能会存储到内存中,将其截断为 64 位,这允许与 last
直接比较。
禁用调试语句后,current
可能是一个存储在 FPU 寄存器中的 80 位值,因此它永远不会 等于 last
,只要 last
是一个 64 位值,并且它们都试图存储 x/1000.0
.
的不精确浮点表示
解决方案是使用 floating point comparison with some allowed error(因为直接检查浮点相等性几乎不是一个好主意)。
进一步说明:我没有查看汇编输出来验证情况是否如此;如果你愿意,你可以自己做。如果启用优化,我只能重现问题。您可以 "fix" 通过调整编译器标志以选择一致性而不是速度来解决错误,但正确的解决方案是使用不精确的比较而不是直接检查是否相等。
而不是
while(current != last);
使用类似的东西:
while(fabs(current - last) > tolerence);
其中 tolerance
可以是一个小数字,例如 1.0e-6
。
这是我的源代码:
#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;
}
它所做的是计算曲线之间的面积。 main() 中有一个循环 - 它的目的是计算值尽可能精确到小数点后 3 位。
没用。为了调试,我添加了唯一注释的行。我想知道循环内部发生了什么。
//cout << current << endl;
当该行出现时 - 当我取消注释时 - 一切正常。如果不是 - 循环似乎是无限的。
神圣的编译器,为什么?
这不是我知道的浮点数不精确的问题。一切都在循环内容的 4 次重复内完成 当 我在其中输出当前值时
@Skizz的评论给出了可能的问题,但要详细说明:
浮点运算很棘手,尤其是经常会出现舍入错误。诸如 1/1000.0(您的 round
调用的结果)之类的数字无法用浮点数精确表示。
更复杂的是,一方面需要在速度与另一方面一致、直观的结果之间进行权衡。例如,Intel 处理器的 FPU 以 80 位扩展精度格式存储值,而 C/C++ double
通常是 64 位。为了提高性能,编译器可能会将值作为 80 位临时值保留在 FPU 中,即使这可能会产生与将它们截断为 64 位时得到的结果不同的结果。
启用调试语句后,current
可能会存储到内存中,将其截断为 64 位,这允许与 last
直接比较。
禁用调试语句后,current
可能是一个存储在 FPU 寄存器中的 80 位值,因此它永远不会 等于 last
,只要 last
是一个 64 位值,并且它们都试图存储 x/1000.0
.
解决方案是使用 floating point comparison with some allowed error(因为直接检查浮点相等性几乎不是一个好主意)。
进一步说明:我没有查看汇编输出来验证情况是否如此;如果你愿意,你可以自己做。如果启用优化,我只能重现问题。您可以 "fix" 通过调整编译器标志以选择一致性而不是速度来解决错误,但正确的解决方案是使用不精确的比较而不是直接检查是否相等。
而不是
while(current != last);
使用类似的东西:
while(fabs(current - last) > tolerence);
其中 tolerance
可以是一个小数字,例如 1.0e-6
。