Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

m32 and m64 compiler options provides different output

Tags:

c

gcc

Sample Code

#include "stdio.h"
#include <stdint.h>

int main()
{
    double d1 = 210.01;
    uint32_t m = 1000;
    uint32_t v1 = (uint32_t) (d1 * m);

    printf("%d",v1);
    return 0;
}

Output
1. When compiling with -m32 option (i.e gcc -g3 -m32 test.c)

/test 174 # ./a.out
210009

2. When compiling with -m64 option (i.e gcc -g3 -m64 test.c)

test 176 # ./a.out
210010

Why do I get a difference?
My understanding "was", m would be promoted to double and multiplication would be cast downward to unit32_t. Moreover, since we are using stdint type integer, we would be further removing ambiguity related to architecture etc etc.

I know something is fishy here, but not able to pin it down.

Update:
Just to clarify (for one of the comment), the above behavior is seen for both gcc and g++.

like image 385
kumar_m_kiran Avatar asked Apr 06 '16 17:04

kumar_m_kiran


1 Answers

I can confirm the results on my gcc (Ubuntu 5.2.1-22ubuntu2). What seems to happen is that the 32-bit unoptimized code uses 387 FPU with FMUL opcode, whereas 64-bit uses the SSE MULS opcode. (just execute gcc -S test.c with different parameters and see the assembler output). And as is well known, the 387 FPU that executes the FMUL has more than 64 bits of precision (80!) so it seems that it rounds differently here. The reason of course is that that the exact value of 64-bit IEEE double 210.01 is not that, but

 210.009999999999990905052982270717620849609375

and when you multiply by 1000, you're not actually just shifting the decimal point - after all there is no decimal point but binary point in the floating point value; so the value must be rounded. And on 64-bit doubles it is rounded up. On 80-bit 387 FPU registers, the calculation is more precise, and it ends up being rounded down.

After reading about this a bit more, I believe the result generated by gcc on 32-bit arch is not standard conforming. Thus if you force the standard to C99 or C11 with -std=c99, -std=c11, you will get the correct result

% gcc -m32 -std=c11 test.c; ./a.out
210010

If you do not want to force C99 or C11 standard, you could also use the -fexcess-precision=standard switch.


However fun does not stop here.

% gcc -m32 test.c; ./a.out
210009
% gcc -m32 -O3 test.c; ./a.out
210010

So you get the "correct" result if you compile with -O3; this is of course because the 64-bit compiler uses the 64-bit SSE math to constant-fold the calculation.


To confirm that extra precision affects it, you can use a long double:

#include "stdio.h"
#include <stdint.h>

int main()
{
    long double d1 = 210.01;  // double constant to long double!
    uint32_t m = 1000;
    uint32_t v1 = (uint32_t) (d1 * m);

    printf("%d",v1);
    return 0;
}

Now even -m64 rounds it to 210009.

% gcc -m64 test.c; ./a.out
210009
like image 180