【发布时间】:2023-03-27 17:11:02
【问题描述】:
我最近分析了一段用 VS2005 编译的旧代码,因为“调试”(无优化)和“发布”(/O2 /Oi /Ot 选项)编译中的数值行为不同。 (简化的)代码如下所示:
void f(double x1, double y1, double x2, double y2)
{
double a1, a2, d;
a1 = atan2(y1,x1);
a2 = atan2(y2,x2);
d = a1 - a2;
if (d == 0.0) { // NOTE: I know that == on reals is "evil"!
printf("EQUAL!\n");
}
如果使用相同的值对(例如f(1,2,1,2))调用函数f,预计会打印“EQUAL”,但这并不总是发生在“发布”中。事实上,编译器已经像d = a1-atan2(y2,x2) 一样优化了代码,并完全删除了对中间变量a2 的赋值。此外,它利用了第二个atan2() 的结果已经在FPU 堆栈上这一事实,因此在FPU 上重新加载a1 并减去这些值。问题是 FPU 以扩展精度(80 位)工作,而 a1 是“仅”双精度(64 位),因此将第一个 atan2() 的结果保存在内存中实际上已经失去了精度。最终,d 包含扩展精度和双精度之间的“转换错误”。
我完全知道应该避免使用 float/double 的身份(== 运算符)。我的问题不是关于如何检查双打之间的接近度。我的问题是应该如何考虑对局部变量的“合同”分配。从我的“幼稚”观点来看,赋值应该强制编译器将值转换为变量类型表示的精度(在我的例子中是双精度)。如果变量是“浮动的”怎么办?如果它们是“int”(奇怪但合法)怎么办?
那么,简而言之,C 标准对这些情况有什么看法?
【问题讨论】:
-
我认为该标准没有承诺库中包含的函数的浮点返回。但这是我对这个主题的非专业理解,所以不是很有帮助。
-
默认情况下,Visual Studio 将精度设置为“精确”,这允许它进行此类优化。您可以尝试将其设置为严格,看看会发生什么。
-
如果一切都失败了,将所有中间体转换为 (double) 应该可以解决问题。至少当我试图为一个非常具体的操作获取特定于 IEEE 的行为并且我不想打开全局 fp:strict 时,它至少有效。
-
如果您要摆脱 x87 的超额精度,通常会有一个特定的编译标志;在 gcc 上有
-fexcess-precision=standard(和-ffloat-store),用于 VC++/fp:strict。 -
@chux VS 2005 甚至没有声称要实现 C99,因此它不必定义
FLT_EVAL_METHOD。而且编译器在定义它时并不总是按照他们所说的去做(stackoverflow.com/questions/17663780/…)
标签: c++ c visual-studio floating-point