Ch 3.2 Bitwise Operators and Special Operators
Bitwise operators directly manipulate the binary representation of integers. They are frequently used in systems programming, cryptography, flag management, and performance optimization.
1. Bitwise Operators
a = 0b1100 # 12 (binary: 1100)
b = 0b1010 # 10 (binary: 1010)
# & — bitwise AND (1 only when both bits are 1)
print(a & b) # 8 (binary: 1000)
print(bin(a & b)) # 0b1000
# | — bitwise OR (1 if at least one bit is 1)
print(a | b) # 14 (binary: 1110)
print(bin(a | b)) # 0b1110
# ^ — bitwise XOR (1 when bits differ)
print(a ^ b) # 6 (binary: 0110)
print(bin(a ^ b)) # 0b110
# ~ — bitwise NOT (flip bits, result = -(n+1))
print(~a) # -13 (bit flip + two's complement)
print(~b) # -11
# << — left shift (equivalent to multiplying by a power of 2)
print(a << 1) # 24 (12 * 2)
print(a << 2) # 48 (12 * 4)
# >> — right shift (equivalent to integer division by a power of 2)
print(a >> 1) # 6 (12 // 2)
print(a >> 2) # 3 (12 // 4)
Visualizing Bit Operations
def show_bits(n: int, label: str = "") -> None:
"""Displays the binary representation of an integer visually."""
binary = format(n & 0xFF, '08b') # display as 8 bits
print(f"{label:15} {n:4d} | {binary}")
a, b = 0b11001010, 0b10110101 # 202, 181
show_bits(a, "a")
show_bits(b, "b")
print("-" * 30)
show_bits(a & b, "a AND b")
show_bits(a | b, "a OR b")
show_bits(a ^ b, "a XOR b")
show_bits(~a & 0xFF, "NOT a (8bit)")
show_bits(a << 1 & 0xFF, "a << 1 (8bit)")
show_bits(a >> 1, "a >> 1")
2. Practical Bitwise Operation Examples
Flag Masking (Permission System)
# File permission flags (Unix style)
READ = 0b100 # 4
WRITE = 0b010 # 2
EXECUTE = 0b001 # 1
# Set permissions
user_perm = READ | WRITE # read + write = 0b110 = 6
# Check permissions — bitwise AND
print(bool(user_perm & READ)) # True (can read)
print(bool(user_perm & WRITE)) # True (can write)
print(bool(user_perm & EXECUTE)) # False (cannot execute)
# Grant permission — bitwise OR
user_perm |= EXECUTE
print(bool(user_perm & EXECUTE)) # True (execute permission added)
# Revoke permission — AND with NOT
user_perm &= ~WRITE
print(bool(user_perm & WRITE)) # False (write permission removed)
# Toggle permission — bitwise XOR
user_perm ^= EXECUTE
print(bool(user_perm & EXECUTE)) # False (toggled — removed if present)
user_perm ^= EXECUTE
print(bool(user_perm & EXECUTE)) # True (toggled — added if absent)
# Wrapping it in a practical class
class Permission:
READ = 1 << 2 # 4
WRITE = 1 << 1 # 2
EXECUTE = 1 << 0 # 1
def __init__(self, value: int = 0):
self._value = value
def grant(self, perm: int) -> "Permission":
return Permission(self._value | perm)
def revoke(self, perm: int) -> "Permission":
return Permission(self._value & ~perm)
def has(self, perm: int) -> bool:
return bool(self._value & perm)
def __str__(self) -> str:
r = "r" if self.has(Permission.READ) else "-"
w = "w" if self.has(Permission.WRITE) else "-"
x = "x" if self.has(Permission.EXECUTE) else "-"
return f"{r}{w}{x}"
perm = Permission()
perm = perm.grant(Permission.READ | Permission.WRITE)
print(perm) # rw-
perm = perm.grant(Permission.EXECUTE)
print(perm) # rwx
perm = perm.revoke(Permission.WRITE)
print(perm) # r-x
Even/Odd Check (n & 1)
# Fast even/odd check using bitwise AND
# The least significant bit of an odd number is always 1
def is_odd_bitwise(n: int) -> bool:
return bool(n & 1)
def is_even_bitwise(n: int) -> bool:
return not (n & 1)
for i in range(10):
parity = "odd" if is_odd_bitwise(i) else "even"
print(f"{i}: {parity}")
Power of Two Check and Fast Operations
# Check if n is a power of two
# A power of two has exactly one bit set in binary
def is_power_of_two(n: int) -> bool:
return n > 0 and (n & (n - 1)) == 0
test_values = [1, 2, 3, 4, 8, 16, 15, 32, 100]
for v in test_values:
print(f"{v:4d}: {is_power_of_two(v)}")
# 1: True, 2: True, 3: False, 4: True, 8: True ...
# Fast division by a power of two (using right shift)
x = 1024
print(x >> 1) # 512 (x // 2)
print(x >> 3) # 128 (x // 8)
print(x >> 10) # 1 (x // 1024)
# Fast multiplication by a power of two (using left shift)
y = 7
print(y << 1) # 14 (y * 2)
print(y << 3) # 56 (y * 8)
print(y << 4) # 112 (y * 16)
3. The @ Operator — Matrix Multiplication (PEP 465)
The @ operator was introduced in Python 3.5+ as the matrix multiplication operator.
# The @ operator cannot be used directly on built-in list types
# (built-in list does not support it)
# numpy installation: pip install numpy
import numpy as np
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
# Matrix multiplication with the @ operator
result = A @ B
print(result)
# [[19 22]
# [43 50]]
# Same result using the older approach
print(np.dot(A, B))
print(A.dot(B))
# Dot product of vectors
v1 = np.array([1, 2, 3])
v2 = np.array([4, 5, 6])
print(v1 @ v2) # 32 (1*4 + 2*5 + 3*6)
# Implementing @ in a custom class
class Matrix:
def __init__(self, data: list[list[float]]):
self.data = data
self.rows = len(data)
self.cols = len(data[0])
def __matmul__(self, other: "Matrix") -> "Matrix":
"""Implement the @ operator"""
if self.cols != other.rows:
raise ValueError("Matrix dimensions do not match")
result = [[sum(self.data[i][k] * other.data[k][j]
for k in range(self.cols))
for j in range(other.cols)]
for i in range(self.rows)]
return Matrix(result)
def __str__(self) -> str:
return "\n".join(str(row) for row in self.data)
4. Deep Dive into // and % — Behavior with Negative Numbers
# Python's // is mathematical floor division
# Different from integer truncation in C and Java
# Positive numbers
print(7 // 2) # 3 (same)
print(7 % 2) # 1 (same)
# Negative numbers — difference appears!
print(-7 // 2) # -4 (Python: floors toward negative infinity)
print(-7 % 2) # 1 (remainder is always non-negative!)
# In C/Java: -7 / 2 = -3 (truncates toward zero)
# In Python: -7 // 2 = -4 (floors toward negative infinity)
# Verification: a == (a // b) * b + (a % b)
a, b = -7, 2
print((a // b) * b + (a % b)) # -7 ✓
# Python's always-non-negative remainder is useful for circular indexing
def circular_index(i: int, size: int) -> int:
return i % size
print(circular_index(7, 5)) # 2 (7 % 5)
print(circular_index(-1, 5)) # 4 (Python: -1 % 5 = 4!)
print(circular_index(-7, 5)) # 3 (-7 % 5 = 3)
# In C/Java, -1 % 5 = -1, so additional handling is required
5. Operator Precedence
| Precedence (high → low) | Operators |
|---|---|
| 1 (highest) | () parentheses |
| 2 | ** exponentiation |
| 3 | +x, -x, ~x unary operators |
| 4 | *, /, //, % |
| 5 | +, - |
| 6 | <<, >> bit shift |
| 7 | & bitwise AND |
| 8 | ^ bitwise XOR |
| 9 | | bitwise OR |
| 10 | ==, !=, <, >, <=, >=, is, in |
| 11 | not |
| 12 | and |
| 13 (lowest) | or |
# Precedence examples
print(2 + 3 * 4) # 14 (*, then +)
print((2 + 3) * 4) # 20 (parentheses first)
print(2 ** 3 ** 2) # 512 (** is right-associative: 3**2=9, 2**9=512)
print(not True or False) # False (not first: False or False)
print(not (True or False)) # False (parentheses first: True, not True = False)
# When in doubt, always use parentheses!
result = (a & b) | (c ^ d) # clear intent
6. Complete Augmented Assignment Operators
x = 10
x += 5 # 15 (addition)
x -= 3 # 12 (subtraction)
x *= 2 # 24 (multiplication)
x /= 4 # 6.0 (division, float)
x //= 2 # 3.0 (floor division)
x **= 3 # 27.0 (exponentiation)
x %= 10 # 7.0 (modulo)
y = 0b1111
y &= 0b1010 # 0b1010 = 10 (bitwise AND)
y |= 0b0001 # 0b1011 = 11 (bitwise OR)
y ^= 0b1100 # 0b0111 = 7 (bitwise XOR)
y <<= 1 # 0b1110 = 14 (left shift)
y >>= 2 # 0b0011 = 3 (right shift)
# Python 3.9+ dictionary merge assignment
d1 = {"a": 1, "b": 2}
d2 = {"b": 3, "c": 4}
d1 |= d2
print(d1) # {'a': 1, 'b': 3, 'c': 4}
Pro Tips: Bitwise Manipulation Patterns
XOR swap — swapping two integers without a temporary variable:
# XOR swap — exchange two integers without a temp variable
a, b = 5, 9
print(f"Before swap: a={a}, b={b}")
a ^= b
b ^= a
a ^= b
print(f"After swap: a={a}, b={b}")
# Before swap: a=5, b=9
# After swap: a=9, b=5
# How it works:
# a = a ^ b = 5 ^ 9 = 12 (0101 ^ 1001 = 1100)
# b = b ^ a = 9 ^ 12 = 5 (1001 ^ 1100 = 0101)
# a = a ^ b = 12 ^ 5 = 9 (1100 ^ 0101 = 1001)
# Note: in Python, a, b = b, a is more concise and safe
Bit counting (popcount):
def count_set_bits(n: int) -> int:
"""Returns the number of bits set to 1."""
count = 0
while n:
count += n & 1
n >>= 1
return count
# Alternative using bin()
def count_set_bits_v2(n: int) -> int:
return bin(n).count('1')
print(count_set_bits(255)) # 8 (11111111)
print(count_set_bits(170)) # 4 (10101010)
print(count_set_bits_v2(255)) # 8
# Real-world example: Hamming distance (number of differing bits between two integers)
def hamming_distance(a: int, b: int) -> int:
return bin(a ^ b).count('1')
print(hamming_distance(1, 4)) # 2 (001 vs 100 — 2 bits differ)
print(hamming_distance(93, 73)) # 2
You have mastered bitwise operators and special operators. The next chapter takes a deep look at Python's powerful string features.