Bug 113679 - long long minus double with gcc -m32 produces different results than other compilers or gcc -m64
Summary: long long minus double with gcc -m32 produces different results than other co...
Status: RESOLVED DUPLICATE of bug 323
Alias: None
Product: gcc
Classification: Unclassified
Component: target (show other bugs)
Version: 13.2.1
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2024-01-31 08:40 UTC by Дилян Палаузов
Modified: 2024-02-10 14:32 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Дилян Палаузов 2024-01-31 08:40:21 UTC
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).
Comment 1 Andrew Pinski 2024-01-31 08:44:29 UTC
I suspect the issue is excessive precision with x87 fp.
Comment 2 Дилян Палаузов 2024-01-31 08:45:30 UTC
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
Comment 3 Andrew Pinski 2024-01-31 08:52:15 UTC
[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 ***
Comment 4 Jakub Jelinek 2024-01-31 08:53:56 UTC
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.
Comment 5 Дилян Палаузов 2024-01-31 09:58:52 UTC
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
Comment 6 Andrew Pinski 2024-01-31 10:02:43 UTC
Because 64bit uses the SSE2 fp instructions rather than x87 fp instructions.
Comment 7 Jakub Jelinek 2024-01-31 10:26:23 UTC
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.
Comment 8 Дилян Палаузов 2024-01-31 14:32:28 UTC
-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
Comment 9 Jakub Jelinek 2024-01-31 14:35:50 UTC
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.
Comment 10 Jakub Jelinek 2024-01-31 14:38:19 UTC
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.
Comment 11 Jakub Jelinek 2024-01-31 15:53:22 UTC
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.
Comment 12 Eric Gallager 2024-02-07 05:55:26 UTC
(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?
Comment 13 Дилян Палаузов 2024-02-10 14:32:44 UTC
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 .