Search code examples
cfloating-point32bit-64bit

atan2f gives different results with m32 flag


I'm porting some code from 32 bit to 64 bit, and ensuring the answers are the same. In doing so, I noticed that atan2f was giving different results between the two.

I created this min repro:

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

void testAtan2fIssue(float A, float B)
{
    float atan2fResult = atan2f(A, B);
    printf("atan2f: %.15f\n", atan2fResult);

    float atan2Result = atan2(A, B);
    printf("atan2: %.15f\n", atan2Result);
}

int main()
{
    float A =  16.323556900024414;
    float B = -5.843180656433105;
    testAtan2fIssue(A, B);
}

When built with:

gcc compilerTest.c -m32 -o 32bit.out -lm

it gives:

atan2f: 1.914544820785522
atan2: 1.914544820785522

When built with:

gcc compilerTest.c -o 64bit.out -lm

it gives:

atan2f: 1.914544701576233
atan2: 1.914544820785522

Note that atan2 gives the same result in both cases, but atan2f does not.

Things I have tried:

  1. Building the 32 bit version with -ffloat-store

  2. Building the 32 bit version with -msse2 -mfpmath=sse

  3. Building the 64 bit version with -mfpmath=387

None changed the results for me.

(All of these were based on the hypothesis that it has something to do with the way floating point operations happen on 32 bit vs 64 bit architectures.)

Question:

What are my options for getting them to give the same result? (Is there a compiler flag I could use?) And also, what is happening here?

I'm running on an i7 machine, if that is helpful.


Solution

  • This is easier to see in hex notation.

    void testAtan2fIssue(float A, float B) {
        double d = atan2(A, B);
        printf("        atan2 : %.13a %.15f\n", d, d);
        float f = atan2f(A, B);
        printf("        atan2f: %.13a %.15f\n", f, f);
        printf("(float) atan2 : %.13a %.15f\n", (float) d, (float) d);
    
        float f2 = nextafterf(f, 0);
        printf("problem value : %.13a %.15f\n", f2, f2);
    }
    
    // _ added for clarity
            atan2 : 0x1.ea1f9_b9d85de4p+0 1.914544_797857041
            atan2f: 0x1.ea1f9_c0000000p+0 1.914544_820785522
    (float) atan2 : 0x1.ea1f9_c0000000p+0 1.914544_820785522
    problem value : 0x1.ea1f9_a0000000p+0 1.914544_701576233
    

    what is happening here?

    The conversion from double to float can be expected to be optimal, yet arctangent functions may be a few ULP off on various platforms. The 1.914544701576233 is the next smaller float value and reflects the slightly inferior arctangent calculation.


    What are my options for getting them to give the same result?

    Few. Code could roll your own my_atan2() from an established code base. Yet even that may have subtle implementation differences. @stark

    Instead, consider making code checking tolerant of the minute variations.