Is this GCC optimization incorrect? Is this GCC optimization incorrect? linux linux

Is this GCC optimization incorrect?


It is a borderline optimization to transform the conversion to float of an application of the double-precision log to a float to an application of the single-precision log, but it can be argued to be acceptable.

Assuming that logf is correctly rounded and that the double-precision log is also correctly rounded or at least faithfully-rounded, the two computations will rarely differ. They can differ (for some rare inputs) because of double-rounding (in which “double” means “twice” and does not refer to the type). Double-rounding is statistically all the less significant that there are additional digits in the intermediate type's significand compared to the final type's significand (and this statistical argument is slightly rubbish from a mathematical point of view, but it “works in practice” for functions that have not been designed to be counter-examples). For didactic reasons, people (or Wikipedia) explain it with one or two extra digits of precision, but when you have 53 - 24 = 29 extra binary digits, it can be expected to happen as rarely as once in 229.

I am surprised by the optimization, and I would disturbed by it if I had written the code myself for an exhaustive search of double-rounding problems with log, but considering that the C++ standard does not mandate any level of accuracy from std::log, it is possible to consider it “not a bug”.


If, instead of log, we had been talking of one of the basic operations (such as *), then the transformation would have been incorrect, for a compiler that claims to provide IEEE 754 semantics, when it introduces visible changes. For a basic operation, the accuracy is specified indirectly by IEEE 754, and the specification leaves no room for variations.

It so happens that for the basic operations, there cannot be a visible change when floatOP(flx,fly) replaces (float)doubleOP((double)flx, (double)fly) (this thesis demonstrates this in chapter 6) , but there can be visible differences when the types are double and long double. This exact bug was recently fixed in Clang by Stephen Canon.


Yes, this optimisation is incorrect. log and logf are only required to be faithfully-rounded, so one could have

logf(4) = 0x1.62e42p+0log(4)  = 0x1.62e42fefa39efp+0

Changing the upconversion, log, and downconversion to a logf call may give incorrect results.

Pascal Cuoq's answer correctly points out that, if logf is correctly-rounded and log isn't garbage, then the results probably won't differ. However, logf on my platform is not correctly-rounded:

logf(0x1.306p-138)       = -0x1.7decc8p+6(float)log(0x1.306p-138) = -0x1.7decc6p+6mpfr_log(0x1.306p-138)   = -0x1.7decc6ff8a7a4a4450e9p+6

Thankfully, I'm unable to reproduce this "optimisation" with my gcc.


I've tried an equivalent C program:

#include <math.h>#include <stdlib.h>#include <stdio.h>static double dostuff(float in){  return log(in);}int main(int argc, char **argv){  if (argc < 2)    exit(EXIT_FAILURE);  float in = strtof(argv[1], NULL);  float out = dostuff(in);  printf("%f\n", out);  return 0;}

And found that gcc does not use logf even when using -Ofast. The only thing -Ofast enables is __log_finite() to be used. However, changing the return type of dostuff() to float does enable this optimization.