Focus We will explore the three shift operators: left shift (<<), logical right shift (>>>), and arithmetic right shift (>>). Shifting left multiplies by a power of two; shifting right divides by a power of two. There are two ways to shift right, varying in how we handle the fill value. By the end of the chapter, we will understand what each of the shift operators does, and how bits are handled as sequences are shifted.

In the decimal system, multiplying by 10 is simple: just append a zero. The digit 4 becomes 40; 23 becomes 230. The digits themselves do not change. Instead, they all move one column to the left, and an empty column at the right is filled with a zero. This is intuitive if we think back on how we described base-10 and base-2 numbers in Chapter 1. Binary works exactly the same way, except the base is 2, so moving one column left means multiplying by 2 instead of 10. The inverse operation is also similar. If we move digits to the right, we can drop those that fall off the edge, and we can pad with zeros. So the number 421 can become 042, which is 42. This is just like dividing by 10 and rounding down.

Bit shifts are the formal name for this column-moving operation on binary numbers. There are three shift operators: one left shift operator, and two right shift operators.

Left Shift <<

The left shift operator is written as A << n. It moves every bit in A exactly n positions to the left. There are only two things to remember: the n vacant positions on the right are filled with zeros, and any bits that fall off the left end are discarded.

Because each bit position represents a power of two, shifting one place to the left moves every bit to a column worth twice as much. Shifting by n multiplies by 2n.

Let’s work through an example. We begin with the binary number 00000011, which is 3 in decimal. We want to left-shift by two positions, so we write 3 << 2. The bits move two places to the left, the two rightmost columns are filled with zeros, and we get 00001100. This is 3 × 22 = 3 × 4 = 12. And 00001100 is precisely 12 in decimal.

This works as long as none of the high bits that get shifted out were set to 1. If they were, the multiplication overflows and the high-order information is simply lost. This is by design and not an error in most programming languages.

Interactive Left Shift Demo

The demo below marks the bits that will be discarded in red so that we can see exactly when overflow occurs. Remember that the demo is using an 8-bit unsigned representation. This covers the 0 to 255 range. Intuitively, overflow occurs when the result of the multiplication is outside of the supported range.

Demo 4.1 — Left Shift
Presets:
A
A << n
decimal
Notes on the presets

The ×2 and ×4 presets illustrate the multiplication directly. The ×4 preset uses A = 3 and n = 2, matching the example we worked through together above. The bit row shows those two positions moving left and two zeros filling in on the right.

The overflow preset (200 << 2) shows what happens when significant bits reach the edge and fall off. The mathematical result would be 800, but the 8-bit value truncates to 32. The discarded bits are highlighted in red so that we can see which information is lost.

The clear lower bits preset (183 << 4) shows how left-shifts can be used to clear bit positions. In the example, the bottom 4 positions are filled with zeros. The upper bits move left and any that fall off are discarded.

Right Shifts

Shifting right is the inverse of the left shift: every bit moves n positions to the right, the n low bits are discarded, and the n vacated positions at the top need to be filled with something. There are two variants of the right shift, depending on the value used to fill those positions.

Logical right shift >>>

The logical right shift always fills the vacated positions with 0, regardless of the original sign bit. It treats the value as a plain unsigned bit pattern. This makes it the right choice for unsigned integers: dividing 128 by 2 with a logical shift gives 64, as expected. Care must be taken when using a logical right shift with signed integers, as the sign bit is overwritten with 0.

Arithmetic right shift >>

The arithmetic right shift fills the vacated positions with copies of the sign bit. If the sign bit is 0 (positive value), it fills with zeros. This is identical to a logical shift. If the sign bit is 1 (negative value), it fills with ones, keeping the result negative.

Example

Consider the 8-bit value 11111000, which is −8 in two’s complement. We know that −8 divided by 2 is −4. With the logical right shift we write −8 >>> 1: bits move one position right, the top is filled with 0, and we get 01111100. Interpreted as a signed integer, that is +124, which is wrong.

With the arithmetic right shift we write −8 >> 1: the top is filled with 1 (the sign bit), giving 11111100, which is −4, and correct.

Sign-bit replication is the only fill value that preserves the two’s complement interpretation of division by 2n. It is not a special rule that was added for convenience. For positive values (sign bit = 0), both variants agree. The distinction only matters for negative values.

Interactive Right Shift Demo

In the demo below, we can visualize how logical and arithmetic right shifts operate. Two things to remember:

Demo 4.2 — Right Shift
Presets:
A
A >>> n
decimal
Notes on the presets

Logical Right Shift Presets

The ÷2 and ÷4 logical-shift presets illustrate straightforward integer division. The values are chosen so no sign bit is involved. Try swapping between the logical and arithmetic shift tabs with these presets. These are clean cases where both shifts agree.

The extract upper bits preset (0b11110000 >>> 4) moves the four upper bits down to positions 0–3 and fills the top with zeros. This is a common technique for reading a packed field from the upper portion of a byte, which we will cover later in this tutorial.

Arithmetic Right Shift Presets

The signed ÷2 preset (A = −64, n = 1) shows arithmetic right shift performing correct signed division: −64 ÷ 2 = −32. The sign bit is 1, so the vacated top position is filled with 1, keeping the result negative. Compare this with the logical tab on the same bit pattern (which reads as 192 unsigned) to see how the fill choice determines whether the result is meaningful as signed division.

The all ones (−1) arithmetic-shift preset (A =−1, n = 3) demonstrates that shifting −1 right by any amount always returns −1. Every vacated position is filled with the sign bit (1), so the all-ones pattern is preserved. This is aligned with the floor-division interpretation of arithmetic right shift. We can divide a negative integer by increasingly large powers of 2, but we always round down to −1.

The positive preset (A = 64, n = 2) confirms that when the sign bit is 0, both shifts produce the same result. Switch between tabs to verify.

The min (−128) preset shows the most-negative 8-bit value halved to −64. The sign fill is clearly visible: the vacated top bit is filled with red, matching the original sign bit.

Bit Shifts in Python

Python’s native int type has no fixed bit width. Unlike C or JavaScript, where integers live in 32- or 64-bit containers, Python’s int grows to however many bits it needs. Positive numbers conceptually have an infinite stream of 0s to the left; negative numbers have an infinite stream of 1s. This single property leads to two notable differences in how shift operators behave:

Note that this applies to Python’s native int only. Libraries that define fixed-width integer types; such as NumPy (numpy.int8, numpy.uint32) and PyTorch (torch.int8, torch.int32); follow fixed-width rules instead. With those types, left shifts can overflow and right shift behavior depends on the type’s signedness, just as in C or JavaScript.

A deeper look at Python’s native int behavior

Unbounded bit representation

In Python, every integer is conceptually represented with infinite leading bits matching its sign:

 1  →  ...0000 0000 0000 0001
-1  →  ...1111 1111 1111 1111
-64 →  ...1111 1111 1100 0000

This is the same two’s complement representation from Chapter 2, extended infinitely to the left rather than capped at a fixed width.

Left shifts never overflow

Because the sequence can always grow, 1 << 100 simply produces a very large integer rather than wrapping around. There is no left edge for bits to fall off. In C, shifting a 32-bit integer by 100 is undefined behavior and most implementations return 0 or a garbage value. In JavaScript, the shift amount is masked to 5 bits (mod 32), so 1 << 100 evaluates as 1 << 4 = 16 — surprising, but not a discard.

Simulating logical right shifts

Because Python’s >> is always arithmetic, negative values fill with 1s on the left. To force zero-fill, mask the value first to strip away the infinite sign extension, leaving only the bits you care about. For an 8-bit logical right shift:

x = -64           # ...1111 1111 1100 0000 in Python's unbounded representation
x & 0xFF          # → 192  (keeps only the low 8 bits: 1100 0000)
(x & 0xFF) >> 1  # → 96   (zero-fill, treating it as unsigned)

The mask 0xFF (11111111) cuts off all the infinite 1s to the left, leaving just the 8-bit pattern. After masking, shifting right always fills with 0 because the sign bit of the masked value is 0. Without the mask, -64 >> 1 gives -32 (arithmetic); with it, (-64 & 0xFF) >> 1 gives 96 (logical). The width of the mask determines the width of the logical shift: 0xFF for 8 bits, 0xFFFF for 16, 0xFFFFFFFF for 32.

Conclusion

Operator Symbol Vacated bits filled with Arithmetic meaning
Left shift << 0 (right side) × 2n (may overflow in fixed-width languages)
Logical right shift >>> 0 (left side) ÷ 2n, unsigned
Arithmetic right shift >> copy of sign bit (left side) ÷ 2n, signed (rounds toward −∞)

In Chapter 5 we will use shifts to build masks. The expression 1 << k is the fundamental building block: a single left shift produces a bit pattern that targets any position we choose.