"double" 的操作和 C 中的优化
Operations on "double" and optimization in C
我最近分析了一段用 VS2005 编译的旧代码,因为 "debug"(无优化)和 "release"(/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
如果使用相同的值对调用(例如 f(1,2,1,2)
),则预计会打印 "EQUAL",但这并不总是发生在 "release" 中.事实上,编译器已经优化了代码,就好像它是 d = a1-atan2(y2,x2)
这样的东西,并完全删除了对中间变量 a2
的赋值。此外,它利用了第二个 atan2()
的结果已经在 FPU 堆栈上的事实,因此在 FPU 上重新加载 a1
并减去值。问题是 FPU 以扩展精度(80 位)工作,而 a1
是 "only" 双精度(64 位),因此将第一个 atan2()
的结果保存在内存中实际上失去了精度.最终,d
包含扩展精度和双精度之间的 "conversion error"。
我完全知道应该避免使用 float/double 的身份(==
运算符)。我的问题不是关于如何检查双打之间的接近度。我的问题是关于 "contractual" 应该如何考虑对局部变量的赋值。根据我的 "naive" 观点,赋值应该强制编译器将值转换为变量类型(在我的例子中为双精度)表示的精度。如果变量是 "float" 怎么办?如果它们是 "int"(奇怪,但合法)怎么办?
那么,简而言之,C 标准对这些情况有何规定?
By my "naive" point of view, an assignment should force the compiler to convert a value to the precision represented by the variable's type (double, in my case).
对,C99标准就是这么说的。见下文。
So, in short, what does the C standard say about that cases?
在某些情况下,C99 标准允许以比类型隐含的精度更高的精度计算浮点运算:在 [=22] 中查找 FLT_EVAL_METHOD
和 FP_CONTRACT
=],这是与超精度相关的两个结构。但我不知道有任何词可以解释为允许编译器任意将浮点值的精度从计算精度降低到类型精度。在对标准的严格解释中,这应该以确定性的方式仅发生在特定位置,例如分配和演员表。
最好阅读 Joseph S. Myers's analysis 与 FLT_EVAL_METHOD
相关的部分:
C99 allows evaluation with excess range and precision following
certain rules. These are outlined in 5.2.4.2.2 paragraph 8:
Except for assignment and cast (which remove all extra range and
precision), the values of operations with floating operands and
values subject to the usual arithmetic conversions and of floating
constants are evaluated to a format whose range and precision may
be greater than required by the type. The use of evaluation
formats is characterized by the implementation-defined value of
FLT_EVAL_METHOD:
Joseph S. Myers 继续描述了在他的 post 附带补丁之前 GCC 中的情况。这种情况和你的编译器(以及无数其他编译器)一样糟糕:
GCC defines FLT_EVAL_METHOD to 2 when using x87 floating point. Its
implementation, however, does not conform to the C99 requirements for
FLT_EVAL_METHOD == 2, since it is implemented by the back end
pretending that the processor supports operations on SFmode and
DFmode:
- Sometimes, depending on optimization, a value may be spilled to
memory in SFmode or DFmode, so losing excess precision unpredictably
and in places other than when C99 specifies that it is lost.
- An assignment will not generally lose excess precision, although
-ffloat-store
may make it more likely that it does.
…
C++标准从C99继承了math.h
的定义,math.h
是定义FLT_EVAL_METHOD
的头文件。出于这个原因,您可能希望 C++ 编译器效仿,但他们似乎并没有认真对待这个问题。甚至 G++ 仍然不支持 -fexcess-precision=standard
,尽管它使用与 GCC 相同的后端(自 Joseph S. Myers 的 post 和随附的补丁以来支持此选项)。
我最近分析了一段用 VS2005 编译的旧代码,因为 "debug"(无优化)和 "release"(/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
如果使用相同的值对调用(例如 f(1,2,1,2)
),则预计会打印 "EQUAL",但这并不总是发生在 "release" 中.事实上,编译器已经优化了代码,就好像它是 d = a1-atan2(y2,x2)
这样的东西,并完全删除了对中间变量 a2
的赋值。此外,它利用了第二个 atan2()
的结果已经在 FPU 堆栈上的事实,因此在 FPU 上重新加载 a1
并减去值。问题是 FPU 以扩展精度(80 位)工作,而 a1
是 "only" 双精度(64 位),因此将第一个 atan2()
的结果保存在内存中实际上失去了精度.最终,d
包含扩展精度和双精度之间的 "conversion error"。
我完全知道应该避免使用 float/double 的身份(==
运算符)。我的问题不是关于如何检查双打之间的接近度。我的问题是关于 "contractual" 应该如何考虑对局部变量的赋值。根据我的 "naive" 观点,赋值应该强制编译器将值转换为变量类型(在我的例子中为双精度)表示的精度。如果变量是 "float" 怎么办?如果它们是 "int"(奇怪,但合法)怎么办?
那么,简而言之,C 标准对这些情况有何规定?
By my "naive" point of view, an assignment should force the compiler to convert a value to the precision represented by the variable's type (double, in my case).
对,C99标准就是这么说的。见下文。
So, in short, what does the C standard say about that cases?
在某些情况下,C99 标准允许以比类型隐含的精度更高的精度计算浮点运算:在 [=22] 中查找 FLT_EVAL_METHOD
和 FP_CONTRACT
=],这是与超精度相关的两个结构。但我不知道有任何词可以解释为允许编译器任意将浮点值的精度从计算精度降低到类型精度。在对标准的严格解释中,这应该以确定性的方式仅发生在特定位置,例如分配和演员表。
最好阅读 Joseph S. Myers's analysis 与 FLT_EVAL_METHOD
相关的部分:
C99 allows evaluation with excess range and precision following certain rules. These are outlined in 5.2.4.2.2 paragraph 8:
Except for assignment and cast (which remove all extra range and precision), the values of operations with floating operands and values subject to the usual arithmetic conversions and of floating constants are evaluated to a format whose range and precision may be greater than required by the type. The use of evaluation formats is characterized by the implementation-defined value of FLT_EVAL_METHOD:
Joseph S. Myers 继续描述了在他的 post 附带补丁之前 GCC 中的情况。这种情况和你的编译器(以及无数其他编译器)一样糟糕:
GCC defines FLT_EVAL_METHOD to 2 when using x87 floating point. Its implementation, however, does not conform to the C99 requirements for FLT_EVAL_METHOD == 2, since it is implemented by the back end pretending that the processor supports operations on SFmode and DFmode:
- Sometimes, depending on optimization, a value may be spilled to memory in SFmode or DFmode, so losing excess precision unpredictably and in places other than when C99 specifies that it is lost.
- An assignment will not generally lose excess precision, although
-ffloat-store
may make it more likely that it does.…
C++标准从C99继承了math.h
的定义,math.h
是定义FLT_EVAL_METHOD
的头文件。出于这个原因,您可能希望 C++ 编译器效仿,但他们似乎并没有认真对待这个问题。甚至 G++ 仍然不支持 -fexcess-precision=standard
,尽管它使用与 GCC 相同的后端(自 Joseph S. Myers 的 post 和随附的补丁以来支持此选项)。