In C, why can I not cast pow(2,64) to an unsigned long?

104 views Asked by At

I'm trying to print the maximum value for an unsigned long in C (18446744073709551615 on my machine) without using values from limits.h and without bitwise operators (exercise 2-1 from K&R, using only information that has been mentioned in the book up to then). Obviously just as an exercise, not for any real world application. My idea (which worked for all other data types) was

printf("Unsigned long: Max: %lu\n", (unsigned long)(pow(2,sizeof(unsigned long)*8)-1));

But for some reason, casting the output of (pow(2,64)-1) to an unsigned long is displayed as 2^63, even though unsigned long is big enough to show pow(2,64)-1

I have tried

printf("Unsigned long: Max: %lu\n", ULONG_MAX); // works
printf("Unsigned long: Max: %lu\n", 2*(unsigned long)pow(2,63)-1); // works
printf("Unsigned long: Max: %.0f\n", pow(2,64)-1); // sort of works, but displays 18446744073709551616, i.e., 2^64 instead of 2^64-1
printf("Unsigned long: Max: %lu\n", (unsigned long)(pow(2,sizeof(unsigned long)*8)-1)); // does not work
printf("Unsigned long: Max: %lu\n", (unsigned long)pow(2,64)-1); //does not work

I don't understand why the last 2 options do not work. Thanks for your help!

2

There are 2 answers

1
DavidHoadley On BEST ANSWER

You have two problems here. The expression pow(2,64) can be represented exactly in a double, but when you typecast it with (unsigned long)pow(2, 64), you are effectively attempting to do the same thing as the expression 1 << 64. This number requires at least 65 bits to represent it. But, by the look of it, the unsigned long type on your system is only 64 bits long.

It is true that the result of the expression (1 << 64) - 1 can be represented in only 64 bits, but it's too late - you have used an intermediate expression that is not representable along the way.

Your other attempt, which is to use (unsigned long)(pow(2, 64) - 1) fails for a different reason. Although pow(2, 64) can be represented exactly in a double, pow(2, 64) - 1 cannot. This is because a double has only 53 bits of mantissa. Once again, this means that the result when cast to (unsigned long) is guaranteed to be wrong.

2
Support Ukraine On

Short answer: Undefined behavior.

Assuming unsigned long is 64 bit, this code

(unsigned long)pow(2,64)

has undefined behavior. The reason is that the integral part of the floating point number can't be represented in an unsigned long. For such situations the C standard specifies that the behavior is undefined.

This code

(unsigned long)(pow(2,sizeof(unsigned long)*8)-1))

which is the same as

(unsigned long)(pow(2,64)-1))

when unsigned long is 64 bit, is a bit more tricky.

It may have undefined behavior, it may have well-defined behavior.

It depends on the precision used when doing the floating point calculation of (pow(2,64)-1)). The C standard doesn't specify precision for floating point types so it's system dependent whether the math result can be represented in a floating number. If not, the floating point rounding mode being used may be important in this case.