Python Bitwise Operators Tutorial

Avatar

By squashlabs, Last Updated: September 8, 2023

Python Bitwise Operators Tutorial

Introduction to Bitwise Operators

Bitwise operators in Python are used to perform operations on individual bits of binary numbers. These operators allow you to manipulate and extract specific bits, which can be useful in various scenarios such as binary number manipulation, data compression, encryption, and more.

Python provides several bitwise operators, including AND, OR, XOR, NOT, left shift, right shift, ones complement, and twos complement. Each operator performs a specific operation on the binary representation of numbers.

Related Article: How To Limit Floats To Two Decimal Points In Python

The AND Operator

The AND operator, represented by the ampersand (&) symbol, performs a bitwise AND operation on two numbers. It compares the corresponding bits of the two numbers and returns a new number where each bit is set to 1 only if both bits in the same position are 1.

Here’s an example of using the AND operator:

a = 5  # Binary: 0101
b = 3  # Binary: 0011

result = a & b  # Binary: 0001
print(result)  # Output: 1

In this example, the AND operator compares the bits of a and b and returns a new number where only the rightmost bit is set to 1 because it is the only bit that is 1 in both a and b.

Another example:

a = 12  # Binary: 1100
b = 10  # Binary: 1010

result = a & b  # Binary: 1000
print(result)  # Output: 8

In this case, the AND operator compares the bits of a and b and returns a new number where only the leftmost bit is set to 1 because it is the only bit that is 1 in both a and b.

The OR Operator

The OR operator, represented by the pipe (|) symbol, performs a bitwise OR operation on two numbers. It compares the corresponding bits of the two numbers and returns a new number where each bit is set to 1 if either of the bits in the same position is 1.

Here’s an example of using the OR operator:

a = 5  # Binary: 0101
b = 3  # Binary: 0011

result = a | b  # Binary: 0111
print(result)  # Output: 7

In this example, the OR operator compares the bits of a and b and returns a new number where all the bits are set to 1 if either of the bits in the same position is 1.

Another example:

a = 12  # Binary: 1100
b = 10  # Binary: 1010

result = a | b  # Binary: 1110
print(result)  # Output: 14

In this case, the OR operator compares the bits of a and b and returns a new number where all the bits are set to 1 if either of the bits in the same position is 1.

The XOR Operator

The XOR operator, represented by the caret (^) symbol, performs a bitwise XOR operation on two numbers. It compares the corresponding bits of the two numbers and returns a new number where each bit is set to 1 if the bits in the same position are different.

Here’s an example of using the XOR operator:

a = 5  # Binary: 0101
b = 3  # Binary: 0011

result = a ^ b  # Binary: 0110
print(result)  # Output: 6

In this example, the XOR operator compares the bits of a and b and returns a new number where each bit is set to 1 if the bits in the same position are different.

Another example:

a = 12  # Binary: 1100
b = 10  # Binary: 1010

result = a ^ b  # Binary: 0110
print(result)  # Output: 6

In this case, the XOR operator compares the bits of a and b and returns a new number where each bit is set to 1 if the bits in the same position are different.

Related Article: How To Rename A File With Python

The NOT Operator

The NOT operator, represented by the tilde (~) symbol, performs a bitwise NOT operation on a number. It flips all the bits of the number, setting the 0s to 1s and the 1s to 0s.

Here’s an example of using the NOT operator:

a = 5  # Binary: 0101

result = ~a  # Binary: 1010 (signed representation)
print(result)  # Output: -6

In this example, the NOT operator flips all the bits of a and returns the signed representation of the result. The output is -6 because the signed representation of the binary number 1010 is -6.

Another example:

a = 12  # Binary: 1100

result = ~a  # Binary: 0011 (signed representation)
print(result)  # Output: -13

In this case, the NOT operator flips all the bits of a and returns the signed representation of the result. The output is -13 because the signed representation of the binary number 0011 is -13.

The Left Shift Operator

The left shift operator, represented by the double less-than (<<) symbol, shifts the bits of a number to the left by a specified number of positions. It effectively multiplies the number by 2 raised to the power of the specified shift amount.

Here's an example of using the left shift operator:

a = 5  # Binary: 0101

result = a << 2  # Binary: 010100
print(result)  # Output: 20

In this example, the left shift operator shifts the bits of a to the left by 2 positions, effectively multiplying the number by 2 raised to the power of 2. The output is 20.

Another example:

a = 12  # Binary: 1100

result = a << 3  # Binary: 1100000
print(result)  # Output: 96

In this case, the left shift operator shifts the bits of a to the left by 3 positions, effectively multiplying the number by 2 raised to the power of 3. The output is 96.

The Right Shift Operator

The right shift operator, represented by the double greater-than (>>) symbol, shifts the bits of a number to the right by a specified number of positions. It effectively divides the number by 2 raised to the power of the specified shift amount, discarding any remainders.

Here’s an example of using the right shift operator:

a = 20  # Binary: 010100

result = a >> 2  # Binary: 0101
print(result)  # Output: 5

In this example, the right shift operator shifts the bits of a to the right by 2 positions, effectively dividing the number by 2 raised to the power of 2. The output is 5.

Another example:

a = 96  # Binary: 1100000

result = a >> 3  # Binary: 1100
print(result)  # Output: 12

In this case, the right shift operator shifts the bits of a to the right by 3 positions, effectively dividing the number by 2 raised to the power of 3. The output is 12.

Related Article: How To Check If List Is Empty In Python

The Binary Ones Complement Operator

The binary ones complement operator, represented by the tilde (~) symbol, performs a ones complement operation on a number. It flips all the bits of the number, setting the 0s to 1s and the 1s to 0s.

Here’s an example of using the ones complement operator:

a = 5  # Binary: 0101

result = ~a  # Binary: 1010 (unsigned representation)
print(result)  # Output: -6

In this example, the ones complement operator flips all the bits of a and returns the unsigned representation of the result. The output is -6 because the unsigned representation of the binary number 1010 is -6.

Another example:

a = 12  # Binary: 1100

result = ~a  # Binary: 0011 (unsigned representation)
print(result)  # Output: -13

In this case, the ones complement operator flips all the bits of a and returns the unsigned representation of the result. The output is -13 because the unsigned representation of the binary number 0011 is -13.

The Binary Twos Complement Operator

The binary twos complement operator is used to represent negative numbers in binary form. It is obtained by taking the ones complement of a number and adding 1 to the result.

Here’s an example of using the twos complement operator:

a = 5  # Binary: 0101

result = -a  # Binary: 1011
print(result)  # Output: -5

In this example, the twos complement operator represents the negative value of a by taking the ones complement of a and adding 1 to the result. The output is -5.

Another example:

a = 12  # Binary: 1100

result = -a  # Binary: 0100
print(result)  # Output: -12

In this case, the twos complement operator represents the negative value of a by taking the ones complement of a and adding 1 to the result. The output is -12.

Use Case: Binary Number Manipulation

One common use case for bitwise operators is binary number manipulation. By manipulating the individual bits of a binary number, you can perform operations such as extracting specific bits, setting bits to 1 or 0, and flipping bits.

Here’s an example of manipulating binary numbers using bitwise operators:

# Extracting specific bits
number = 53  # Binary: 110101
bit_0 = number & 1  # Extracting the rightmost bit
bit_1 = (number >> 1) & 1  # Extracting the second rightmost bit
bit_2 = (number >> 2) & 1  # Extracting the third rightmost bit

print(bit_0, bit_1, bit_2)  # Output: 1 0 1

# Setting bits to 1
number = 53  # Binary: 110101
number = number | (1 << 3)  # Setting the fourth rightmost bit to 1

print(number)  # Output: 61 (Binary: 111101)

# Flipping bits
number = 53  # Binary: 110101
flipped_number = ~number  # Flipping all the bits

print(flipped_number)  # Output: -54 (Binary: 110110)

In this example, we extract specific bits from a binary number, set a bit to 1, and flip all the bits using bitwise operators.

Related Article: How To Check If a File Exists In Python

Use Case: Flags and Masks

Bitwise operators are commonly used for manipulating flags and masks. Flags are binary values that represent certain conditions or settings, while masks are binary patterns used to selectively modify bits.

Here’s an example of using bitwise operators for flags and masks:

# Flags
READ = 1  # Binary: 0001
WRITE = 2  # Binary: 0010
EXECUTE = 4  # Binary: 0100

permissions = READ | WRITE  # Setting the READ and WRITE flags

if permissions & READ:
    print("Read permission granted.")

if permissions & WRITE:
    print("Write permission granted.")

if permissions & EXECUTE:
    print("Execute permission granted.")  # This condition is not met

# Masks
number = 53  # Binary: 110101
mask = 15  # Binary: 1111

masked_number = number & mask  # Applying the mask

print(masked_number)  # Output: 5 (Binary: 0101)

In this example, we use bitwise OR to set flags for permissions and bitwise AND to check if a certain flag is set. We also use bitwise AND to apply a mask to a number, isolating specific bits.

Use Case: Data Compression and Encryption

Bitwise operators are also used in data compression and encryption algorithms. These algorithms often involve manipulating and transforming binary data to achieve compression or encryption.

Here’s a simplified example of using bitwise operators for data compression:

data = "Hello, world!"  # ASCII representation: 72 101 108 108 111 44 32 119 111 114 108 100 33

# Compression
compressed_data = ""
for char in data:
    compressed_data += str(ord(char) & 15)  # Take the first 4 bits of each ASCII code

print(compressed_data)  # Output: 881881811144211416131321

# Decompression
decompressed_data = ""
for i in range(0, len(compressed_data), 2):
    ascii_code = int(compressed_data[i:i+2]) | 64  # Add 64 to reconstruct the ASCII code
    decompressed_data += chr(ascii_code)

print(decompressed_data)  # Output: Hello, world!

In this example, we compress the ASCII representation of the string “Hello, world!” by taking the first 4 bits of each ASCII code. We then decompress the compressed data by reconstructing the ASCII codes and converting them back to characters.

Best Practice: Ensuring Compatibility with Different Python Versions

When using bitwise operators in Python, it’s important to ensure compatibility with different Python versions. While the behavior of bitwise operators is generally consistent across versions, there are some differences to be aware of.

One common difference is the handling of negative numbers. In Python 2, the right shift operator (>>) preserves the sign bit when shifting right, while in Python 3, it fills the shifted bits with 0 regardless of the sign.

To ensure compatibility, it’s recommended to use the sys.maxsize constant to determine the number of bits in an integer and to use bitwise operators in a way that doesn’t rely on implementation details.

Here’s an example of ensuring compatibility with different Python versions:

import sys

# Right shift with negative numbers
number = -5

if sys.version_info.major == 2:
    result = number >> 1  # Python 2: Preserves the sign bit
else:
    result = number // 2  # Python 3: Fills the shifted bits with 0

print(result)  # Output: -3 in Python 2, -3 in Python 3

In this example, we check the Python version using sys.version_info.major and handle the right shift differently depending on the version.

Related Article: How to Use Inline If Statements for Print in Python

Best Practice: Using Parentheses for Clarity

When performing complex bitwise operations, it’s often a good practice to use parentheses to clarify the intended order of operations. This helps avoid confusion and ensures that the operations are evaluated correctly.

Here’s an example of using parentheses for clarity:

a = 5
b = 3

result = (a ^ b) & ((a | b) << 2)

print(result)  # Output: 28

In this example, we use parentheses to group the XOR and OR operations separately, and then perform the AND and left shift operations on the results.

Real World Example: Implementing a Simple Encryption Algorithm

Bitwise operators can be used to implement simple encryption algorithms. One such algorithm is the XOR cipher, which works by XORing each character of a message with a key. This algorithm is reversible, meaning that applying the same key again will decrypt the message.

Here’s an example of implementing a simple XOR encryption algorithm in Python:

def xor_cipher(message, key):
    encrypted_message = ""
    for i, char in enumerate(message):
        encrypted_char = chr(ord(char) ^ ord(key[i % len(key)]))
        encrypted_message += encrypted_char
    return encrypted_message

message = "Hello, world!"
key = "secret"

encrypted_message = xor_cipher(message, key)
decrypted_message = xor_cipher(encrypted_message, key)

print(encrypted_message)  # Output: '\x05\x10\x04\x04\x1bK\x01\x1e\x0f\x08\x1a\x05\x1e\nK'
print(decrypted_message)  # Output: 'Hello, world!'

In this example, the xor_cipher function takes a message and a key as input. It XORs each character of the message with the corresponding character of the key, repeating the key if it is shorter than the message. The result is an encrypted message. To decrypt the message, the same key is applied again.

Real World Example: Building a Binary Calculator

Bitwise operators can be used to build a binary calculator, which performs arithmetic operations on binary numbers. A binary calculator can add, subtract, multiply, and divide binary numbers using bitwise operators.

Here’s an example of building a binary calculator in Python:

def binary_addition(a, b):
    carry = 0
    result = 0
    bit_position = 1

    while a != 0 or b != 0:
        bit_a = a & 1
        bit_b = b & 1

        sum_bits = bit_a ^ bit_b ^ carry
        carry = (bit_a & bit_b) | (bit_a & carry) | (bit_b & carry)
        result |= (sum_bits <>= 1
        b >>= 1
        bit_position += 1

    result |= (carry << bit_position)

    return result

a = 10  # Binary: 1010
b = 5  # Binary: 0101

sum_result = binary_addition(a, b)

print(sum_result)  # Output: 15 (Binary: 1111)

In this example, the binary_addition function takes two binary numbers a and b as input and performs binary addition using bitwise operators. It iterates through the bits of the numbers, calculates the sum and carry bits, and constructs the result by setting the appropriate bits.

Related Article: How to Use Stripchar on a String in Python

Performance Consideration: Bitwise vs Arithmetic Operations

When performing simple operations on individual bits, bitwise operators are generally faster than arithmetic operations. This is because bitwise operations work at the binary level, directly manipulating the bits, while arithmetic operations involve more complex calculations.

Here’s an example comparing the performance of bitwise and arithmetic operations:

import time

# Bitwise operations
start_time = time.time()
result = 0
for i in range(1000000):
    result |= (1 << i)
end_time = time.time()
bitwise_time = end_time - start_time

# Arithmetic operations
start_time = time.time()
result = 0
for i in range(1000000):
    result += (2 ** i)
end_time = time.time()
arithmetic_time = end_time - start_time

print("Bitwise time:", bitwise_time)
print("Arithmetic time:", arithmetic_time)

In this example, we measure the time taken to set all the bits from 0 to 999,999 using bitwise operations and arithmetic operations. The bitwise operations are expected to be faster due to the lower level of complexity involved.

Performance Consideration: Bitwise Operations and Memory Usage

Bitwise operations can be memory-efficient compared to other operations. Since bitwise operators work at the binary level, they allow you to represent and manipulate data using fewer bits, which can lead to reduced memory usage.

Here’s an example demonstrating the memory efficiency of bitwise operations:

import sys

a = 100  # Binary: 1100100
b = 50  # Binary: 110010

bitwise_result = a & b  # Binary: 1100100

arithmetic_result = a + b  # Decimal: 150 (Binary: 10010110)

bitwise_size = sys.getsizeof(bitwise_result)
arithmetic_size = sys.getsizeof(arithmetic_result)

print("Bitwise size:", bitwise_size)
print("Arithmetic size:", arithmetic_size)

In this example, we compare the memory usage of a bitwise result and an arithmetic result. The bitwise result requires fewer bits to represent the same information, resulting in a smaller memory size.

Advanced Technique: Bitwise Operations and Binary Trees

Bitwise operations can be used in conjunction with binary trees to efficiently store and manipulate binary data. By using bitwise operators, you can perform operations such as finding the parent, left child, or right child of a node in a binary tree.

Here’s an example of using bitwise operations with binary trees:

def get_parent(node):
    return node >> 1

def get_left_child(node):
    return (node << 1) + 1

def get_right_child(node):
    return (node << 1) + 2

node = 5

parent = get_parent(node)
left_child = get_left_child(node)
right_child = get_right_child(node)

print(parent)  # Output: 2
print(left_child)  # Output: 11
print(right_child)  # Output: 12

In this example, the get_parent, get_left_child, and get_right_child functions use bitwise operators to calculate the parent, left child, and right child of a given node in a binary tree.

Related Article: How To Delete A File Or Folder In Python

Advanced Technique: Bitwise Operations and Hash Functions

Bitwise operations can be used in hash functions to efficiently generate hash values for data. By applying bitwise operators to the binary representation of the data, you can create hash functions that distribute the hash values evenly across a hash table.

Here’s an example of using bitwise operations with hash functions:

def hash_function(data):
    hash_value = 0
    for byte in data:
        hash_value ^= byte
        hash_value = (hash_value <> 31)  # Rotate the hash value
    return hash_value

data = b"Hello, world!"

hash_value = hash_function(data)

print(hash_value)  # Output: 4098336486

In this example, the hash_function applies bitwise XOR and bitwise rotation to each byte of the data to generate a hash value. The hash value is then used to index into a hash table.

Code Snippet: Using Bitwise AND to Determine Even or Odd

Bitwise AND can be used to determine whether a number is even or odd. By ANDing a number with 1, the rightmost bit (the least significant bit) can be checked. If the result is 0, the number is even; otherwise, it is odd.

Here’s a code snippet demonstrating the use of bitwise AND to determine even or odd:

def is_even(number):
    return (number & 1) == 0

def is_odd(number):
    return (number & 1) == 1

number = 10

print(is_even(number))  # Output: True
print(is_odd(number))  # Output: False

In this code snippet, the is_even function checks whether a number is even by ANDing it with 1 and comparing the result to 0. The is_odd function does the same but compares the result to 1.

Code Snippet: Using Bitwise XOR for Data Swapping

Bitwise XOR can be used to swap the values of two variables without using a temporary variable. By XORing a variable with another variable and then XORing the result with the original variable, the values are swapped.

Here’s a code snippet demonstrating the use of bitwise XOR for data swapping:

a = 5
b = 10

a = a ^ b
b = a ^ b
a = a ^ b

print(a)  # Output: 10
print(b)  # Output: 5

In this code snippet, the values of a and b are swapped using bitwise XOR operations. The same principle can be applied to swap the values of variables of any type.

Related Article: How To Move A File In Python

Code Snippet: Using Bitwise NOT for Binary Inversion

Bitwise NOT can be used to invert the bits of a binary number, effectively changing all the 0s to 1s and vice versa. By applying the NOT operator to a number, the complement of the number is obtained.

Here’s a code snippet demonstrating the use of bitwise NOT for binary inversion:

number = 5

inverted_number = ~number

print(inverted_number)  # Output: -6

In this code snippet, the bitwise NOT operator is used to invert the bits of the number 5. The result is -6 because the signed representation of the binary number 1010 is -6.

Code Snippet: Using Left Shift for Multiplication

Left shift can be used to multiply a number by a power of 2. By shifting the bits of a number to the left, the number is effectively multiplied by 2 raised to the power of the shift amount.

Here’s a code snippet demonstrating the use of left shift for multiplication:

number = 5

multiplied_number = number << 2

print(multiplied_number)  # Output: 20

In this code snippet, the left shift operator is used to multiply the number 5 by 2 raised to the power of 2. The result is 20.

Code Snippet: Using Right Shift for Division

Right shift can be used to divide a number by a power of 2. By shifting the bits of a number to the right, the number is effectively divided by 2 raised to the power of the shift amount.

Here’s a code snippet demonstrating the use of right shift for division:

number = 20

divided_number = number >> 2

print(divided_number)  # Output: 5

In this code snippet, the right shift operator is used to divide the number 20 by 2 raised to the power of 2. The result is 5.

Related Article: How to Implement a Python Foreach Equivalent

Error Handling: Dealing with Overflow Errors

When working with bitwise operators, it’s important to be aware of potential overflow errors that can occur when manipulating numbers with a fixed number of bits. An overflow occurs when the result of an operation cannot be represented using the available number of bits.

To deal with overflow errors, you can use Python’s built-in support for arbitrary-precision arithmetic by using the int type instead of the built-in integer types (int, long, etc.). The int type automatically adjusts its size to accommodate the result of an operation.

Here’s an example of dealing with overflow errors using the int type:

a = 2 ** 1000
b = 2 ** 1000

result = int(a) & int(b)

print(result)  # Output: 0

In this example, a and b are large numbers that would cause an overflow error if used with the built-in integer types. By converting them to int objects, Python automatically handles the overflow and produces the correct result.

Error Handling: Handling Invalid Bitwise Operation Inputs

When performing bitwise operations, it’s important to handle cases where the inputs are not valid for the intended operation. This can include cases such as dividing by zero, shifting by a negative amount, or applying bitwise operators to non-integer values.

To handle these cases, it’s recommended to use appropriate conditional statements and exception handling to ensure the program behaves correctly and gracefully handles invalid inputs.

Here’s an example of handling invalid bitwise operation inputs:

def left_shift(number, shift):
    if shift < 0:
        raise ValueError("Shift amount must be non-negative.")
    return number << shift

def right_shift(number, shift):
    if shift > shift

def bitwise_and(a, b):
    if not isinstance(a, int) or not isinstance(b, int):
        raise TypeError("Inputs must be integers.")
    return a & b

number = 5
shift = -2
a = 5
b = "10"

try:
    result = left_shift(number, shift)
    print(result)
except ValueError as e:
    print("Error:", str(e))

try:
    result = right_shift(number, shift)
    print(result)
except ValueError as e:
    print("Error:", str(e))

try:
    result = bitwise_and(a, b)
    print(result)
except TypeError as e:
    print("Error:", str(e))

In this example, the functions left_shift, right_shift, and bitwise_and check for invalid inputs and raise appropriate exceptions. The program then catches these exceptions and handles them accordingly, displaying an error message.

More Articles from the Python Tutorial: From Basics to Advanced Concepts series:

How to Use Slicing in Python And Extract a Portion of a List

Slicing operations in Python allow you to manipulate data efficiently. This article provides a simple guide on using slicing, covering the syntax, positive and negative... read more

How to Check a Variable’s Type in Python

Determining the type of a variable in Python is a fundamental task for any programmer. This article provides a guide on how to check a variable's type using the... read more

How to Use Increment and Decrement Operators in Python

This article provides a guide on the behavior of increment and decrement operators in Python. It covers topics such as using the += and -= operators, using the ++ and --... read more

How to Import Other Python Files in Your Code

Simple instructions for importing Python files to reuse code in your projects. This article covers importing a Python module, importing a Python file as a script,... read more

How to Use Named Tuples in Python

Named tuples are a useful feature in Python programming that allows you to create lightweight, immutable data structures. This article provides a simple guide on how to... read more

How to Work with CSV Files in Python: An Advanced Guide

Processing CSV files in Python has never been easier. In this advanced guide, we will transform the way you work with CSV files. From basic data manipulation techniques... read more