Embercode
✕
C++ OperatorsLesson 11 of 14

45 min read

Shifts on negative and unsigned values

Why this matters

Shifts move the bits of a number to the left or to the right. Programs use them to build bit masks, to pack several small values into one number, and to read those values back. A shift also looks like multiplying or dividing by a power of two, and for unsigned numbers that comparison is a fair guide.

For signed numbers, the rules are less obvious. A wrong guess gives a wrong number and no error. The packing program below is a small example. It takes the top byte of a 32-bit value, and for a signed int it prints -16 when the byte is really 240.

This lesson covers what the C++ standard says about shifts on unsigned values, on signed values, and on shift counts that are too large. The bit masks lesson earlier in this module shows & and |, and the popcount lesson shows how to count set bits. This lesson is about moving bits.

Shifts in one minute

A shift moves every bit in a number by a fixed number of places.

  • x << k moves every bit k places to the left. The right side fills with zeros.
  • x >> k moves every bit k places to the right. The bits that fall off the right end are lost.

The number 3 is 0011 in 4-bit binary. Shifting it left by 2 gives 1100, which is 12. Shifting it right by 1 gives 0001, which is 1.

Shifting left by 3 multiplies an unsigned value by 8, because 8 is 2 to the power of 3. The number 5 is 0101. Shifting it left by 3 gives 0101000, which is 40, the same result as 5 * 8. The shift is useful when the bit positions matter, for example when a number is a set of flags. For plain multiplication, 5 * 8 is clearer, and compilers usually produce the same code for both.

For unsigned numbers, each left shift by 1 doubles the value, as long as no bit falls off the top. Each right shift by 1 halves the value and rounds down. The rest of this lesson is about the cases where that simple rule stops working.

The examples use std::uint8_t, which holds 8 bits, and int, which holds 32 bits on the g++ build used for these examples. The C++ standard promises only that int has at least 16 bits, so the exact width depends on the compiler.

Unsigned shifts

An unsigned type has a fixed number of bits and no sign. Shifts on unsigned values are well defined as long as the shift count is in range, which is covered later in this lesson.

Bits pushed past the top are lost

A left shift can push a set bit past the top of the type. The C++ standard says the result is the value modulo 2 to the power of the type's width, so the bits that fall off are removed. The next program shows this with 8-bit values:

#include <cstdint>
#include <iostream>
 
int main() {
    // Unsigned left shift: bits that leave the top are dropped.
    std::uint8_t u8 = 0b1000'0001;
    std::uint8_t shifted = static_cast<std::uint8_t>(u8 << 1);
    std::cout << "uint8 0x81 << 1 stored in uint8 = " << static_cast<int>(shifted) << "\n";
    return 0;
}

The value 0x81 is 1000 0001. Shifting left by 1 gives nine bits, 1 0000 0010. The top 1 has nowhere to go, so only the low eight bits are kept: 0000 0010, which is 2. The program prints:

uint8 0x81 << 1 stored in uint8 = 2

The static_cast to std::uint8_t is there to store the result back in an 8-bit variable. The next part explains why it is needed.

Small types are widened first

The left operand of << is widened before the shift. This is called integer promotion: in an expression, a type smaller than int is converted to int. For std::uint8_t, the shift therefore happens in a 32-bit int, and the bits are not lost until the result is stored back into an 8-bit variable.

#include <cstdint>
#include <iostream>
 
int main() {
    std::uint8_t x = 200;
    std::cout << "200 << 1 as int = " << (x << 1) << "\n";
    std::cout << "stored in uint8 = " << static_cast<int>(static_cast<std::uint8_t>(x << 1)) << "\n";
    return 0;
}

Output:

200 << 1 as int = 400
stored in uint8 = 144

The shift of 200 by 1 gives 400, which does not fit in 8 bits. The first line shows the value that the expression really has. Stored in an 8-bit variable, only the low 8 bits remain. 400 minus 256 is 144, which is the second line. A program that prints x << 1 without the cast shows 400, and a program that stores it in a std::uint8_t shows 144. Pick the one that matches what you need, and write the cast so a reader can see it.

Right shifts fill with zeros

For an unsigned value, a right shift fills the empty bits on the left with zeros. The next program shifts the value 0x80000000, which has only the top bit set:

#include <cstdint>
#include <iostream>
 
int main() {
    std::uint32_t big = 0x8000'0000u;
    std::cout << "0x80000000 >> 4 = 0x" << std::hex << (big >> 4) << std::dec << "\n";
    return 0;
}

Output:

0x80000000 >> 4 = 0x8000000

The top bit moves four places to the right, to bit 27. The value is 0x08000000, and the program prints it in hexadecimal without the leading zero, so it shows 0x8000000.

Signed right shifts on negative values

A signed int uses two's complement, where the top bit is the sign bit. Negative numbers have that bit set. Before C++20, the standard left the result of right-shifting a negative value up to the compiler. C++20 defines it: the shift is an arithmetic shift, which means the new bits on the left copy the sign bit. The programs in this lesson use -std=c++20 for that reason.

#include <iostream>
 
int main() {
    int neg = -8;
    std::cout << "-8 >> 1 = " << (neg >> 1) << "\n";
    std::cout << "-1 >> 3 = " << (-1 >> 3) << "\n";
    std::cout << "-7 >> 1 = " << (-7 >> 1) << "\n";
    return 0;
}

Output:

-8 >> 1 = -4
-1 >> 3 = -1
-7 >> 1 = -4

The value -8 is ...1111 1000 in 32-bit two's complement. Shifting right by 1 and copying the sign bit into the new top bit gives ...1111 1100, which is -4. The value -1 is all ones, and shifting it copies the ones back in, so it stays -1.

The table below shows the same operations in the rule before C++20 and in C++20:

Operation on a negative signed intBefore C++20C++20
-8 >> 1Up to the compiler (implementation-defined)-4, the sign bit is copied
-3 << 2Undefined behaviour-12, two's complement result

The value -7 is the one to watch. Shifting right gives -4, not -3. The shift rounds toward negative infinity, which is the next lower whole number. Division with / rounds toward zero, so -7 / 2 is -3. The floor division lesson earlier in this module covers this difference in detail.

Signed left shifts on negative values

In C++20, a left shift of a negative signed value is also defined. The result is the two's complement bit pattern shifted left, as long as the result fits in the type:

#include <iostream>
 
int main() {
    std::cout << "-3 << 2 = " << (-3 << 2) << "\n";
    return 0;
}

Output:

-3 << 2 = -12

The value -3 is ...1111 1101. Shifting left by 2 gives ...1111 0100, which is -12.

A positive value that overflows

Shifting a positive signed value so that the result goes past the sign bit is different. The next program shifts 1 left by 31 in a 32-bit int. The result 2147483648 is larger than the largest int, which is 2147483647. Before C++20 the behaviour is undefined. C++20 wraps the value modulo 2 to the power of 32, which gives -2147483648:

#include <iostream>
 
int main() {
    int one = 1;
    unsigned int one_u = 1;
 
    // Signed: 1 << 31 does not fit in int. Undefined before C++20, -2147483648 in C++20.
    std::cout << "1 << 31 (signed) = " << (one << 31) << "\n";
 
    // Unsigned: 1u << 31 is defined and gives the top bit.
    std::cout << "1u << 31 = " << (one_u << 31) << "\n";
    return 0;
}

Output from a run with g++ -std=c++20 -Wall:

1 << 31 (signed) = -2147483648
1u << 31 = 2147483648

The first line is defined in C++20, and the undefined behaviour checker did not report it. Compiled as C++17 or older, the same line is undefined, so another compiler may print something else. For a single top bit, use an unsigned type: 1u << 31 is defined in every version and gives 2147483648.

Shift counts must be in range

The number on the right of << or >> is the shift count. For a type with N bits, the count must be at least 0 and less than N. A count of 32 for a 32-bit int is outside that range, and so is a negative count. Both are undefined behaviour. The program below uses a count computed at run time, so the compiler cannot reject it:

#include <iostream>
 
int main(int argc, char**) {
    int count = 31 + argc;  // 32 at run time, computed so the compiler cannot reject it
    int one = 1;
 
    // Shift count equal to the width of int is undefined behaviour.
    std::cout << "1 << 32 (signed) = " << (one << count) << "\n";
    return 0;
}

A normal run prints:

1 << 32 (signed) = 1

This result matches a processor that uses only the low 5 bits of the count, so 32 became 0 and the result was 1. The standard does not promise that. A build with the undefined behaviour checker stops with a message:

sh2.cpp:8:48: runtime error: shift exponent 32 is too large for 32-bit type 'int'

If the count is a constant that the compiler can see, it can report the problem at compile time. This line:

int wide = 1 << 40;

gives this warning from g++ -std=c++20 -Wall:

warning: left shift count >= width of type [-Wshift-count-overflow]

The -Wall flag did not warn about 1 << 31 in the previous section, so do not rely on warnings to catch every bad shift.

Worked example 2: building and testing masks

A mask is a number whose bits select positions. The usual way to make a mask for position k is 1u << k. The u makes the 1 an unsigned int. The reason is the overflow shown earlier: with a signed 1, position 31 does not fit in an int, and before C++20 that is undefined.

The program below makes a mask for each position in a 4-bit value, tests each bit, and then uses the popcount idea from the previous lesson. The count function is the one from the popcount lesson, with a 64-bit parameter.

#include <bit>
#include <cstdint>
#include <iostream>
 
int count_bits(std::uint64_t n) {
    int count = 0;
    while (n != 0) {
        n &= n - 1;
        ++count;
    }
    return count;
}
 
int main() {
    unsigned int value = 0b1010;
    for (unsigned int k = 0; k < 4; ++k) {
        unsigned int mask = 1u << k;
        std::cout << "bit " << k << " of 1010 is " << ((value & mask) != 0) << "\n";
    }
 
    std::cout << "count_bits(0) = " << count_bits(0) << "\n";
    std::cout << "count_bits(1) = " << count_bits(1) << "\n";
    std::cout << "count_bits(~0ull) = " << count_bits(~0ull) << "\n";
    std::cout << "count_bits(0xFF) = " << count_bits(0xFF) << " std = " << std::popcount(0xFFull) << "\n";
 
    unsigned int top = 1u << 31;
    std::cout << "1u << 31 = " << top << "\n";
    return 0;
}

Output:

bit 0 of 1010 is 0
bit 1 of 1010 is 1
bit 2 of 1010 is 0
bit 3 of 1010 is 1
count_bits(0) = 0
count_bits(1) = 1
count_bits(~0ull) = 64
count_bits(0xFF) = 8 std = 8
1u << 31 = 2147483648

The test (value & mask) != 0 is true when the bit is set. The value 0b1010 has bits 1 and 3 set, so the loop prints 1 for those positions and 0 for the others.

The last line is the case that needs the u. With 1u, the mask for position 31 is the top bit of an unsigned 32-bit value, and the program prints 2147483648. Replacing 1u with 1 would make that line undefined before C++20.

Worked example 3: packing three colour parts

A common use of shifts is to store several small values in one number. A colour is often stored as three 8-bit parts for red, green and blue. This program packs them into one 32-bit unsigned number, unpacks them again, and then runs the same extraction on a signed int:

#include <cstdint>
#include <iostream>
 
int main() {
    // Pack three 8-bit colour parts into one 32-bit unsigned number.
    std::uint32_t red = 0xAB, green = 0xCD, blue = 0xEF;
    std::uint32_t packed = (red << 16) | (green << 8) | blue;
    std::cout << std::hex << "packed = 0x" << packed << std::dec << "\n";
 
    // Unpack: shift right, then keep the low 8 bits with a mask.
    std::cout << std::hex
              << "red   = 0x" << ((packed >> 16) & 0xFF) << "\n"
              << "green = 0x" << ((packed >> 8) & 0xFF) << "\n"
              << "blue  = 0x" << (packed & 0xFF) << std::dec << "\n";
 
    // Same extraction on a signed int whose top bit is set.
    int signed_packed = static_cast<int>(0xF0000000u);
    std::cout << "signed top byte (>> 24) = " << (signed_packed >> 24) << "\n";
    std::cout << "signed top byte masked  = " << ((signed_packed >> 24) & 0xFF) << "\n";
 
    std::uint32_t unsigned_packed = 0xF0000000u;
    std::cout << "unsigned top byte       = " << (unsigned_packed >> 24) << "\n";
    return 0;
}

Output:

packed = 0xabcdef
red   = 0xab
green = 0xcd
blue  = 0xef
signed top byte (>> 24) = -16
signed top byte masked  = 240
unsigned top byte       = 240

The unpacking works for unsigned values. The top byte of 0xF0000000 is 0xF0, which is 240. The shift moves it to the bottom, and the & 0xFF keeps only those 8 bits.

The signed line shows the trap. The int holds the same bit pattern, but its top bit is the sign bit. The right shift copies that sign bit into the new top bits, so the result is -16 instead of 240. The mask & 0xFF fixes it, and the third line prints 240 again. The unsigned version needs no mask for the top byte, because the shift fills with zeros.

The rule to take from this example: when a number holds packed bit fields, use an unsigned type.

Common mistakes

Reading a packed field from a signed int

This is the mistake from the packing example. Here is the output with the signed value on its own:

signed top byte (>> 24) = -16

The fix is to use std::uint32_t for the packed value, or to mask the result with & 0xFF as the example does.

Writing 1 << bit for a large bit position

With a signed 1, 1 << 31 does not fit in int. It is undefined before C++20. In C++20 it gives -2147483648, which can slip past a quick check:

1 << 31 (signed) = -2147483648

Write 1u << bit instead. The result type is then unsigned and the value is defined for every position from 0 to 31.

Using a shift count equal to the width

A count of 32 for a 32-bit type is undefined, even if the variable is unsigned. The limit comes from the width of the left operand, not from the type of the count. The output on this build was 1, not 0, which is one more reason not to depend on what a build does:

1 << 32 (signed) = 1

If you need to shift by a count that can reach the width, check it first. A clear form is:

unsigned int shift_or_zero(unsigned int value, unsigned int count) {
    return count < 32 ? value << count : 0u;
}

The 32 is the width of unsigned int on this build. Use std::numeric_limits<unsigned int>::digits from <limits> if you want the width without writing the number. The next program calls the function and prints that width:

#include <iostream>
#include <limits>
 
unsigned int shift_or_zero(unsigned int value, unsigned int count) {
    return count < 32 ? value << count : 0u;
}
 
int main() {
    std::cout << "shift_or_zero(1, 4) = " << shift_or_zero(1, 4) << "\n";
    std::cout << "shift_or_zero(1, 32) = " << shift_or_zero(1, 32) << "\n";
    std::cout << "digits = " << std::numeric_limits<unsigned int>::digits << "\n";
    return 0;
}

Output from a run with g++ -std=c++20 -Wall:

shift_or_zero(1, 4) = 16
shift_or_zero(1, 32) = 0
digits = 32

Expecting an 8-bit result without a cast

The shift of an 8-bit value is done in int, so the result is 400 and not 144 until you store it. Printing x << 1 shows 400. Storing it in a std::uint8_t gives 144, which is the value the byte really holds.

Practice

Exercise 1

What does this print?

std::uint8_t x = 0xF0;
std::cout << (x >> 4) << " " << static_cast<int>(static_cast<std::uint8_t>(x << 4)) << "\n";

Show the answer

The output is 15 0. The value 0xF0 is 1111 0000. Shifting right by 4 gives 0000 1111, which is 15. Shifting left by 4 gives 1111 0000 0000 in the wider int. Storing that in an 8-bit value keeps only the low 8 bits, 0000 0000, which is 0.

Exercise 2

What are the values of -16 >> 2, -5 >> 1, and -5 / 2?

Show the answer

They are -4, -3 and -2. The value -16 is ...1111 0000, and shifting right by 2 with sign copying gives ...1111 1100, which is -4. The value -5 is ...1111 1011, and shifting right by 1 gives ...1111 1101, which is -3. Division with / rounds toward zero, so -5 / 2 is -2. The shift rounds toward negative infinity, so -5 >> 1 is -3.

Exercise 3

For the value 0b1010, what is ((value >> 3) & 1u)? What is ((value >> 2) & 1u)?

Show the answer

The first is 1 and the second is 0. The value 1010 has bit 3 set (worth 8) and bit 2 clear. The shift moves the wanted bit to position 0, and the & 1u keeps only that bit. The test program for the mask example printed the same bits for positions 2 and 3.

Exercise 4

The line int flag = 1 << 31; is in a program. Explain what is wrong with it and write the fixed line.

Show the answer

The result 2147483648 does not fit in an int. Before C++20 the standard makes the shift undefined, and in C++20 the result is the negative number -2147483648. The fix is to shift an unsigned value: unsigned int flag = 1u << 31;. The value is then 2147483648, which is defined in every version.

Exercise 5

Why is 1u << 32 a bad idea for a 32-bit unsigned int, even though the result is stored in an unsigned variable?

Show the answer

The rule depends on the count, not on the type of the result. A count equal to the width of the left operand is outside the range the standard allows, so the result is undefined. The u on the 1 makes the value unsigned, but it does not make the count valid.

Summary

  • A left shift of an unsigned value drops bits that go past the top. The result is defined.
  • Small types such as std::uint8_t are widened to int before a shift, so store the result with a cast when you need its low bits.
  • Right shifts of a negative int copy the sign bit in C++20. The result rounds toward negative infinity, not toward zero.
  • Before C++20, a shift that puts a bit into the sign position of a signed int is undefined. A count equal to or larger than the width is undefined in every version. Use 1u << k for masks.
  • For packed bit fields, use an unsigned type, or mask the result after each shift.
Practice this topic: coding problems for C++ Operators →

Stuck? Ask the tutor

Ask about anything in this lesson. Answers are short and use simple words.

Sign in to ask the tutor.

← Popcount and n & (n - 1)