我最近分析了一段用 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
如果使用相同的值对调用(例如f(1,2,1,2)
),但这并不总是发生在“发布”中。事实上,编译器已经优化了代码,就好像它是这样的d = a1-atan2(y2,x2)
并完全删除了对中间变量的赋值a2
。此外,它还利用了第二个事实:atan2()
的结果已经在 FPU 堆栈上,因此重新加载a1
在 FPU 上并减去这些值。问题是 FPU 以扩展精度(80 位)工作,而a1
是“唯一”双精度(64位),所以保存第一个atan2()
内存中的结果实际上已经失去了精度。最终,d
包含扩展精度和双精度之间的“转换错误”。
我完全知道这个身份(==
应避免使用 float/double 运算符)。我的问题不是关于如何检查双打之间的接近度。我的问题是如何考虑对局部变量的“契约”赋值。从我的“天真的”观点来看,赋值应该强制编译器将值转换为变量类型(在我的例子中为双精度)表示的精度。如果变量是“浮动”怎么办?如果它们是“int”(奇怪但合法)怎么办?
那么,简而言之,C 标准对这些情况有何规定?
从我的“天真的”观点来看,赋值应该强制编译器将值转换为变量类型(在我的例子中为双精度)表示的精度。
是的,C99标准就是这么说的。见下文。
那么,简而言之,C 标准对这些情况有何规定?
在某些情况下,C99 标准允许以比类型隐含的精度更高的精度来计算浮点运算:FLT_EVAL_METHOD
and FP_CONTRACT
in the standard http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1256.pdf,这是与超额精度相关的两个构造。但我不知道有任何词语可以解释为允许编译器任意将浮点值的精度从计算精度降低到类型精度。根据对标准的严格解释,这应该仅以确定性方式发生在特定位置,例如作业和演员表。
最好的就是读书约瑟夫·S·迈尔斯的分析 https://gcc.gnu.org/ml/gcc-patches/2008-11/msg00105.html相关部分的FLT_EVAL_METHOD
:
C99 允许以超范围和精度跟踪进行评估
一定的规则。 5.2.4.2.2 第 8 段概述了这些内容:
除了赋值和转换(这会删除所有额外的范围和
精度),具有浮点操作数的运算值和
值受通常的算术转换和浮动
常量被评估为其范围和精度可能的格式
大于类型所需的值。评价的运用
格式的特点是实现定义的值
FLT_EVAL_METHOD:
Joseph S. Myers 继续描述了在他的帖子附带的补丁之前 GCC 的情况。这种情况和你的编译器(以及无数其他编译器)中的情况一样糟糕:
使用 x87 浮点时,GCC 将 FLT_EVAL_METHOD 定义为 2。它是
然而,实现不符合 C99 的要求
FLT_EVAL_METHOD == 2,因为它是由后端实现的
假装处理器支持 SFmode 上的操作并且
DF模式:
- 有时,根据优化,值可能会溢出到
SFmode 或 DFmode 下的内存,因此会不可预测地丢失多余的精度
以及 C99 指定丢失以外的地方。
- 赋值通常不会丢失过多的精度,尽管
-ffloat-store
可能会使其更有可能发生。
…
C++标准继承了以下定义math.h
来自 C99,以及math.h
是定义的标题FLT_EVAL_METHOD
。出于这个原因,您可能期望 C++ 编译器也会效仿,但他们似乎并没有认真对待这个问题。即使G++仍然不支持-fexcess-precision=standard
,尽管它使用与 GCC 相同的后端(自 Joseph S. Myers 的帖子和随附补丁以来,GCC 一直支持此选项)。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)