gcc optimizations and the sin function
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.