4

I came across this unusual bug while working on some bitwise exercises. When the output of pow() was coerced to an unsigned int, the result of pow() called with a variable as the exponent became zero, while the result when the exponent was a literal integer was coerced normally to 0xFFFFFFFF (2^32 - 1). This only happens when the value is excessively large, in this case 2^32. The type of the variable used as the exponent argument does not seem to affect this result. I also tried storing the output of the both calls to pow() as doubles, and then applying the coercion when referencing the variables; the disparity persisted.

#import <math.h>

int main (void) {
  int thirtytwo = 32; // double, unsigned, etc... all yielded the same result

  printf("Raw Doubles Equal: %s\n", pow(2, 32) == pow(2, thirtytwo) ? "true" : "false"); // -> true
  printf("Coerced to Unsigned Equal: %s\n", (unsigned) pow(2, 32) == (unsigned) pow(2, thirtytwo) ? "true": "false"); // -> false

  return 0;
}

Out of curiosity, I ran the same code through clang/llvm, and obtained a different result: regardless of whether the exponent was a variable, coercing the result to an unsigned int yielded zero (as expected).

Edit: The maximum 32-bit unsigned integer is 2^32 - 1, so neither coerced output is actually correct. My mistake was overflowing the integer size limit. Why the gcc essentially rounded down to the maximum integer value is an interesting curiosity, but not of particular importance.

Alex Patch
  • 125
  • 1
  • 9

1 Answers1

3

The compiler will use constant-folding to replace the pow(2, 32) with a constant result; the pow(2, thirtytwo) will be calculated during the run-time. C11 actually allows a compile-time calculation to be more precise than a corresponding run-time calculation (C11 6.6p5):

If a floating expression is evaluated in the translation environment, the arithmetic range and precision shall be at least as great as if the expression were being evaluated in the execution environment.

For example GCC is known to do this. Thus the C standard does not really require the first one to print true (and in practice this does occur in some implementations).


As for why the second one is printing false: it is because pow(2 ^ 32) is not representable in an unsigned int of 32 bits. C11 6.3.1.4:

When a finite value of real floating type is converted to an integer type other than _Bool, the fractional part is discarded (i.e., the value is truncated toward zero). If the value of the integral part cannot be represented by the integer type, the behavior is undefined.

Thus that the second one prints false is due to undefined behaviour. Unlike narrowing integer conversions to unsigned integers, the overflow is explicitly undefined for conversions from floating point to even unsigned integers.

It is remarkable that I cannot get my GCC 6.2.0 to warn about compile-time undefined behaviour for (unsigned int)pow(2, 32). (I tried with -lm -Wall -Werror -pedantic -ubsan -Wfloat-conversion -Wconversion -Wextra -std=c11 and nothing is output).

Community
  • 1
  • 1
  • In that case, don't you find it surprising that the runtime result is apparently more accurate? I don't have a machine handy, just an android, but i wonder how `(unsigned) pow(2, 32)` is being compiled. – rici Mar 28 '17 at 07:20
  • @rici - I personally am not. If GCC wishes to optimize build times, it may choose to do so at the expense of floating point constant folding precision, among other ways of course. – StoryTeller - Unslander Monica Mar 28 '17 at 07:23
  • @storyteller: i agree that the result is compliant with the standard and you are free to be unsurprised; I deliberately didn't respond to your comment on the OP. But this answer says that compile-time is more accurate, so I asked Antti. – rici Mar 28 '17 at 07:27
  • @rici answered that one, the second one prints false because of UB. There is no mod-arithmetic when converting a floating point type into an unsigned integer. – Antti Haapala -- Слава Україні Mar 28 '17 at 07:32
  • 1
    Since the UB happens in compile time that's all answer that is needed (the UB allows the result of the first comparison to be whatever too). However I agree with @rici that it would be reasonable for the compiler to warn - it warns for other similar faults, so why not warn here? – skyking Mar 28 '17 at 07:38
  • @antti, ah, you're right. I misremembered that clause. It still seems to violate the POLA – rici Mar 28 '17 at 08:07
  • @skyking maybe because the folding is done after the checks. So it won't detect it. – Kami Kaze Mar 28 '17 at 08:27