How Bitwise Operators Work in C, Rust, and Python

Bitwise operators manipulate the individual bits of integers directly at the binary level, bypassing arithmetic logic to provide low-level control, high computational efficiency, and direct memory manipulation. While languages like C, Rust, and Python share standard operator syntax for operations like AND, OR, XOR, NOT, and bit shifts, the underlying execution varies based on how each language handles integer width, memory layout, and signed integer representations.

Binary Representation of Integers

Computers store integers as binary sequences (bits of 0s and 1s). Positive integers are mapped directly to base-2 representations, while negative integers are almost universally represented using two’s complement notation.

Core Bitwise Operations

1. Bitwise AND (&)

Compares each bit of two operands. If both corresponding bits are 1, the resulting bit is 1; otherwise, it is 0. * Binary Example: 0b1100 & 0b1010 = 0b1000 (12 & 10 = 8) * Common Use: Masking to isolate specific bits or clearing specific flags.

2. Bitwise OR (|)

Compares each bit of two operands. If at least one corresponding bit is 1, the resulting bit is 1. * Binary Example: 0b1100 | 0b1010 = 0b1110 (12 | 10 = 14) * Common Use: Setting specific bits or combining bitmasks.

3. Bitwise XOR (^)

Evaluates to 1 if the corresponding bits are different, and 0 if they are the same. * Binary Example: 0b1100 ^ 0b1010 = 0b0110 (12 ^ 10 = 6) * Common Use: Toggling bit states, parity checks, and fast swapping without temporary variables.

4. Bitwise NOT (~)

A unary operator that inverts all bits of the operand. Under two’s complement, applying ~x evaluates mathematically to -(x + 1). * Binary Example: ~0b00001010 (in an 8-bit signed integer, 10 becomes -11 / 0b11110101). * Common Use: Inverting bitmasks.

Shift Operators

Left Shift (<<)

Shifts the bits to the left by a specified number of positions, filling vacant positions on the right with 0. Shifting left by \(n\) positions is equivalent to multiplying the integer by \(2^n\). * 5 << 2 (0b00000101 << 2) becomes 20 (0b00010100).

Right Shift (>>)

Shifts the bits to the right by a specified number of positions, discarding the bits that fall off. * Logical Shift: Vacant positions on the left are filled with 0. (Standard for unsigned types in C and Rust). * Arithmetic Shift: Vacant positions on the left are filled with a copy of the sign bit to preserve the sign of negative numbers. (Standard for signed types in C, Rust, and Python). * 20 >> 2 (0b00010100 >> 2) becomes 5 (0b00000101).

Language-Specific Behaviors

C

C maps bitwise operations directly to CPU instructions. * Shifts greater than or equal to the bit width of the type result in undefined behavior. * Bit shifting negative signed integers using >> is implementation-defined (though usually arithmetic shift), while shifting negative numbers with << can cause undefined behavior.

Rust

Rust enforces strict type safety and distinguishes explicitly between unsigned (u8, u32, etc.) and signed (i8, i32, etc.) types. * In debug builds, bit shifts that exceed the type’s width trigger a runtime panic. * Rust provides explicit standard library methods such as wrapping_shl, rotate_left, and count_ones for deterministic low-level bit manipulation across different platforms.

Python

Python treats integers as having infinite bit depth. * Applying ~x returns -(x + 1) using virtual two’s complement arithmetic, but bits do not truncate to fixed boundaries. * Left shifts (<<) expand the integer size dynamically without overflow, consuming more memory as required. * To achieve fixed-width behavior (such as 32-bit integer limits), outputs must be explicitly masked using an operation such as x & 0xFFFFFFFF.