为何启用-fexcess-precision=standard时GCC会认为相等浮点值不等?
-fexcess-precision=standard参数对浮点比较的影响分析 先看这段代码:
int main() { double v = 1.1; return v == 1.1; }
在32位编译环境下,指定-fexcess-precision=standard参数时程序返回0,不指定时返回1,下面拆解背后的原因:
一、参数导致返回值差异的核心逻辑
当启用-fexcess-precision=standard时,GCC会把代码中的1.1字面量按long double(x86平台下为80位扩展精度)处理——从汇编代码能看到它用TBYTE格式加载这个常量。而变量v是double类型,存储的是1.1的双精度近似值。
这里的关键是:1.1无法用二进制浮点数精确表示,双精度(64位)和扩展精度(80位)对1.1的近似值存在细微差异。启用参数后,比较在long double精度下执行:变量v被加载到FPU后会自动提升为long double精度,但它的原始值是double的近似值;而右边的1.1是直接按long double精度生成的近似值,两者并不相等,因此程序返回0。
不启用该参数时,GCC会把两边的1.1都按double精度处理,比较的是同一个双精度近似值,所以返回1。
GCC官方bug评论里明确说明这是预期行为,并非bug。另外,如果把比较处的1.1强制转换为double(比如写成v == (double)1.1),两边就都按double精度参与比较,差异会直接消失。
二、未优化时出现两次比较的原因
从给出的汇编代码能看到,不管是否启用参数,都出现了两次fucomip指令。这是因为在未开启优化(默认-O0)的情况下,GCC为了严格对应C语言语义、简化编译器实现,会生成冗余代码:
浮点比较会设置FPU状态字,涉及是否非规格化、是否相等、是否无序(比如NaN参与比较)等状态。第一次比较后用setnp处理“非无序”的情况(排除NaN导致的无效比较),第二次比较专门判断数值是否相等,最后通过cmovne指令把两个条件的结果合并成最终返回值。这种冗余是-O0下的正常现象,开启优化(比如-O1)后,编译器会自动合并成一次比较操作。
未启用-fexcess-precision=standard的汇编代码
main: push ebp mov ebp, esp and esp, -8 sub esp, 16 fld QWORD PTR .LC0 fstp QWORD PTR [esp+8] fld QWORD PTR [esp+8] fld QWORD PTR .LC0 fucomip st, st(1) fstp st(0) setnp al mov edx, 0 fld QWORD PTR [esp+8] fld QWORD PTR .LC0 fucomip st, st(1) fstp st(0) cmovne eax, edx movzx eax, al leave ret .LC0: .long -1717986918 .long 1072798105
启用-fexcess-precision=standard后的汇编代码
main: push ebp mov ebp, esp and esp, -8 sub esp, 16 fld QWORD PTR .LC0 fstp QWORD PTR [esp+8] fld QWORD PTR [esp+8] fld TBYTE PTR .LC1 fucomip st, st(1) setnp al mov edx, 0 fld TBYTE PTR .LC1 fucomip st, st(1) fstp st(0) cmovne eax, edx movzx eax, al leave ret .LC0: .long -1717986918 .long 1072798105 .LC1: .long -858993459 .long -1932735284 .long 16383
内容的提问来源于stack exchange,提问作者geza

