Loading content...
Bit shifting is elegant and fast—until you push it past its limits. Shift overflow and bit loss are the dark corners of this operation where bits silently disappear, values wrap unexpectedly, and undefined behavior lurks in wait for the unwary programmer.
Unlike arithmetic overflow (which often triggers exceptions or is detectable), shift-related data loss is typically silent. Your program continues running with corrupted data, producing wrong results that may not surface until much later—or ever, if you're unlucky enough. Understanding these edge cases is essential for writing robust bit manipulation code.
By the end of this page, you will understand exactly what happens when bits shift beyond the boundaries of their container, the difference between defined wraparound behavior and undefined behavior, how to detect when overflow has occurred or will occur, and how to write safe code that either prevents these issues or handles them gracefully.
When you left-shift a value, bits move leftward and zeros fill from the right. But what happens to the bits that move beyond the leftmost position? They're discarded—gone forever, with no trace left behind.
In fixed-width integer types (like int32 or uint8), there's a finite container for bits. Left shifting eventually pushes significant bits out of this container:
| Shift | Binary Before | Binary After | Lost Bits | Decimal |
|---|---|---|---|---|
| << 0 | 10101010 | 10101010 | none | 170 |
| << 1 | 10101010 | 01010100 | 1 | 84 |
| << 2 | 10101010 | 10101000 | 01 | 168 |
| << 3 | 10101010 | 01010000 | 101 | 80 |
| << 4 | 10101010 | 10100000 | 0101 | 160 |
| << 5 | 10101010 | 01000000 | 10101 | 64 |
| << 6 | 10101010 | 10000000 | 010101 | 128 |
| << 7 | 10101010 | 00000000 | 1010101 | 0 |
| << 8 | 10101010 | 00000000 | 10101010 | 0 |
Observe the progression: with each shift, more original bits are pushed out. By shift 7, only the original LSB remains (now in the MSB position). By shift 8, all original bits are gone—replaced entirely by zeros.
The mathematical perspective:
Left shift by k is equivalent to multiplication by 2^k, but with modular arithmetic:
(x << k) mod 2^n where n is the bit width
This modular behavior is why 170 << 1 = 340 mod 256 = 84 for an 8-bit type. The multiplication produces 340, but only the low 8 bits (340 & 0xFF = 84) are kept.
In most languages, bit loss during left shift produces no runtime error. The operation 'succeeds' and returns a value—it's just not the value you might expect from mathematical multiplication. This silent failure makes shift overflow bugs particularly insidious.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
import numpy as np def demonstrate_left_shift_overflow(): """Show how left shift loses bits in fixed-width integers.""" # Python's native int has arbitrary precision - no overflow x_python = 170 print("Python (arbitrary precision):") for shift in range(10): result = x_python << shift print(f" 170 << {shift} = {result}") # NumPy integers have fixed width - they overflow/wrap print("\nNumPy uint8 (8-bit unsigned):") x_np = np.uint8(170) for shift in range(10): result = x_np << shift print(f" 170 << {shift} = {result} (binary: {format(result, '08b')})") print("\nNotice: Mathematical 170*2 = 340, but uint8 gives 84 (340 mod 256)") def detect_left_shift_overflow_32bit(x: int, shift: int) -> tuple: """ Detect if left shifting x by shift positions would overflow a 32-bit unsigned. Returns: (would_overflow, result_if_masked) """ MAX_UINT32 = 0xFFFFFFFF if shift >= 32: # Shifting by 32+ always loses all bits return (x != 0, 0) # Check if any bits would be shifted out # Bits at positions (32 - shift) and above will be lost high_bits_mask = MAX_UINT32 << (32 - shift) if shift > 0 else 0 would_overflow = (x & high_bits_mask) != 0 result = (x << shift) & MAX_UINT32 return (would_overflow, result) if __name__ == "__main__": demonstrate_left_shift_overflow() print("\nOverflow detection examples:") test_cases = [ (1, 30), # 1 << 30 = 1073741824, fits in 32 bits (1, 31), # 1 << 31 = 2147483648, fits in uint32 (1, 32), # Would need 33 bits (3, 31), # 3 << 31 = 6442450944, needs 33 bits (0xFF, 25), # 0xFF << 25 = 8556380160, needs 34 bits ] for x, shift in test_cases: overflow, result = detect_left_shift_overflow_32bit(x, shift) status = "OVERFLOW" if overflow else "OK" print(f" {x} << {shift}: {status}, masked result = {result}")Right shift loses bits from the opposite end—the low-order bits are pushed out and discarded. This is equivalent to integer division where the remainder is thrown away.
Despite the name 'overflow' being less commonly used for right shift, the concept of information loss is just as real. Once bits are shifted out, there's no way to recover the original value without external information.
| Shift | Binary Before | Binary After | Lost Bits | Decimal |
|---|---|---|---|---|
0 | 10101010 | 10101010 | none | 170 |
1 | 10101010 | 01010101 | 0 | 85 |
2 | 10101010 | 00101010 | 10 | 42 |
3 | 10101010 | 00010101 | 010 | 21 |
4 | 10101010 | 00001010 | 1010 | 10 |
5 | 10101010 | 00000101 | 01010 | 5 |
6 | 10101010 | 00000010 | 101010 | 2 |
7 | 10101010 | 00000001 | 0101010 | 1 |
8 | 10101010 | 00000000 | 10101010 | 0 |
Key observations:
Truncation toward zero — Each right shift by 1 approximately halves the value, discarding any fractional part. 170 → 85 → 42 → 21 → 10 → 5 → 2 → 1 → 0.
Non-reversible — You cannot recover 170 from 85 using left shift. 85 << 1 = 170 works, but 21 << 3 = 168 ≠ 170. The lost bits are truly lost.
Total loss — Shifting by the bit width or more produces 0 for all positive values (or -1 for negative values with arithmetic shift).
The remainder connection:
The bits shifted out represent the remainder of division by 2^k. This can be captured using a mask:
x >> k gives the quotient of x ÷ 2^kx & ((1 << k) - 1) gives the remainder of x ÷ 2^k12345678910111213141516171819202122232425262728293031323334353637383940414243444546
def demonstrate_information_loss(): """Show that right shift is not reversible.""" original = 170 print("Right shift and attempted reversal:") print(f"Original: {original} = {bin(original)}") print() for shift in range(1, 8): shifted = original >> shift reversed_attempt = shifted << shift lost_bits = original & ((1 << shift) - 1) recoverable = "Yes" if reversed_attempt == original else "No" print(f" >> {shift}: {shifted:3d} ({bin(shifted):>10s})") print(f" << {shift}: {reversed_attempt:3d} ({bin(reversed_attempt):>10s}) - Matches original? {recoverable}") print(f" Lost bits: {lost_bits} ({bin(lost_bits)})") print() def extract_quotient_and_remainder(x: int, k: int) -> tuple: """ Compute x ÷ 2^k and x mod 2^k using only bit operations. This demonstrates that right shift captures the quotient and masking captures the remainder. """ quotient = x >> k remainder = x & ((1 << k) - 1) # Verify: x == quotient * 2^k + remainder assert x == (quotient << k) + remainder return (quotient, remainder) if __name__ == "__main__": demonstrate_information_loss() print("Quotient and remainder extraction:") for x in [170, 255, 100, 7]: for k in [1, 2, 3, 4]: q, r = extract_quotient_and_remainder(x, k) divisor = 1 << k print(f" {x} ÷ {divisor} = {q} remainder {r} (verify: {q}*{divisor}+{r} = {q*divisor+r})") print()If you need the bits that would be shifted out (the remainder), extract them with a mask BEFORE performing the shift. Once the shift executes, those bits are gone. This is a common pattern in division algorithms and when converting bit positions.
Beyond bit loss within valid shift amounts, there are dangerous edge cases when the shift amount itself is problematic. These cause the most serious bugs because the behavior varies dramatically across languages and platforms.
x << 0 = x and x >> 0 = x. Harmless but occasionally wasteful.x << 32 for a 32-bit integer. Undefined in C/C++! In Java/JavaScript, wraps to x << 0 = x.x << 40 for a 32-bit type. Also undefined in C/C++; wraps modulo 32/64 in Java/JS.x << -1. Undefined in C/C++; in Java, -1 & 31 = 31, so it becomes x << 31.| Expression | C/C++ | Java | JavaScript (|0) | Python |
|---|---|---|---|---|
| x << 0 | x | x | x | x |
| x << 32 | Undefined! | x << 0 = x | x << 0 = x | x × 2^32 |
| x << 33 | Undefined! | x << 1 | x << 1 | x × 2^33 |
| x << 64 | Undefined! | x << 0 = x | x << 0 = x | x × 2^64 |
| x << -1 | Undefined! | x << 31 | x << 31 | Error |
| x >> 32 | Undefined! | x >> 0 = x | x >> 0 = x | 0 or -1* |
*Python: For arbitrary precision integers, right shift by any amount toward 0 (positive) or -1 (negative).
Why C/C++ is undefined:
The C standard leaves shift by >= width undefined to allow for efficient compilation on different hardware. Some CPUs (x86) mask the shift amount; others (some ARM modes) saturate it; others do something else entirely. By leaving it undefined, the compiler can generate the fastest code for the target architecture without guaranteeing specific behavior.
Why Java/JavaScript wrap:
These languages chose to define specific behavior for portability. The shift amount is masked with & 31 (for 32-bit) or & 63 (for 64-bit), effectively using only the low bits of the shift amount. This is predictable but can hide bugs if you expect x << 32 = 0.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
#include <stdint.h>#include <stdio.h>#include <stdbool.h> #define BITS_32 32#define BITS_64 64 // UNSAFE: This function has undefined behavior for n >= 32uint32_t unsafe_left_shift(uint32_t x, unsigned int n) { return x << n; // UB if n >= 32} // SAFE: Explicitly handle edge casesuint32_t safe_left_shift_32(uint32_t x, unsigned int n) { if (n >= BITS_32) { return 0; // All bits shifted out } return x << n;} // SAFE: Match Java/JavaScript behavior (wrapping)uint32_t java_style_left_shift(uint32_t x, int n) { // Java masks with & 31 unsigned int effective_shift = n & 31; return x << effective_shift;} // SAFE: Saturating shift (clamp to valid range)uint32_t saturating_left_shift(uint32_t x, unsigned int n) { if (n >= BITS_32) { n = BITS_32 - 1; // Clamp to maximum valid shift } return x << n;} // SAFE: Check for potential overflow before shiftingbool would_left_shift_overflow(uint32_t x, unsigned int n, uint32_t *result) { if (n >= BITS_32) { *result = 0; return x != 0; // Overflow if any bits were set } // Check if any of the bits that will be shifted out are set uint32_t bits_to_shift_out = x >> (BITS_32 - n); *result = x << n; return bits_to_shift_out != 0;} // SAFE: Right shift with bounds checkinguint32_t safe_right_shift_32(uint32_t x, unsigned int n) { if (n >= BITS_32) { return 0; // All bits shifted out } return x >> n;} // SAFE: Signed arithmetic right shift with bounds checkingint32_t safe_arithmetic_right_shift(int32_t x, unsigned int n) { if (n >= BITS_32) { // All bits become the sign bit return x < 0 ? -1 : 0; } return x >> n; // Implementation-defined but usually arithmetic} int main(void) { uint32_t x = 0x80000000; // 2^31 printf("Safe shift demonstrations:\n\n"); printf("safe_left_shift_32(1, 31) = %u\n", safe_left_shift_32(1, 31)); printf("safe_left_shift_32(1, 32) = %u\n", safe_left_shift_32(1, 32)); printf("safe_left_shift_32(1, 100) = %u\n", safe_left_shift_32(1, 100)); printf("\njava_style_left_shift(1, 32) = %u (wraps to << 0)\n", java_style_left_shift(1, 32)); printf("java_style_left_shift(1, 33) = %u (wraps to << 1)\n", java_style_left_shift(1, 33)); printf("\nOverflow detection:\n"); uint32_t result; bool overflow = would_left_shift_overflow(x, 1, &result); printf("0x%08X << 1: overflow = %s, result = 0x%08X\n", x, overflow ? "YES" : "NO", result); overflow = would_left_shift_overflow(0x7FFFFFFF, 1, &result); printf("0x7FFFFFFF << 1: overflow = %s, result = 0x%08X\n", overflow ? "YES" : "NO", result); return 0;}If a shift amount comes from external input (user data, file, network), it could be any value—including negative numbers or values larger than the bit width. Always validate and clamp shift amounts before use. Failure to do so is a common source of security vulnerabilities in low-level code.
The interaction between left shift and signed integers is particularly treacherous. In C/C++ (before C++20), left-shifting a positive signed value such that it 'overflows' into the sign bit is undefined behavior.
The problematic scenario:
int x = 1; // A positive signed 32-bit integer
int y = x << 31; // Shifts 1 into the sign bit position
Mathematically, 1 × 2^31 = 2147483648, which exceeds the maximum signed 32-bit value (2147483647). The result would be interpreted as a negative number in two's complement. In C/C++, this is undefined behavior.
| Expression | Mathematical Result | Max Signed 32-bit | Status |
|---|---|---|---|
| 1 << 30 | 1,073,741,824 | 2,147,483,647 | OK |
| 1 << 31 | 2,147,483,648 | 2,147,483,647 | Undefined! |
| 2 << 30 | 2,147,483,648 | 2,147,483,647 | Undefined! |
| 3 << 30 | 3,221,225,472 | 2,147,483,647 | Undefined! |
| 0x40000000 << 1 | 2,147,483,648 | 2,147,483,647 | Undefined! |
Why this is undefined in C:
Signed integer overflow is undefined behavior in C because different hardware handles it differently (some wrap, some trap, some saturate). The standard doesn't impose a model to allow maximum performance.
What typically happens (but is not guaranteed):
On most systems with two's complement arithmetic and at low optimization levels, 1 << 31 produces -2147483648 (INT_MIN). But optimizing compilers may:
The C++20 fix:
C++20 finally defined the behavior: left-shifting a signed value produces the correctly wrapped two's complement result, as if the computation were done in unsigned and converted back. This matches what most hardware does anyway.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
#include <stdint.h>#include <limits.h>#include <stdbool.h> // Check if left-shifting a signed value would overflow// Returns true if overflow would occurbool would_signed_left_shift_overflow(int32_t x, unsigned int n) { if (n == 0) return false; if (n >= 32) return x != 0; // Any shift by 32+ loses all bits // For negative values, left shift is always UB in pre-C++20 if (x < 0) return true; // For positive values, check if result would exceed INT32_MAX // Maximum value that can be shifted by n without overflow: // INT32_MAX >> n int32_t max_shiftable = INT32_MAX >> n; return x > max_shiftable;} // Safe signed left shift - uses unsigned to avoid UB// Returns the two's complement resultint32_t safe_signed_left_shift(int32_t x, unsigned int n) { if (n >= 32) return 0; // Convert to unsigned, shift, convert back // This is well-defined (though implementation-defined on conversion back) uint32_t ux = (uint32_t)x; uint32_t result = ux << n; return (int32_t)result;} // Checked signed left shift - returns success/failurebool checked_signed_left_shift(int32_t x, unsigned int n, int32_t *result) { if (would_signed_left_shift_overflow(x, n)) { *result = 0; // Or any sentinel value return false; // Overflow would occur } *result = x << n; // Safe: no overflow return true; // Success} // Example usage#include <stdio.h> int main(void) { printf("Signed left shift overflow detection:\n\n"); struct { int32_t x; unsigned int n; } tests[] = { {1, 30}, // OK: 1 << 30 = 1073741824 {1, 31}, // Overflow: 1 << 31 = 2147483648 > INT_MAX {2, 30}, // Overflow: 2 << 30 = 2147483648 > INT_MAX {0x20000000, 1}, // OK: 0x20000000 << 1 = 0x40000000 {0x40000000, 1}, // Overflow: 0x40000000 << 1 = 0x80000000 > INT_MAX {-1, 1}, // Undefined: negative left shift }; for (size_t i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { int32_t x = tests[i].x; unsigned int n = tests[i].n; bool overflow = would_signed_left_shift_overflow(x, n); int32_t safe_result = safe_signed_left_shift(x, n); printf(" %d << %u: %s, safe result = %d\n", x, n, overflow ? "OVERFLOW" : "OK", safe_result); } return 0;}For maximum safety and portability, always use unsigned types for bit manipulation. Cast signed values to unsigned before shifting, perform the operations, then cast back if needed. This sidesteps all signed overflow undefined behavior.
Rather than dealing with overflow after it happens (which may be too late), we can detect potential overflow before performing the shift. This is essential for robust code handling untrusted inputs.
n >= bit_width, all significant bits will be lost. Handle this case explicitly.n bits are set before a left shift by n, those bits will be lost.__builtin_mul_overflow and similar intrinsics.123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
from typing import Tuple def safe_left_shift_32(x: int, n: int) -> Tuple[int, bool]: """ Perform 32-bit unsigned left shift with overflow detection. Returns: (result, overflow_occurred) - result: The masked 32-bit result - overflow_occurred: True if bits were lost """ if n < 0: raise ValueError("Negative shift amount") MAX_UINT32 = 0xFFFFFFFF # Clamp x to 32 bits first x = x & MAX_UINT32 if n >= 32: # All bits shifted out return (0, x != 0) if n == 0: return (x, False) # Check if any bits in positions [32-n, 31] are set # These bits will be shifted out high_bits_mask = (MAX_UINT32 >> n) << n # Top n bits only high_bits_mask = MAX_UINT32 ^ ((1 << (32 - n)) - 1) # Clearer version bits_that_will_be_lost = (x >> (32 - n)) != 0 result = (x << n) & MAX_UINT32 return (result, bits_that_will_be_lost) def safe_multiply_by_power_of_2(x: int, power: int, bit_width: int = 32) -> Tuple[int, bool]: """ Multiply x by 2^power with overflow detection. This is mathematically equivalent to left shift but makes the semantic intent clearer. """ return safe_left_shift_32(x, power) if bit_width == 32 else None def count_leading_zeros_32(x: int) -> int: """Count leading zeros in a 32-bit value.""" if x == 0: return 32 count = 0 mask = 0x80000000 # Start from MSB while (x & mask) == 0 and count < 32: count += 1 mask >>= 1 return count def max_left_shift_without_overflow_32(x: int) -> int: """ Determine the maximum left shift that won't lose bits. Returns the largest n such that (x << n) fits in 32 bits. """ if x == 0: return 32 # Can shift by any amount; result is always 0 x = x & 0xFFFFFFFF # Ensure 32-bit # Maximum shift is determined by leading zeros return count_leading_zeros_32(x) if __name__ == "__main__": print("Safe left shift with overflow detection:") print("-" * 50) test_cases = [ (1, 31), # 1 << 31 = 2^31, fits (1, 32), # 1 << 32 = 2^32, overflow (0x80000000, 1), # Already has MSB set, any shift loses it (0x7FFFFFFF, 1), # Max positive, shifting doubles it (255, 24), # 255 << 24 = 255 * 2^24, fits (255, 25), # 255 << 25 = 255 * 2^25, overflow ] for x, n in test_cases: result, overflow = safe_left_shift_32(x, n) status = "OVERFLOW" if overflow else "OK" print(f" 0x{x:08X} << {n:2d} = 0x{result:08X} [{status}]") print("\nMaximum safe shift amounts:") for x in [1, 0x100, 0x10000, 0x80000000]: max_shift = max_left_shift_without_overflow_32(x) print(f" 0x{x:08X} can be shifted by at most {max_shift}")The count of leading zeros in a value directly tells you the maximum left shift that won't overflow. If a 32-bit value has 5 leading zeros, you can safely left-shift it by up to 5 positions. This is why CLZ (Count Leading Zeros) is a common CPU instruction—it's fundamental to safe shift operations.
Python's integers have arbitrary precision—they can grow to any size, limited only by available memory. This fundamentally changes how shift overflow works compared to fixed-width languages.
In Python:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
def demonstrate_arbitrary_precision(): """Show Python's arbitrary precision prevents overflow.""" print("Python arbitrary precision left shift:") x = 1 for shift in [31, 32, 63, 64, 100, 1000]: result = x << shift # No overflow! Just a very large number print(f" 1 << {shift:<4d} = {result}") if shift <= 64: print(f" (binary length: {result.bit_length()} bits)") # Python can handle truly massive shifts huge = 1 << 10000 # A number with over 3000 decimal digits print(f"\n 1 << 10000 has {huge.bit_length()} bits and {len(str(huge))} decimal digits") def simulate_fixed_width_overflow(x: int, n: int, width: int) -> int: """ Simulate fixed-width integer overflow in Python. This is useful when porting algorithms from C or when you need to match fixed-width behavior exactly. """ mask = (1 << width) - 1 # e.g., 0xFFFFFFFF for 32-bit # Clamp input to width x = x & mask # Perform shift with modular wrapping if n >= width: return 0 return (x << n) & mask def simulate_signed_fixed_width(x: int, n: int, width: int) -> int: """ Simulate signed fixed-width arithmetic right shift in Python. """ # First, interpret x as a signed value of given width sign_bit = 1 << (width - 1) max_unsigned = (1 << width) - 1 x = x & max_unsigned # Clamp to width if x & sign_bit: # Negative in two's complement # x is negative; need to sign-extend # Python's >> on negative is arithmetic, but we have unsigned repr x = x - (1 << width) # Convert to negative Python int # Now shift if n >= width: return -1 if x < 0 else 0 result = x >> n # Convert back to unsigned representation return result & max_unsigned if __name__ == "__main__": demonstrate_arbitrary_precision() print("\nSimulating 8-bit unsigned wraparound:") for x in [128, 200, 255]: for n in [1, 2, 4]: result = simulate_fixed_width_overflow(x, n, 8) print(f" {x} << {n} (8-bit) = {result}") print("\nSimulating 8-bit signed arithmetic right shift:") for x in [0xFF, 0x80, 0x7F]: # -1, -128, 127 in 8-bit signed for n in [1, 2, 4, 7]: result = simulate_signed_fixed_width(x, n, 8) # Interpret result as signed for display signed = result if result < 128 else result - 256 print(f" 0x{x:02X} ({x if x < 128 else x-256:4d}) >> {n} = 0x{result:02X} ({signed:4d})")When to simulate fixed-width behavior in Python:
Python's arbitrary precision is great for prototyping and algorithms where numeric range shouldn't be a concern. For production code that must match fixed-width behavior, use the simulated functions with explicit masking. For maximum performance, consider NumPy arrays with explicit dtypes (uint32, int64, etc.).
Shift overflow bugs have caused real problems in production systems. Understanding these cases helps build intuition for where to look for similar issues in your own code.
(low + high) / 2 overflows for large arrays. Fixed with (low + high) >>> 1. This bug existed in Java for 9 years before discovery.123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869
public class BinarySearchBug { // THE BUG: This can overflow for large arrays public static int binarySearchBuggy(int[] arr, int target) { int low = 0; int high = arr.length - 1; while (low <= high) { // BUG: low + high can overflow if both are > Integer.MAX_VALUE / 2 int mid = (low + high) / 2; // Overflow here! if (arr[mid] == target) return mid; else if (arr[mid] < target) low = mid + 1; else high = mid - 1; } return -1; } // THE FIX: Use unsigned right shift public static int binarySearchFixed(int[] arr, int target) { int low = 0; int high = arr.length - 1; while (low <= high) { // FIX: >>> treats the (possibly negative) sum as unsigned int mid = (low + high) >>> 1; // No overflow! if (arr[mid] == target) return mid; else if (arr[mid] < target) low = mid + 1; else high = mid - 1; } return -1; } // ALTERNATIVE FIX: Avoid overflow in the first place public static int binarySearchAlternative(int[] arr, int target) { int low = 0; int high = arr.length - 1; while (low <= high) { // ALTERNATIVE: Compute delta, then add to low // This never overflows because high - low <= high <= MAX_VALUE int mid = low + (high - low) / 2; // Safe! if (arr[mid] == target) return mid; else if (arr[mid] < target) low = mid + 1; else high = mid - 1; } return -1; } public static void main(String[] args) { // Demonstrate the overflow int low = Integer.MAX_VALUE - 10; // 2147483637 int high = Integer.MAX_VALUE; // 2147483647 int badMid = (low + high) / 2; // Overflow! Negative! int goodMid = (low + high) >>> 1; // Correct! int altMid = low + (high - low) / 2; // Also correct! System.out.println("low = " + low); System.out.println("high = " + high); System.out.println("(low + high) = " + (low + high) + " (OVERFLOW!)"); System.out.println(); System.out.println("Buggy mid: " + badMid + " (WRONG!)"); System.out.println("Fixed mid (>>>): " + goodMid); System.out.println("Alt mid (delta): " + altMid); }}Even expert programmers at major companies miss these bugs. The binary search overflow existed in Java's standard library for nearly a decade. Code reviews, testing with small values, and even formal verification didn't catch it. Only testing with arrays larger than 2^30 elements would trigger the bug—a scale rarely encountered in practice. Always consider: 'What happens at the edge of representable values?'
We've explored the dangerous territory of shift overflow and bit loss. Here are the essential survival strategies:
Putting it all together:
You now have a complete understanding of bit shifting operations—left shift for multiplication, right shift for division, the logical vs. arithmetic distinction, and the pitfalls of overflow and bit loss. This knowledge forms the foundation for all bit manipulation techniques.
With these four pages complete, you're ready to apply bit shifting in real algorithms: creating bitmasks, packing/unpacking data, fast arithmetic, and all the clever tricks that make low-level code efficient. The next module will build on this foundation with more advanced bit manipulation patterns.
Congratulations! You've mastered bit shifting—the fundamental operation underlying countless optimizations in systems programming, embedded development, graphics, cryptography, and algorithm design. You understand not just how shifts work, but why they work, when they fail, and how to use them safely. This knowledge separates engineers who merely use bit operations from those who truly understand them.