有和没有-ansi差异的gcc浮点数

gcc floating point with and without -ansi disparity

考虑这两个程序。第一个在 gcc 5.3.0 上打印 "Unequal"(目标:i686-pc-cygwin)。当使用 -ansi 选项时,打印 "Equal"。

int main () {
        double d = 2.335 - 2.334;
        double q = 0.001;
        if (d == q) {
            printf ("Equal\n");
        } else {
            printf ("Unequal\n");
        }
    return 0;
 }

第二个打印 "Unequal" 有或没有 -ansi 选项。

int main () {
    if (2.335 - 2.334 == 0.001) {
        printf ("Equal\n");
    } else {
        printf ("Unequal\n");
    }
    return 0;
}

差异的根源是什么? 当然,实数不应该进行相等性测试是常识。我了解 IEEE754 标准对涉及浮点计算的(不)精度的影响。然而,据我所知,这两个程序在语义上应该是等价的,并且给出相同的结果。

在 C99 中删除的第一个 C89 模式中是否进行了一些隐式转换?

警告:这可能不是关于原因的完整答案,但这里有一些基于使用各种选项编译和反汇编输出的数据...

第二个程序[仅使用文字常量]生成任何浮点指令。它执行单个 printf。 (即所有计算都在编译器内完成)。

所以,以下仅限于第一个节目。

您没有指定给 gcc 的 [其他] 命令行选项,但我想只有 -ansi

我有一台 64 位 linux 机器 [gcc 5.3.1],所以我必须添加 -m32.

没有它,反汇编 [for 64 bit],有或没有 -ansi 都是 same 并产生 Unequal [它使用 XMM 说明]。使用 -O2 不会生成 浮点指令,只是一个 printf [有或没有 -ansi]。还是一样。

如果我使用-m32-O2,只生成printf,输出是Unequal,不管-ansi与否

唯一的差异发生在 32 位、优化关闭以及是否使用 -ansi 之间。这里,gcc 为 older/traditional 387 FP 协处理器单元生成指令。

没有-ansi,这里是反汇编:

 dyn.o:     file format elf32-i386

Disassembly of section .text:

00000000 <main>:
   0:   8d 4c 24 04             lea    0x4(%esp),%ecx
   4:   83 e4 f0                and    [=10=]xfffffff0,%esp
   7:   ff 71 fc                pushl  -0x4(%ecx)
   a:   55                      push   %ebp
   b:   89 e5                   mov    %esp,%ebp
   d:   51                      push   %ecx
   e:   83 ec 14                sub    [=10=]x14,%esp
  11:   dd 05 10 00 00 00       fldl   0x10
  17:   dd 5d f0                fstpl  -0x10(%ebp)
  1a:   dd 05 18 00 00 00       fldl   0x18
  20:   dd 5d e8                fstpl  -0x18(%ebp)
  23:   dd 45 f0                fldl   -0x10(%ebp)
  26:   dd 45 e8                fldl   -0x18(%ebp)
  29:   df e9                   fucomip %st(1),%st
  2b:   dd d8                   fstp   %st(0)
  2d:   7a 1e                   jp     4d <L00>
  2f:   dd 45 f0                fldl   -0x10(%ebp)
  32:   dd 45 e8                fldl   -0x18(%ebp)
  35:   df e9                   fucomip %st(1),%st
  37:   dd d8                   fstp   %st(0)
  39:   75 12                   jne    4d <L00>
  3b:   83 ec 0c                sub    [=10=]xc,%esp
  3e:   68 00 00 00 00          push   [=10=]x0
  43:   e8 fc ff ff ff          call   44 <main+0x44>
  48:   83 c4 10                add    [=10=]x10,%esp
  4b:   eb 10                   jmp    5d <L01>
  4d:L00 83 ec 0c               sub    [=10=]xc,%esp
  50:   68 06 00 00 00          push   [=10=]x6
  55:   e8 fc ff ff ff          call   56 <main+0x56>
  5a:   83 c4 10                add    [=10=]x10,%esp
  5d:L01 b8 00 00 00 00         mov    [=10=]x0,%eax
  62:   8b 4d fc                mov    -0x4(%ebp),%ecx
  65:   c9                      leave
  66:   8d 61 fc                lea    -0x4(%ecx),%esp
  69:   c3                      ret

-ansi一样除了单条指令:

--- dynstd.dis  2016-06-09 09:58:18.719906988 -0700
+++ dynansi.dis 2016-06-09 09:58:44.266286688 -0700
@@ -14,7 +14,7 @@
    e:  83 ec 14                sub    [=11=]x14,%esp
   11:  dd 05 10 00 00 00       fldl   0x10
   17:  dd 5d f0                fstpl  -0x10(%ebp)
-  1a:  dd 05 18 00 00 00       fldl   0x18
+  1a:  dd 05 10 00 00 00       fldl   0x10
   20:  dd 5d e8                fstpl  -0x18(%ebp)
   23:  dd 45 f0                fldl   -0x10(%ebp)
   26:  dd 45 e8                fldl   -0x18(%ebp)

请注意,略高于差异的是 fldl 0x10。然后,没有 -ansi,后面跟着 fldl 0x18-ansi 后跟 fldl 0x10。所以,如果没有 -ansi [我最好的猜测是] 我们正在比较 0x10 == 0x18 [不相等] 并且有 -ansi 我们正在比较 0x10 == 0x10 [等于]

几乎作为旁注,我用 clang 重复了相同的测试,但即使使用 -m32,它也会生成 XMM 指令,反汇编是相同的,并且输出总是 Unequal.

所以,AFAICT,对于一组有限的选项,这可能是代码生成问题(即错误)gcc

C99 和 C11 精确定义了当主机平台只能方便地计算到比 floatdouble 更高的精度时会发生什么。早期的 C89(或“ANSI”)C 标准没有。在 C99 或 C11 中,编译器将 FLT_EVAL_METHOD 定义为 1 或 2,这告诉程序员浮点常量和操作将被解释为比其类型更高的精度。

这是在 this message 中讨论的补丁中在 GCC 中实现的。 补丁提供的选项-fexcess-precision=standard在C99和C11中默认启用,但在“ANSI”(C89)模式下不启用。

尝试解释编译器在 C89 模式下做了什么没有太大意义:它有点模糊,浮点变量的值在没有赋值的情况下改变,或者在优化级别之间改变,因为this report 中描述。在C99模式下,FLT_EVAL_METHOD被编译器定义为2,差值2.335 - 2.334被编译器计算为一个80位的浮点数,80位的差值2335/1000 的 FP 表示和 2334/1000 的 80 位 FP 表示。这个数字恰好不同于 1/1000 的 80 位表示。这就是为什么你的测试程序的第二个版本的行为与没有 -ansi 时一样。在测试程序的第一个版本中,对 double 变量的赋值会导致数字四舍五入为双精度(64 位)浮点值。两者四舍五入后相等