It is well-known that gcc optimization levels affect the results of floating point calculations, and the link says it can even happen with simple statements like x * y + z. This has an unfortunate corollary: how accurate a compiled c program’s floating point calculations will be is not a function of the source code alone.

Consider the following code.

#include <math.h>
#include <stdio.h>

int main() {
  double x = 0x1.d528296056e06p-2;
  printf("%a\n", sin(x));
  return 0;
}

If I compile with gcc without optimization I get output ending with 8. If I compile with optimization, I get output ending with 9.

With the help of the amazing godbolt we can see why. Compiling with gcc -O0 (i.e. most optimizations off) produces:

.LC1:
        .string "%d\n"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        movsd   xmm0, QWORD PTR .LC0[rip]
        movsd   QWORD PTR [rbp-8], xmm0
        mov     rax, QWORD PTR [rbp-8]
        movq    xmm0, rax
        call    sin
        movq    rax, xmm0
        mov     QWORD PTR [rbp-16], rax
        mov     rax, QWORD PTR [rbp-16]
        movq    xmm0, rax
        mov     edi, OFFSET FLAT:.LC1
        mov     eax, 1
        call    printf
        mov     eax, 1
        leave
        ret
.LC0:
        .long   -1778029050
        .long   1071469186

The only important bit here is that the precompiled sin function, linked in from the glibc math library, is actually called when the executable runs. Compiling with gcc -O1, enabling more optimizations, we get

.LC1:
        .string "%d\n"
main:
        sub     rsp, 8
        movsd   xmm0, QWORD PTR .LC0[rip]
        mov     edi, OFFSET FLAT:.LC1
        mov     eax, 1
        call    printf
        mov     eax, 1
        add     rsp, 8
        ret
.LC0:
        .long   -1307210647
        .long   1071402658

This time there is no call to a linked sin function. The executable just prints out a precomputed value. The compiler has evaluated the sin function during optimization and produced a (very slightly) different value. The difference is purely down to what evaluates the sin call: the compiler during its optimization phase, or the precompiled sin function from libm.