Created attachment 47454 [details]
test case with return value 0 or 8 expected, returns 12
GCC treats floating-point constants suffixed with 'f' as double, when compiled with either -std=c11 or -std=c18. The attached test case illustrates this problem: There are four functions f0, f1, f2, and f3. The first three functions f0, f1, and f2 should all compile to the same code, f4 is different and there for reference.
It turns out that with -std=387 and -m32 (or with -mfpmath=387), GCC compiles f0 and f1 to equivalent asm but not f2. Instead, it creates the same asm for f2 as for f3, involving some loading a constant into an FPU register using double precision. Apparently, writing '0.1f' is not enough to convince GCC that the constant is single precision.
This can be seen with the attached program:
$ gcc -O -m32 testcase.c && ./a.out; echo $?
$ gcc -std=c11 -O -m32 testcase.c && ./a.out; echo $?
The result should be either 0, 8, or 15, depending on how exactly the decimal constant 0.1 is rounded to binary floating-point, but it should never be anything else.
The bug seems to be independent of optimization; it happens with -O0, -O1, -O2, -O3, -Os, and -Og. I can trace it from GCC 9.2 all the way back to 4.8.4. It can be triggered on 387 (with -m32 or -mfpmath=387).
Does -ffloat-store help?
See 323 also.
No, -ffloat-store does not help. And this has little if anything to do with 323, imho.
The asm generated for f3 is instructive: There, GCC wants to load a double constant 0x3fb999999999999a into the 387 FPU, which is 0.10000000000000000555... The single-precision constant t passed as argument to f0...f3 is 0x3dcccccd, which is 0.10000000149... So, it's no wonder that f3 returns 1; after all 0.10000000149 > 0.10000000000000000555.
The questions are: Why does the code generated for f2 try to load a double constant even if it is explicitly suffixed 'f'? Why should casting to (float) help? Why does std=c11 or c18 change the game?
The value of FLT_EVAL_METHOD applies to constants as well as to
operations. That is, when FLT_EVAL_METHOD == 2, 0.1f has the precision of
long double but the semantic type of float, and 0.1 has the precision of
long double but the semantic type of double. An explicit cast to float
removes excess precision. This is as specified in the C standard.
I don't know about other archs, but on amd64, GCC emits comiss insns (SSE2 single-precision scalar compare) for f0, f1, and f2 and comisd insns (SSE2 double-precision scalar compare) for f3. So this is evidently not a problem on amd64 (i.e. it does not depend on how the decimal-to-binary rounding was done for the value 0.1). Conclusion: Ignoring the 'f' suffix seems to be specific for 387.
Most architectures use FLT_EVAL_METHOD == 0. It's specific to x87 (and
older m68k) that FLT_EVAL_METHOD == 2 because x87 doesn't support direct
arithmetic on float or double. Lack of direct float and double arithmetic
requires FLT_EVAL_METHOD == 2 and FLT_EVAL_METHOD == 2 requires
interpreting floating constants to the range and precision of long double,
whatever their semantic type.
(In reply to firstname.lastname@example.org from comment #3)
> This is as specified in the C standard.
I guess you are referring to the C18 section
18.104.22.168 (2) "The values of floating operands and of the results of floating expressions may be represented in greater range and precision than that required by the type; the types are not changed thereby. --footnote: The cast and assignment operators are still required to remove extra range and precision."
in conjunction with
6.6 (5) "An expression that evaluates to a constant is required in several contexts. If a floating expression is evaluated in the translation environment, the arithmetic range and precision shall be at least as great as if the expression were being evaluated in the execution environment. --footnote: The use of evaluation formats as characterized by FLT_EVAL_METHOD also applies to evaluation in the translation environment."
If so, I propose to close this bug (as __FLT_EVAL_METHOD__ is indeed 2).
(In reply to email@example.com from comment #5)
> Lack of direct float and double arithmetic requires FLT_EVAL_METHOD == 2
No, FLT_EVAL_METHOD could also be negative, in which case GCC would be allowed to evaluate floating-point constants of type float in whatever range and precision it wishes.
(In reply to firstname.lastname@example.org from comment #6)
> 6.6 (5) "An expression that evaluates to a constant is required in several
> contexts. If a floating expression is evaluated in the translation
> environment, the arithmetic range and precision shall be at least as great
> as if the expression were being evaluated in the execution environment.
> --footnote: The use of evaluation formats as characterized by
> FLT_EVAL_METHOD also applies to evaluation in the translation environment."
This is about constant expressions and is not applicable to constants. See 22.214.171.124.2p9 for constants.
due to the part about floating/double constant suffixes, I think bug 84717 is related
This has nothing to do with 84717. The present bug is simply invalid; GCC
is acting as specified in the C standard for excess precision. 84717 is
arguably a legitimate issue about lack of documentation for an extension
(albeit an extension taken from an old TR).