3

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.

kipi
  • 31
  • 2
  • 1
    Re: `I require identical floating point results` - you're gonna have a bad time. – alter_igel Jan 08 '21 at 15:38
  • Thanks for the link. It seems I'm probably suffering from the same thing (not sure how I missed it in my search). And yes, it sounds like bad news if there's no guarantees of consistency. – kipi Jan 08 '21 at 15:50
  • 2
    Does this answer your question? [atan2f gives different results with m32 flag](https://stackoverflow.com/questions/42034329/atan2f-gives-different-results-with-m32-flag) – NKSM Jan 08 '21 at 16:19
  • Time to switch to clang – xaxxon Jan 08 '21 at 16:46
  • lots of duplicates: [std::pow produce different result in 32 bit and 64 bit application](https://stackoverflow.com/q/33887108/995714), [Why would the same code yield different numeric results on 32 vs 64-bit machines?](https://stackoverflow.com/q/7847274/995714), [Difference in floating point arithmetics between x86 and x64](https://stackoverflow.com/q/22710272/995714)... – phuclv Jan 08 '21 at 16:50
  • @xaxxon Clang also uses x87 for 32-bit code and SSE for 64-bit code so the same thing will happen – phuclv Jan 08 '21 at 16:55
  • kipi, Curious, Is it really your intentions to use 32-bit `float` constants rather than 64-bit `double` ones like `std::atan2(366.470947, -116.213623)` (no `f`)? – chux - Reinstate Monica Jan 08 '21 at 22:21
  • Note: `1.877881` is the nearer the correct answer of `1.87788057...` – chux - Reinstate Monica Jan 08 '21 at 22:24
  • @chux-ReinstateMonica yes, it is --- the code I'm trying to port to 64-bit passes floats into atan2 in this way. – kipi Jan 09 '21 at 07:15

1 Answers1

4

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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
halfflat
  • 1,584
  • 8
  • 11
  • Thanks for the detailed answer. Could I write wrappers for the affected math functions in C, compile with `-fexcess-precision=standard`, and link these into my C++ program? I'm fortunately able to change the function calls in the C++ code to refer to my wrapped versions, so this would be a decent solution. – kipi Jan 08 '21 at 16:28
  • @kipi I think that should work! Another option is to write your own transcendental functions (or use someone else's). This dodges the compile-time optimization issue, and you can then pick an implementation that is as accurate as you wish, or makes use of vectorization, etc. This is clearly not the easy option though :) – halfflat Jan 08 '21 at 16:30