5

I attempted KR exercise 2.1 - determine the range of long variables by direct calculation.

#include <stdio.h>
#include <limits.h>

int main()
{
    unsigned short long_bits =  sizeof( long ) * CHAR_BIT;
    printf( "LONG_MAX   = %ld\n", ( 1L << ( long_bits - 1 ) ) - 1 );
    printf( "LONG_MIN   = %ld\n", ( -1 ) * ( 1L << ( long_bits - 1 ) ) );
    printf( "ULONG_MAX (1) = %lu\n", ( 1UL << long_bits ) - 1 );    // doesn't work

    printf( "ULONG_MAX (2) = %lu\n", 2*( 1UL << ( long_bits - 1 ) ) - 1 );  // work
    printf( "\n" );
}
  • ULONG_MAX (1) = 0 is wrong because the left shift overflow I suppose.
  • ULONG_MAX (2) = 18446744073709551615 seems correct by replacing the last left shift with a multiply by 2.

So it appears that left shift operator suffers from overflow, but multiplication does not? Does this intermediate calculation 2*( 1UL << ( long_bits - 1 ) ) promote to some type more than long? In my machine, long and long long are exactly the same ( 8 Bytes ).


Edit: As Lundin pointed out, all needed for ULONG_MAX is printf( "ULONG_MAX = %lu\n", ~0L );

Using left shift in this case caused UB, and multiply by 2 is potentially UB, too (although result of case 2 looks correct).

artm
  • 17,291
  • 6
  • 38
  • 54
  • 4
    As a rule of thumb: signed arithmetic suffers from (potential) overflow; unsigned arithmetic *cannot* overflow (since performed modulo 2^N). – Jens Dec 16 '15 at 10:34
  • Case 2 is not correct either... due to overflow as for case 1 – LPs Dec 16 '15 at 10:50
  • @LPs - the output of case 2 seems correct though. That's my question, overflow doesn't seem to happen in this case – artm Dec 16 '15 at 11:23
  • It is not correct because of `0x80000000=2147483648` that multiply by 2 should be `0x100000000=4294967296`. Then -1 should be `0xFFFFFFFF=4294967295` – LPs Dec 16 '15 at 11:27
  • @Jens On a C level, this has not really anything to do with arithmetic overflow. There's a special rule for shifts saying that "If the value of the right operand is negative or is greater than or equal to the width of the promoted left operand, the behavior is undefined." Regardless of signedness and value, so your rule of thumb does not apply. On the assembler level though, such shift operations might work just fine. – Lundin Dec 16 '15 at 11:54
  • 1
    @artm For unsigned types, the max value can easily be obtained like this: `unsigned some_t max = ~(some_t)0;` – Lundin Dec 16 '15 at 11:57
  • @Lundin Is there a case where the max value of some unsigned type is not found with `some_unsigned_type max = -1;`? – chux - Reinstate Monica Dec 16 '15 at 15:35
  • @Lundin - indeed cannot be simpler than that, `printf( "ULONG_MAX (1) = %lu\n", ~0L );` - thanks – artm Dec 17 '15 at 01:16

1 Answers1

4

The behaviour of left-shifting a value is only defined if the amount you are left-shifting by is less than the size of the type. Therefore, 1UL << long_bits is undefined behaviour and anything can happen, including dæmons flying out of your nose.

Let n be the number of bits in the type we are working with. In practice, depending on platform, there are two behaviours that happen in this case: Either, any left-shift by n or more produces 0 (as the whole bit-pattern is shifted out), or the left-shift is reduced modulo n, so a left-shift by n places behaves like a left shift by 0 places, yielding 1UL << 0 or 1.

fuz
  • 88,405
  • 25
  • 200
  • 352
  • Thanks. I agree case 1 is undefined. Is case 2 correct? `2*( 1UL << ( long_bits - 1 ) )` appears more than what a `long` type can hold, so I wonder why it works here – artm Dec 16 '15 at 11:25
  • 1
    @artm That case is undefined, too. Why don't you just do `(unsigned long)-1L`? – fuz Dec 16 '15 at 11:32