Warm tip: This article is reproduced from serverfault.com, please click

-Ofast produces incorrect code while using long double

发布于 2020-06-03 13:21:09
#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

Questioner
Ghastlcon
Viewed
0
Daniel Langr 2020-06-03 23:21:51

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.