-Ofast produces incorrect code while using long double

Viewed 242
#include <cstdio>

int main(void)
{
    int val = 500;
    printf("%d\n", (int)((long double)val / 500));
    printf("%d\n", (int)((long double)500 / 500));
}

Obviously it should output 1 1. But if you compile it with -Ofast, it will output 0 1, why?

And if you change 500 to other values (such as 400) and compile with -Ofast, it will still output 1 1.

Compiler explorer with -Ofast: https://gcc.godbolt.org/z/YkX7fB

It seems this line causes the problem.

enter image description here

3 Answers

-Ofast

Disregard strict standards compliance. -Ofast enables all -O3 optimizations. It also enables optimizations that are not valid for all standard-compliant programs. It turns on -ffast-math, -fallow-store-data-races and the Fortran-specific [...]

-ffast-math

Sets the options -fno-math-errno, -funsafe-math-optimizations, -ffinite-math-only, -fno-rounding-math, -fno-signaling-nans, -fcx-limited-range and -fexcess-precision=fast.

This option causes the preprocessor macro __FAST_MATH__ to be defined.

This option is not turned on by any -O option besides -Ofast since it can result in incorrect output for programs that depend on an exact implementation of IEEE or ISO rules/specifications for math functions. It may, however, yield faster code for programs that do not require the guarantees of these specifications.

Conclusion: Don't use -ffast-math unless you are willing to get surprises like the one you've gotten now.

With -Ofast, -ffast-math is enabled, which can cause some operations to be calculated in a different and faster way. In your case, (long double)val / 500) can be calculated as (long double)val * (1.0L / 500)). This can be seen in the generated assembly when you compare -O2 and -Ofast for the following function:

long double f(long double a)
{
    return a / 500.0L;
}

The assembly generated with -O2 involves fdiv instruction, while the assembly generated with -Ofast involves fmul instruction, see https://gcc.godbolt.org/z/58VHxb.

Next, 1/500, that is, 0.002, is not representable by long double exactly. Therefore, some rounding occurs and, seemingly, in your case, this rounding happens to be down. This can be checked by the following expression:

500.0L * (1.0L / 500.0L) < 1.0L

which is evaluated as true: https://gcc.godbolt.org/z/zMcjxJ. So, the exact stored multiplier is 0.002 - some very small delta.

Finally, the result of the multiplication is 500 * (0.002 - delta) = 1 - some small value. And when this value in converted into int, it's truncated, therefore the result in int is 0.

Even if the shown program snippet has a 'problem', it is the wrong way to work with floating point numbers anyway.

You, more or less, ask the program if a floating point number has an 'exact value' - in this case of '1'. Ok, to be more precise - if the value is '< 1' or '>= 1' for a value which is 'around' 1 - so exactly around the dividing limit of the two answers. But as the others have already written (or can easily be found in wikipedia, ...) floating point numbers have just limited precision. So such deviations can and will happen.

So, coming to a conclusion: You should always use rounding when doing floating point to integer conversions, i.e. '(int) round (floating_point_value)'.

PS. Contrary what others maybe say or recommend - I don't see any problem with -ffast-math calculations at all. The only 'problem' would be when (bitwise) comparing the results of some program after letting it run on different computers.

I do all my (scientific) calculations with -ffast-math (actually -Ofast). But that has never been a problem so far - since I expect floating point numbers to have some rounding errors (this is true, regardless if using -ffast-math or not) - but that's all, as far as I know. Since I typically use 64bit floating points (double) this means, the calculations are precise to around 15 to 17 decimal digits - and the last (few) of them are inflicted with these inaccuracies - still giving me lots of 'accurate' digits - say - more than 13, depending on how complicate my calculations are.

Related