1

Consider the two programs. First one prints "Unequal" on gcc 5.3.0 (target: i686-pc-cygwin). When -ansi option is used "Equal" is printed.

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

Second one prints "Unequal" with or without -ansi option.

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

What is the source of disparity? Of course it is a common knowledge that real numbers should not be tested for equality. I understand the implications of IEEE754 standard on the (im-)precision of calculations involving floating point. However, to my best understanding, those two programs should be semantically equivalent and give the same results.

Is there some implicit conversion going on in the first one in C89 mode that was removed in C99?

Thomas Dickey
  • 51,086
  • 7
  • 70
  • 105
lukeg
  • 4,189
  • 3
  • 19
  • 40

2 Answers2

2

The C99 and C11 define precisely what happens when the host platform can only conveniently compute to a higher precision than that of float and double. The earlier C89 (or “ANSI”) C standard did not. In C99 or C11, the compiler defines FLT_EVAL_METHOD to 1 or 2, which tells the programmer that floating-point constants and operations are going to be interpreted to a higher precision than that of their types.

This was implemented in GCC in the patch discussed in this message. The option -fexcess-precision=standard provided by the patch is enabled by default in C99 and C11, but not enabled in “ANSI” (C89) mode.

It does not make too much sense to try to interpret what the compiler does in C89 mode: it's a bit fuzzy, with the value of floating-point variables changing without assignments to them, or changing between optimization levels, as described in this report. In C99 mode, with FLT_EVAL_METHOD defined by the compiler to 2, the difference 2.335 - 2.334 is computed by the compiler as a 80-bit floating-point number, the difference between the 80-bit FP representation of 2335/1000 and the 80-bit FP representation of 2334/1000. This number happens to be different from the 80-bit representation of 1/1000. This is why the second version of your test program behaves as it does without -ansi. In the first version of your test program, the assignments to double variables cause the numbers to be rounded to double-precison (64-bit) floating-point values. They are equal after both having been rounded thus.

Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281
0

Caveat: This may not be a complete answer as to why, but here's some data based on compiling with various options and disassembling the output ...

The second program [using only literal constants] does not generate any floating point instructions. It does a single printf. (i.e. all calculations are done within the compiler).

So, the following is limited to the first program.

You didn't specify what [other] command line options you gave to gcc, but I imagine only -ansi.

I've got a 64 bit linux machine [with gcc 5.3.1], so I had to add -m32.

Without that, the disassembly [for 64 bit], with or without -ansi is the same and produces Unequal [It uses the XMM instructions]. With -O2, no floating point instructions are generated, just a printf [with or without -ansi]. Again, the same.

If I use -m32 and -O2, only the printf is generated and the output is Unequal, regardless of -ansi or not

The only disparity occurs between 32 bit, optimization off, and use of -ansi or not. Here, gcc generates instructions for the older/traditional 387 FP coprocessor unit.

Without -ansi, here is the disassembly:

 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    $0xfffffff0,%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    $0x14,%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    $0xc,%esp
  3e:   68 00 00 00 00          push   $0x0
  43:   e8 fc ff ff ff          call   44 <main+0x44>
  48:   83 c4 10                add    $0x10,%esp
  4b:   eb 10                   jmp    5d <L01>
  4d:L00 83 ec 0c               sub    $0xc,%esp
  50:   68 06 00 00 00          push   $0x6
  55:   e8 fc ff ff ff          call   56 <main+0x56>
  5a:   83 c4 10                add    $0x10,%esp
  5d:L01 b8 00 00 00 00         mov    $0x0,%eax
  62:   8b 4d fc                mov    -0x4(%ebp),%ecx
  65:   c9                      leave
  66:   8d 61 fc                lea    -0x4(%ecx),%esp
  69:   c3                      ret

With -ansi, it is the same except for a single instruction:

--- 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    $0x14,%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)

Notice that slightly above the difference there is an fldl 0x10. Then, without -ansi, it is followed by fldl 0x18. With -ansi, it is followed by fldl 0x10. So, without -ansi [my best guess is] we are comparing 0x10 == 0x18 [Unequal] and with -ansi we are comparing 0x10 == 0x10 [Equal]

Almost as a side note, I repeated the same tests with clang, but even with -m32, it generates XMM instructions, the disassembly is the same, and the output is always Unequal.

So, AFAICT, this may be a code generation issue (i.e. bug) with gcc for one limited set of options.

Craig Estey
  • 30,627
  • 4
  • 24
  • 48