21
votes

According to C++03, 5.8/2, left-shifting is defined as follows:

The value of E1 << E2 is E1 (interpreted as a bit pattern) left-shifted E2 bit positions; vacated bits are zero-filled. If E1 has an unsigned type, the value of the result is E1 multiplied by the quantity 2 raised to the power E2, reduced modulo ULONG_MAX+1 if E1 has type unsigned long, UINT_MAX+1 otherwise.

What bothers me here is that unsigned types are explicitly mentioned yet signed types are ignored completely. Compare this to 5.8/3 which defines right-shifting:

The value of E1 >> E2 is E1 right-shifted E2 bit positions. If E1 has an unsigned type or if E1 has a signed type and a nonnegative value, the value of the result is the integral part of the quotient of E1 divided by the quantity 2 raised to the power E2. If E1 has a signed type and a negative value, the resulting value is implementation-defined.

In 5.8/3 both signed and unsigned are explicitly mentioned, even signed holding non-negative and signed holding negative values are mentioned separately.

AFAIK when something is not explicitly defined in C++ Standard the behavior is undefined. I've also seen this question, but it focuses on differences between C and C++ and doesn't seem to have an answer everyone would agree upon.

Is left-shifting a signed integer defined in C++03?

2
I would interpret that as an additional constraint on the numerical value of the result for unsigned numbers, with no similar constraint on the numerical value for signed numbers (because the standard doesn't presume two's-complement). Whereas right-shifting doesn't even fully define the bit pattern of the result for signed values. - John Calsbeek
It might be worth mentioning that C++11 has some explicit wording: "Otherwise, if E1 has a signed type and non-negative value, and E1 × 2^^E2 is representable in the result type, then that is the resulting value; otherwise, the behavior is undefined." - Michael Burr
@MichaelBurr: I would see that as an answer, or the seed of one at the very least. Many paragraphs were clarified in C++11 to better indicate their intent. - Matthieu M.

2 Answers

13
votes

5.8/2 says that it interprets it as a bit-pattern, which is only implementation dependent if for some reason your implementation doesn't use 2's complement, or if your compiler second-guesses you (they don't). C++11 is more explicit, but says the same thing.

Signed integers use what's known as 2's complement. Basically if you bit-shift a signed integer by 1, if it's positive and below 2^(bits - 2) it will work as if it were unsigned. If it is above that but positive, you will create a strange negative number that bears no relation to the original. If it is negative to begin with, you'll get possibly a negative, possibly a positive number.

For instance, if we have an 8-bit signed integer representing -1:

11111111 // -1

If we left-shift that we end up with

11111110 // -2

However, let's say we have -120

10001000  // -120

We shall end up with

00010000  // 16

Obviously that is not correct!

Continuing, using number 65:

01000001  // 65

Shifted left, this will become:

10000001  // -127

Which equates to -127.

However, the number 16:

00010000 // 16

Shifted left is

00100000 // 32

As you can see, it "sometimes works, sometimes doesn't" but usually works if your number is below 2^(bits-2) and sometimes but not usually if it is above -(2^(bits-2)). That is, to shift left by 1. To shift left by 2, take another bit off. Etc.

12
votes

I would like to add that the rules changed in C++11.

In C++11, signed shift left of a negative number is always undefined, even if the underlying machine defines it for values that are in range. It is not implementation-defined, it is undefined. This means that if you do this, the compiler is free to do anything it wants, including delete a bunch of your code unexpectedly. This is in contrast to signed shift right of negative numbers, which is implementation-defined, meaning that its result depends upon the machine type.

Clang's -fsanitize=undefined mode catches attempts to shift left negative numbers.