Hacker News new | ask | show | jobs
by chengsun 3211 days ago
This optimisation is actually fully correct if int is 32-bits wide. This is because doubles have a 52-bit mantissa, which means that they can exactly represent all integers up to 2^53 in magnitude. However you are right that the optimisation would not be valid, had int been 64-bits in width.
3 comments

Since it's a *=, I'd expect the operation to occur in double, quantize accordingly, and handle overflow as double-to-int would, rather than int multiplication would.

For 10.0 specifically, double is more than large enough to, as far as I know, result in the same output. For arbitrary double literals, I wouldn't expect this to be the case.

Note: overflow cast from double to int is undefined, last I looked, so one could argue that the numerical regions for which quantization would occur are outside of the practical range of this chunk of code.

It is still valid after constant lifting or if the range of the long int in question vs can be proven to be representable in 53 bits or less. (E.g. using the polyhedral loop optimizer gcc has.)
Right, though some side effects (e.g. Status registers) might be affected. (One reason strict modes blow floating point optimization, generally.)
Indeed. Anyway, this guarded version shows the same behavior and, if I got everything right, cannot invoke undefined behavior for any values of p2:

    #include <limits.h>

    int N;
    int fn5(int p1, int p2) {
      int a = p2;
      if (N && INT_MIN / 10.0 <= a && a <= INT_MAX / 10.0)
        a *= 10.0;
      return a;
    }
GCC still does the multiplication as double, Clang still does it as int.

(And both evaluate the guards as int, so there.)

I'd be more curious what Clang did for values that would result in different behaviors for some ranges of a. I understand that there are limits, and that there is range checking in literals for escalation to, for instance, 64-bit values, but it seems like an optimization that is:

A) fraught with edge-case peril

and

B) easily accounted for by the developer (e.g. write "a *= 10;")...

> I'd be more curious what Clang did for values that would result in different behaviors for some ranges of a.

I don't think I understand what you mean. Can you give an example?

As for "the developer should write what they mean", one standard answer from compiler developers is along the lines of "but this code might not be hand-written, it might come from a primitive code generator". You can decide whether that convinces you or not ;-)

Yeah. I'm partially unconvinced. That is to say that, if the compiler Dev wants to take on the completeness responsibility, cool. If not, I'm okay with that, too. Adding the optimization and screwing up the edge cases is the only way to end up on my bad side.

An example number might be 8388608.0 (or 2^23). Hell could have broken out more than four million integer values before that, but it's as nice a test as any (except maybe the exact edge).

There could also be interesting cases of compound arithmetic literal statements. And then, of course, there are other side-effect considerations (e.g. the x87 opcode register).

I'll stick to getting my literal types right in the first place, but, as stated above, it doesn't feel like a real miss to me. Either a dev or a code generator wrote ".0". I'm inclined to respect that to some degree.

Probably because I'm old.

I'll compile this in gcc and clang and see what shakes out. My bet is that clang does the right thing once the value is out of safe range.