Inconsistent atan2 results between 64-bit and 32-bit architecture

Viewed 145

When using std::atan2, I get different results depending on whether I cross-compile for a 32-bit architecture rather than compiling for the (native) 64-bit.

$ cat test.cc 
#include <cmath>
#include <stdio.h>

int main(int argc, char **argv) {
    printf("%f\n", std::atan2(366.470947f, -116.213623f));
}

$ gcc test.cc -o test -lm -ffp-contract=off -ffloat-store
$ ./test 
1.877880
$ gcc -m32 test.cc -o test -lm -ffp-contract=off -ffloat-store
$ ./test 
1.877881

I was hoping for the results to be the same, since I'm porting an application over to 64-bit and require identical floating point results. As you can see I've already tried -ffp-contract=off -ffloat-store which I'm aware can cause floating point inconsistency. Is there something else I'm missing? Or are the trig functions simply not standardized in this way?

I'm running Ubuntu 18.04.2, with gcc 7.5.0. CPU is a i7-1065G7.

1 Answers

You've unfortunately come across a variant of one of the longest standing gcc bugs: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323

The problem is that GCC will or will not use 80-bit FP precision in different circumstances, and our ability to constrain it is limited.

Using -m32 forces gcc to generate code that will run on i386, and so there is no choice but to use the 80-bit extended precision FPU instructions. You could, in principle, use -mx32 for a 32-bit memory model but otherwise x86-64, if your runtime environment is happy with it.

But you're still going to be in trouble:

  • -m32 is going to invoke the standard library version of atan2 that pulls arguments from the 387 FP stack, which is a different implementation to the one that uses xmm registers to pass arguments.
  • With optimization, GCC will use an extended double precision evaluation to calculate the result at compile time. So with e.g. -O3 you'll get 1.877881 with both -m32 and -m64, both of which disagree with the value you will get from a 32-bit float computation.
  • The option -fexcess-precision=standard — which promises to avoid such higher-precision evaluations — only works in C, not C++. See: https://gcc.gnu.org/wiki/FAQ#PR323

In short, the situation is very unsatisfactory, and has been for over twenty years.

Related