Skip to main content
Advertisement

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
11not
12and
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.

Advertisement