int has 32 bits, so you are allowed to shift a number of bits between 0 and 31. Only 5 bits are needed to described a number from 0 to 31, because 0 is
0000 0000 0000 0000 0000 0000 0000 0000
and 31 is
0000 0000 0000 0000 0000 0000 0001 1111
Only those 5 least-significant bits matter, so the compiler ignores the 27 most-significant bits. This behavior is equivalent to masking the number with & 0x1f
-1 in binary is
1111 1111 1111 1111 1111 1111 1111 1111
which is interpreted as 31 when only the 5 least-significant bits are taken into account. So (8 << -1) is the same as (8 << 31).
If you don't want to think in bit
patterns, a simpler way of dealing with this situation is to remember this pattern:
(8 << 36) == (8 << 4)
(8 << 35) == (8 << 3)
(8 << 34) == (8 << 2)
(8 << 33) == (8 << 1)
(8 << 32) == (8 << 0)
(8 << 31) == (8 << -1)
(8 << 30) == (8 << -2)
(8 << 29) == (8 << -3)
(8 << 28) == (8 << -4)
Note that 36 % 32 is 4, and 32 % 32 is 0, but when you get to negative numbers, the modulus operator no longer applies. Instead, counting down from zero is the same as counting down from 32. So -1 is 31, -2 is 30, etc.
All this is based on the fact that int has 32 bits. When the left-hand operand is a long, remember that long has 64 bits. Instead of the 5 least-significant bits mattering, the 6 least-significant bits matter. You're allowed to shift a long variable a number of bits between 0 and 63. Instead of masking the right-hand operand by 0x1f (31) you mask it by 0x3f (63)