看这个片段:
int main() {
double v = 1.1;
return v == 1.1;
}
在 32 位编译中,如果指定了
-fexcess-precision=standard
,则该程序返回 0。如果没有它,程序将返回 1。
为什么会有差异?查看汇编代码(godbolt),似乎对于
-fexcess-precision=standard
,gcc使用1.1
作为long double
常量(它将常量加载为TBYTE
)。为什么会这样?
一开始我以为是bug,但是我发现了这个gcc bug comment,看来这个行为是故意的,或者至少不是意料之外的。
这是 QoI 问题吗?我知道比较是使用
long double
精度执行的,但我的 1.1
仍然不是 long double
文字。奇怪的是,如果我将 1.1
与 double
(已经是 double
)进行比较,问题就会消失。
(另一个奇怪的事情是 GCC 会加载并比较两次,请参阅双
fucomip
指令。但即使在 64 位模式下它也会这样做。我知道在我的 godbolt 链接中,优化已关闭,但仍然,我的代码中只有一次比较,为什么 GCC 比较两次?)
在C中,这种行为显然是符合要求的。 C 标准允许编译器在计算浮点表达式时使用额外的精度,包括将源代码中的数字转换为浮点表示形式时。 C 2024 6.4.5.3 说:
…浮点常量的值可以用比类型所要求的范围和精度更大的范围和精度来表示…
C++ 并没有如此明确地谈论文字,但 C++ 草案 N4849 6 说:
浮点操作数的值和浮点表达式的结果可以用比类型要求的更高的精度和范围来表示......
我们可以将有关浮点操作数的这条语句包括为文字的操作数。
当使用该值初始化
v
时,必须将其转换为实际的double
值。 (赋值、初始化和转换会产生标称类型的实际值,而不会产生过多的精度。)因此,在 return v == 1.1;
中,v
具有 double
值,但 1.1
具有 long double
值,并且它们是不同的,至少在你测试的情况下是这样。 (并非所有目标都是如此;GCC 可能会也可能不会使用超额精度,具体取决于目标特征和其他因素。)
当使用
-fexcess-precision=standard
时,GCC 不会使用这种多余的精度。