Python Bitwise Operators Tutorial

Avatar

By squashlabs, Last Updated: Sept. 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: Python Numpy.where() Tutorial

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 Use Collections 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 &lt;&lt; 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 &lt;&lt; 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 &gt;&gt; 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 &gt;&gt; 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.

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.

Related Article: How to Find a Value in a Python List

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 &amp; 1  # Extracting the rightmost bit
bit_1 = (number &gt;&gt; 1) &amp; 1  # Extracting the second rightmost bit
bit_2 = (number &gt;&gt; 2) &amp; 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 &lt;&lt; 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.

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 &amp; READ:
    print("Read permission granted.")

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

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

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

masked_number = number &amp; 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) &amp; 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.

Related Article: How to Append One String to Another in Python

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 (&gt;&gt;) 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 &gt;&gt; 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.

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) &amp; ((a | b) &lt;&lt; 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 &amp; 1
        bit_b = b &amp; 1

        sum_bits = bit_a ^ bit_b ^ carry
        carry = (bit_a &amp; bit_b) | (bit_a &amp; carry) | (bit_b &amp; carry)
        result |= (sum_bits &lt;&gt;= 1
        b &gt;&gt;= 1
        bit_position += 1

    result |= (carry &lt;&lt; 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 the to_timestamp Function in Python and Pandas

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 &lt;&lt; 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(&quot;Bitwise time:&quot;, bitwise_time)
print(&quot;Arithmetic time:&quot;, 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 &amp; 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 &gt;&gt; 1

def get_left_child(node):
    return (node &lt;&lt; 1) + 1

def get_right_child(node):
    return (node &lt;&lt; 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.

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 &lt;&gt; 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.

Related Article: How to Work with Lists and Arrays in Python

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 &amp; 1) == 0

def is_odd(number):
    return (number &amp; 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.

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 &lt;&lt; 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.

Related Article: Python Scikit Learn Tutorial

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 &gt;&gt; 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.

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) &amp; 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 &lt; 0:
        raise ValueError(&quot;Shift amount must be non-negative.&quot;)
    return number &lt;&lt; shift

def right_shift(number, shift):
    if shift &gt; shift

def bitwise_and(a, b):
    if not isinstance(a, int) or not isinstance(b, int):
        raise TypeError("Inputs must be integers.")
    return a &amp; 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 Read JSON From a File In Python

Reading JSON data from a file in Python is a common task for many developers. In this tutorial, you will learn different methods to read JSON from a … read more

Seamless Integration of Flask with Frontend Frameworks

Setting up Flask with frontend frameworks like React.js, Vue.js, and HTMX can greatly enhance the capabilities of web applications. This article expl… read more

Tutorial: Django + MongoDB, ElasticSearch & Message Brokers

This article explores how to integrate MongoDB, ElasticSearch, and message brokers with Python Django. Learn about the advantages of using NoSQL data… read more

How to Convert a String to Boolean in Python

Guide on converting strings to Boolean values in Python, a basic yet crucial task. This article covers using the bool() function, using a dictionary,… read more

How to Implement Data Science and Data Engineering Projects with Python

Data science and data engineering are essential skills in today's technology-driven world. This article provides a and practical guide to implementin… read more

19 Python Code Snippets for Everyday Issues

Learn how to solve everyday programming problems with 19 Python code snippets. From finding the maximum value in a list to removing duplicates, these… read more

How To Limit Floats To Two Decimal Points In Python

Formatting floats to two decimal points in Python can be easily achieved using the format() function or the round() function. This article explores t… read more

How to Send an Email Using Python

Sending emails using Python can be a simple and process. This article will guide you through the steps of setting up email parameters, creating the e… read more

Python Super Keyword Tutorial

Python's super keyword is a powerful tool that can enhance code functionality. In this tutorial, you will learn how to use the super method to improv… read more

How To Change Matplotlib Figure Size

Learn how to adjust the size of figures drawn using Matplotlib in Python for better visualization and presentation. This article will guide you throu… read more