diff.c is: #include <stdio.h> int main(void) { long long l = 9223372036854775806; double d = 9223372036854775808.0; printf("%f\n", (double)l - d); return 0; } With gcc (GCC) 13.2.1 20231205 (Red Hat 13.2.1-6), gcc (Ubuntu 9.4.0-1ubuntu1~20.04.2) 9.4.0, clang 16.0.4 and clang 17.0.5: $ gcc -m64 -o diff diff.c && ./diff 0.000000 $ gcc -m32 -o diff diff.c && ./diff -2.000000 $ clang -m64 -o diff diff.c && ./diff 0.000000 $ clang -m32 -o diff diff.c && ./diff 0.000000 With cl.exe 19.29.3015319.29.30153 (first is x84 - 32 bit, second is 64 bit) C:\> CALL "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" x86 10.0.17763.0 C:\> cl diff.c >nul 2>nul & .\diff.exe 0.000000 C:\> CALL "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 10.0.17763.0 C:\> cl diff.c >nul 2>nul & .\diff.exe 0.000000 gcc -m32 produces a different result, compared to gcc -m64, clang 17 (32 and 64bit), and MSCV Visual Studio 2019 (32 and 64bit).
I suspect the issue is excessive precision with x87 fp.
This happens only without optimizations: $ gcc -O0 -m32 -o diff diff.c && ./diff -2.000000 $ gcc -O1 -m32 -o diff diff.c && ./diff 0.000000 $ gcc -O2 -m32 -o diff diff.c && ./diff 0.000000 $ gcc -O3 -m32 -o diff diff.c && ./diff 0.000000
[apinski@xeond2 gcc]$ ~/upstream-gcc/bin/gcc -m32 tr56.c [apinski@xeond2 gcc]$ ./a.out -2.000000 [apinski@xeond2 gcc]$ ~/upstream-gcc/bin/gcc -m32 tr56.c -fexcess-precision=standard [apinski@xeond2 gcc]$ ./a.out 0.000000 [apinski@xeond2 gcc]$ ~/upstream-gcc/bin/gcc -m32 tr56.c -msse2 -mfpmath=sse [apinski@xeond2 gcc]$ ./a.out 0.000000 Yes it is due to excessive precision of x87. Use either `-fexcess-precision=standard` or `-msse2 -mfpmath=sse` if you don't want to use the execessive precision of the x87 FP. *** This bug has been marked as a duplicate of bug 323 ***
Yeah, it is, that is how excess precision behaves. Due to the cast applying just to l rather than l - d it returns 0.0 with -fexcess-precision=standard, but if you change it to (double)(l - d) then it will return -2.0 at all optimization levels with -fexcess-precision=standard. -fexcess-precision=fast behaves depending on what instructions are actually used and where the conversions to float or double happen due to storing of expressions or subexpressions into memory as documented. If you don't like excess precision and have SSE2, you can use -msse2 -mfpmath=sse.
gcc -m64 -fexcess-precision=fast -o diff diff.c && ./diff 0.000000 gcc -m32 -fexcess-precision=fast -o diff diff.c && ./diff -2.000000 clang -m32 -fexcess-precision=fast -o diff diff.c && ./diff 0.000000 clang -m64 -fexcess-precision=fast -o diff diff.c && ./diff 0.000000 gcc -m64 -fexcess-precision=standard -o diff diff.c && ./diff 0.000000 gcc -m32 -fexcess-precision=standard -o diff diff.c && ./diff 0.000000 clang -m32 -fexcess-precision=standard -o diff diff.c && ./diff 0.000000 clang -m64 -fexcess-precision=standard -o diff diff.c && ./diff 0.000000 If this excess precision has justification, why are the results different for 32 and 64bit code? With printf("%f\n", (double)l - d); printf("%f\n", (double)(l - d)); there is indeed a difference: $ gcc -m32 -fexcess-precision=standard -o diff diff.c && ./diff 0.000000 -2.000000
Because 64bit uses the SSE2 fp instructions rather than x87 fp instructions.
And while SSE/SSE2 has instructions for performing arithmetics in IEEE754 single and double formats, x87 does not, everything is done in extended precision (unless the FPU is configured to use smaller precision but then it doesn't support the extended precision long double on the other side) and conversions to IEEE754 single/double have to be done when storing the extended precision registers into memory. So, it is impossible to achieve the expected IEEE754 single and double arithmetics behavior, one can get only something close to it (but with double rounding problems) if all the temporaries are immediately stored into memory and loaded from it again. The -ffloat-store option does it to a limited extent (doesn't convert everything though), but still, the performance is terrible. C allows extended precision and specifies how to should behave, that is the -fexcess-precision=standard model (e.g. enabled by default for -std=c{99,11,...} options as opposed to -std=gnu..., then it is consistently using the excess precision with some casts/assignments mandating rounding to lower precisions, while -fexcess-precision=fast is what gcc has been implementing before it has been introduced, excess precision is used there as long as something is kept in the FPU registers and conversions are done when it needs to be spilled to memory.
-fexcess-precision=standard does not ensure consistent behaviour between gcc 13.2.1 20231205 (Red Hat 13.2.1-6) and clang 17.0.5. -msse2 -mfpmath=sse does for diff.c: #include <stdio.h> #include <math.h> int main(void) { long long l = 9223372036854775806; double d = 9223372036854775808.0; printf("%f\n", (double)l - d); printf("%i\n", pow(3.3, 4.4) == 191.18831051580915); return 0; } $ gcc -lm -fexcess-precision=standard -m32 -o diff diff.c && ./diff 0.000000 0 $ clang -lm -fexcess-precision=standard -m32 -o diff diff.c && ./diff 0.000000 1 $ gcc -lm -fexcess-precision=standard -m64 -o diff diff.c && ./diff 0.000000 1 $ clang -lm -fexcess-precision=standard -m64 -o diff diff.c && ./diff 0.000000 1 $ gcc -lm -fexcess-precision=fast -m32 -o diff diff.c && ./diff -2.000000 1 $ clang -lm -fexcess-precision=fast -m32 -o diff diff.c && ./diff 0.000000 1 $ gcc -lm -fexcess-precision=fast -m64 -o diff diff.c && ./diff 0.000000 1 $ clang -lm -fexcess-precision=fast -m64 -o diff diff.c && ./diff 0.000000 1 $ gcc -lm -msse2 -mfpmath=sse -m32 -o diff diff.c && ./diff 0.000000 1 $ clang -lm -msse2 -mfpmath=sse -m32 -o diff diff.c && ./diff 0.000000 1 $ gcc -lm -msse2 -mfpmath=sse -m64 -o diff diff.c && ./diff 0.000000 1 $ clang -lm -msse2 -mfpmath=sse -m64 -o diff diff.c && ./diff 0.000000 1 cl.exe also prints 0.000000 and 1
That is not what I read from what you've posted, -fexcess-precision=standard is consistent between the compilers, -fexcess-precision=fast is not (and doesn't have to be), neither between different compilers, nor between different optimization levels etc.
Oh, you mean the pow equality comparison. I think you should study something about floating point, errors, why equality comparisons of floating point values are usually a bad idea etc. There is no gcc bug, just bad user expectations.
Anyway, seems clang is buggy: clang -O2 -m32 -mno-sse -mfpmath=387 -fexcess-precision=standard #include <float.h> int main () { #if FLT_EVAL_METHOD == 2 && LDBL_MANT_DIG == 64 && DBL_MANT_DIG == 53 if ((double) 191.18831051580915 == 191.18831051580915) __builtin_abort (); #endif } should always succeed, because if FLT_EVAL_METHOD is 2, it ought to be evaluated as (long double) (double) 191.18831051580915L == 191.18831051580915L and (double) 191.18831051580915L is 0x1.7e606a3c65c95p+7 while 191.18831051580915L is 0x1.7e606a3c65c9503ap+7L, so they aren't equal.
(In reply to Jakub Jelinek from comment #10) > Oh, you mean the pow equality comparison. I think you should study > something about floating point, errors, why equality comparisons of floating > point values are usually a bad idea etc. > There is no gcc bug, just bad user expectations. This is why gcc has the -Wfloat-equal warning flag, isn't it?
For clang being buggy from https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113679#c11 I filled https://github.com/llvm/llvm-project/issues/81358 .