GNU GCC does not round floating-point divisions to the nearest value
lemire.me
lemire.me
https://www.kotaku.com.au/2018/06/the-mario-64-trick-that-ta...
If you're going to watch one commentated "tool-assisted" run, this is it.
(I grew up watching SpongeBob, if that matters)
..dark energy contributes 68% of the total energy..
so if anyone complains about the shoddy quality of x87 floats, point them to the universe and tell them it could be worse.I skimmed over that code and it seems that hack is required because a) you're not happy with what the compiler does with a double b) insisting on using a "double" type.
It's easy to get your own semantics for numbers in C, it's not easy to do so while insisting on using C's own types whose behavior are contrary to what you want.
If you want your own floating point semantics why not make a struct with a significand & base and pass that around? Something like that is how established numbers-in-C-without-native-C-semantics libraries do it, e.g. GMP.
Flags, pragmas, function attributes, there are so many ways this could be communicated to the compiler. If this behavior is not a bug, then having a way to change it should be a feature.
That doesn't mean it's necessarily the right thing to do though.
Clang can be made generate the same code as the 'broken' gcc output by declaring the variables as 'long double' instead of 'double'. So I think this is where the issue lies?
Edit: godbolt link requiring large monitor: https://godbolt.org/z/tFiZwW
Which step in this (very naive) argument is wrong?
1.23456789 - 1.23456788 = 0.00000001 = 1 * 10^-8
So here we’ve gone from 9 significant figures down to 1. This phenomenon will make a naïve Taylor series approximation of e^x be very inaccurate for negative x, due to the sign alternating between positive and negative on every term, causing a lot of catastrophic cancellation.
Imagine the analogue for integers. You might use code like this to round down to a multiple of 2:
int i = 7;
int j = (i / 2) * 2;
If a compiler optimized that to j = i then that's more mathematically accurate but less correct.The behavior appears to be consistent with the standard. Keep in mind that GCC needs to provide the same result irrespective of the architecture it runs on and one reason to use GCC for floating point is to take advantage of dedicated hardware. And the reason to use floating point is speed not precision (at least when using decimal floating point). If precision is critical then arbitrary precision or fixed point is the way to go. Normally it isn’t that critical.
A few paragraphs later, it adds, “There is not complete agreement on what operations a floating-point standard should cover. In addition to the basic operations +, -, × and /, the IEEE standard also specifies that square root, remainder, and conversion between integer and floating-point be correctly rounded. It also requires that conversion between internal formats and decimal be correctly rounded (except for very large numbers).”
I will admit that I worked as a programmer for several years without knowing that. I learned it from a former physics professor who has now moved on to become a quant with a hedge fund. He usually didn’t worry much about it, but when he needed to be sure a particular calculation was as accurate as possible, he’d start counting individual operations and stay away from particular function calls.
Reading down thread, I see that the behavior might be related to x87 since the build script references i386...which makes IEEE 754 conformance moot. The methods of achieving conformance when targeting i386 are described in the GCC documentation.
A decimal equivalent would be like saying 200/3 = 66, so rounded towards zero instead of nearest. That is usually not problematic, but it can be in some situations, and in some of those, rounding to nearest can mitigate (for example, when you sum a large number of division results).
The author points out that the behaviour should be configurable, but gcc doesn't seem to honor that.
The only way of doing FP on x86 and keeping your sanity is by making sure you use 32/64 bit floats 100% of the time. That is: use SSE registers and never the 80-bit x87 arithmetic.
I thought compilers for 64 bit programs these days did exactly this. I know the .NET Jit does for example.
In contrast, the 32-bit C ABI passes floating point args in x87 registers, and there are 32-bit CPUs without SSE2, so x87 is used by default.
x87 is, in quite a few respects, awful.
A few drivers use floating point values/calculations. Which way are they rounded when expressions are folded at compile time? Does that match what would happen at runtime?
The x86 kernel also disables SSE generally (kernel_fpu_{begin|end} via -mno-{x87|sse|sse2|....}. This generally lowers the overhead of context switching as there's fewer registers to save+restore, nowadays the SIMD vectors on x86 are huge.
Further, it uses an 8B stack alignment, which makes it so that when FP is used, the compiler cannot select instructions that require 16B aligned operands to be loaded/stored from/to the stack.
Finally, -Ofast implies -funsafe-math. And that's not "fun safe math." Actually had folks at work use "-Ofast" because "why wouldn't I want my code to be fast?" then ask why their reciprocals were also wrong.
That looks like an ARM issue.
> Further, it uses an 8B stack alignment, which makes it so that when FP is used, the compiler cannot select instructions that require 16B aligned operands to be loaded/stored from/to the stack.
On x86_64, on a recent GCC, this all works correctly. If you end up with a 16-byte-aligned variable (like an SSE vector) on the stack, GCC will correctly align the stack in the prologue. On older GCCs, there were a serious of obnoxious I-know-better-than-you-isms in the command line option handling that made this all malfunction.
I seem to be missing the Share button on godbolt.org, so no link. But you can test with:
typedef int v4si __attribute__ ((vector_size (16)));
v4si *v;
int func(void)
{
v4si z;
v = &z;
return 0;
}
Add -mpreferred-stack-boundary=3 to the options.I originally had the wrong answer here, but I'm including below for historical purposes and embarrassment. Re-reading on my laptop meant I didn't misread/read-the-expected?
Basically by default when compiling for IA32 gcc assumes that there is no SSE unit, and so must use the x87 fpu.
Alas the x87 unit doesn't have either 32 or 64 bit ieee floating point types, so the calculation at runtime must be rounded to float/double at the end of the computation. That results in a double round that is incorrect.
Minor additional comment - x87's reduced precision modes (that reduce mantissa precision to ieee754-32/64) don't reduce the exponent size so it's conceivable you could get multiple-rounding derived errors even then. But no one would be toggling x87 state like that anyway, so theoretically not something that would matter in most cases.
Old incorrect answer:
On ia32 long double is the x87 80bit ieee754 numeric type.
On x86_64 on windows, and maybe Linux, long double is just a 64bit ieee float.
So my guess is that GCC computes constant evaluation of floating point expressions in “long double” for maximum precision, and then rounds to the required precision at the end. This is not valid going from 64->32 bit floats and you could probably induce this bug if you tried hard enough.
Anyway it is not sound to round twice in any numeric operation, and by doing the calculation at one precision and then rounding that to the required precision that is what gcc is doing.
This is a bug in GCC, floating point is well defined, there isn’t randomness to it, and they’ve introduced additional rounding that is not valid.
Except the C standard has a way to communicate how the extra rounding is being introduced, and it's not random (for C, not C++).
There is simply no way around this issue with the x87 instruction set, GCC (again only for C only, not C++) implements FLT_EVAL_METHOD==2 which is the most sensible that can be implemented with decent performance and without breaking 80-bit long double.
If you are sure you don't need long doubles, use -mpc64.
If you are constrained to x87 your only option is multiple roundings which is bad. Ideally you'd just keep processing in the x87 stack as long as possible, but that would result in a different version of "incorrect" results.
Honestly if you're a compiler required to use x87 with non-80bit floating point I'm not sure how you can do the right thing :-/
I don't know what gcc has but llvm has a full software floating point implementation to handle cross compiling so it can match native arithmetic.
That's the best they can do.
> The long double type uses a 15 bit exponent, a 64-bit mantissa with an explicit high order significant bit and an exponent bias of 16383.
So it is an 80-bit float.
For example on osx it is 80bit ieee, but on win64 it is a 64bit ieee.
That ABI difference means that having the same instruction set is not the only thing that matters.
EDIT: GCC gets it wrong with -O0 (when it's evaluated at runtime) and right with -O2 (when it evaluates it at compile time)
Clang appears to get it right when evaluated at compile time or runtime.
Clang uses the divsd instruction; GCC uses the fdiv instruction – so this really is SSE vs x87 FPU.
You can configure gcc to use sse math with the -mfpmath=sse instruction. The author states that gcc still gets it "wrong" but he simply is not correct. gcc will use divsd and gets 0.501782303180000055.
gcc defaults to use the x87 fpu instead of sse because not all 32 bit x86 cpus have SSE instructions. It's a safer default. When compiled in 64 bit mode, it uses up to SSE2 instructions, because all x86_64 CPUs have SSE2.
If you are still writing 32 bit x86 C code, it's probably some embedded or legacy CPU, and it's wrong to assume SSE instructions are available. So GCC correctly defaults to using FPU instructions instead of SSE instructions on 32 bit x86.
Javascript and python will basically never be run in embedded environments, and their legacy environments probably aren't that legacy that SSE instructions aren't available. So it's reasonable to default to SSE.
JavaScript JITs blindly use SSE without checking if it is available. Bytecode interpreters behave the same as above.
Edit: Also, curiously, things worked as expected for me using -std=gnu11. This was a fairly old version of GCC though, things might have changed.
[1]:https://www.khronos.org/registry/OpenCL/specs/2.2/html/OpenC...
See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=323 for the gory details; clang works the same as GCC's C++ frontend and the same that GCC <4.5 used to behave for C.
In unbiased rounding, .5 rounds to even, rather than always up, so 1.5 rounds to 2 but 4.5 rounds to 4
https://www.gnu.org/software/gawk/manual/html_node/Round-Fun...
It also matters a lot whether the error is predictable and possible to compensate for.
> someone defended gcc
> someone attacked it
> someone who just started learning C or C++ made a comment
> someone mentioned clang did it right
> someone mentioned Rust