Kodeclik Blog
Shifting octets in Python
An octet is a unit of digital information consisting of exactly eight bits. While often used synonymously with a byte, the term octet is more precise as it always represents 8 bits, whereas historically, bytes could vary in size. One octet can represent values from 0 to 255 (binary 00000000 to 11111111).
The left shift (<<) and right shift (>>) operators in Python can be used to manipulate octets. Here is a very simple program to illustrate the idea:
number = 0b10101010
print(f"Original number: {format(number, '08b')} ({number})")
number = (number << 1) & 0xFF
print(f"After first shift: {format(number, '08b')} ({number})")
number = (number << 1) & 0xFF
print(f"After second shift: {format(number, '08b')} ({number})")
In this code, we start with a binary number (that's a number made up of just 0s and 1s) written as 0b10101010 - the '0b' at the start just tells Python it's a binary number. The code then prints this number in two ways: as an 8-digit binary number and as the regular decimal number you're used to seeing. Note that 10101010 in binary is 170 because 128 (1×2⁷) + 0 (0×2⁶) + 32 (1×2⁵) + 0 (0×2⁴) + 8 (1×2³) + 0 (0×2²) + 2 (1×2¹) + 0 (0×2⁰) = 128 + 32 + 8 + 2 = 170.
Then comes the fun part: we use '<<' which is like telling the computer "slide all the digits one space to the left and put a zero at the end." The '& 0xFF' part makes sure our number stays as 8 digits. The “<< 1” means we want to shift it by one position.
We do this sliding action twice, and each time we print out what our new number looks like in both binary and decimal. It's similar to how when you multiply by 10 in regular math, you add a zero at the end - but here we're working with binary numbers and sliding bits instead!
The output will be:
Original number: 10101010 (170)
After first shift: 01010100 (84)
After second shift: 10101000 (168)
Let us write some more generic programs to shift either left or right and shift a specified number of steps in one stroke! Here are three different programs.
Method 1: Using Bitwise Operators
Below is a program that generalizes the positions and directions of shifting octets in Python.
def shift_octet(value, positions, direction='left'):
print(f'Original octet: {format(value, "08b")}')
if direction == 'left':
result = (value << positions) & 0xFF
print(f'Shifting left {positions} positions results in: {format(result, "08b")}')
else:
result = (value >> positions) & 0xFF
print(f'Shifting right {positions} positions results in: {format(result, "08b")}')
return result
# Example usage
octet = 0b10101010
left_shifted = shift_octet(octet, 2, 'left')
right_shifted = shift_octet(octet, 2, 'right')
As mentioned earlier, this code implements a binary shift operation on an 8-bit value (octet), with the ability to shift either left or right by a specified number of positions. The function takes three parameters: the value to shift, the number of positions to shift, and the direction of the shift. Before performing the shift operation, it prints the original binary value formatted as an 8-bit string for clarity.
After performing the shift operation, the function applies a bitwise AND with 0xFF (11111111 in binary) to ensure the result stays within 8 bits, preventing any overflow. The function then prints the resulting binary value after the shift operation, clearly showing how the bits have moved in the specified direction. For a left shift, zeros are added on the right, while for a right shift, zeros are added on the left, as demonstrated in the output where 10101010 becomes 10101000 when shifted left by 2 positions, and 00101010 when shifted right by 2 positions.
The output will be:
Original octet: 10101010
Shifting left 2 positions results in: 10101000
Original octet: 10101010
Shifting right 2 positions results in: 00101010
Method 2: Use Binary String Manipulation
Here is a second approach that implements a binary shift operation using string manipulation rather than traditional bitwise operators.
def shift_octet_string(value, positions, direction='left'):
# Convert to binary string, removing '0b' prefix
binary = format(value, '08b')
print(f'Original octet: {binary}')
if direction == 'left':
shifted = binary[positions:] + '0' * positions
print(f'Shifting left {positions} positions results in: {shifted}')
else:
shifted = '0' * positions + binary[:-positions]
print(f'Shifting right {positions} positions results in: {shifted}')
return int(shifted, 2)
# Example usage with both left and right shifts
octet = 0b10101010
left_result = shift_octet_string(octet, 2, 'left')
right_result = shift_octet_string(octet, 2, 'right')
For left shifts, the program slices the binary string from the positions index to the end (binary[positions:]) and concatenates zeros at the end ('0' * positions). For example, shifting "10101010" left by 2 positions removes the first two digits and adds two zeros at the end, resulting in "10101000". Conversely, for right shifts, it prepends the specified number of zeros to the front and removes the same number of digits from the end of the string.
The function maintains the 8-bit format throughout the operation and converts the resulting binary string back to an integer using int(shifted, 2), where the base-2 parameter indicates binary conversion. The print statements provide visual feedback by showing the original binary representation and the shifted result, making it easier to understand how the bits are moving. This string manipulation approach, while perhaps not as computationally efficient as bitwise operations, offers a more intuitive way to visualize and understand binary shifts, especially for educational purposes.
The output will be:
Original octet: 10101010
Shifting left 2 positions results in: 10101000
Original octet: 10101010
Shifting right 2 positions results in: 00101010
Method 3: Use a class-based approach
Finally, here is an OctetShifter class that takes an object-oriented approach to handling binary shift operations:
class OctetShifter:
def __init__(self, value):
self.value = value & 0xFF # Ensure 8-bit value
def shift_left(self, positions):
print(f'Original octet: {format(self.value, "08b")}')
result = (self.value << positions) & 0xFF
print(f'Shifting left {positions} positions results in: {format(result, "08b")}')
return OctetShifter(result)
def shift_right(self, positions):
print(f'Original octet: {format(self.value, "08b")}')
result = (self.value >> positions) & 0xFF
print(f'Shifting right {positions} positions results in: {format(result, "08b")}')
return OctetShifter(result)
def __str__(self):
return format(self.value, '08b')
# Example usage
shifter = OctetShifter(0b10101010)
shifted_left = shifter.shift_left(2)
shifted_right = shifter.shift_right(2)
The class initializes with a value that's forced to be 8 bits (an octet) using the & 0xFF operation. This ensures we're always working with numbers between 0-255. Think of it like having a digital display with exactly 8 light bulbs that can each be either on (1) or off (0).
When you create a new OctetShifter with shifter = OctetShifter(0b10101010), it stores this binary number internally, ensuring it's only 8 bits long. Then methods like shift_left() and shift_right() implement the desired functionality, similar to Method 1 above.
The output will be the same as before:
Original octet: 10101010
Shifting left 2 positions results in: 10101000
Original octet: 10101010
Shifting right 2 positions results in: 00101010
Each shift operation creates a new instance of OctetShifter, allowing you to keep track of multiple states if needed, while the print statements show you exactly what's happening at each step of the transformation.
Applications of bit shifting
Bit shifting operations find extensive application in various areas of computer programming, particularly in scenarios where performance and memory optimization are crucial. In low-level programming and embedded systems, shifting is commonly used for fast multiplication and division by powers of two, as shifting left by n positions effectively multiplies a number by 2ⁿ, while shifting right divides by 2ⁿ. This technique is especially valuable in gaming and graphics programming, where quick mathematical operations are essential.
In networking and systems programming, bit shifting plays a vital role in data manipulation and storage optimization. Developers use shifts for packing multiple boolean flags into a single byte to save memory and improve data transmission efficiency. This is particularly important in control systems where reducing the number of transmitted data points can significantly improve performance. For instance, status information like on/off states, error conditions, and operational modes can be packed into a single 32-bit word, potentially reducing transmission times from seconds to milliseconds.
Graphics programming frequently employs bit shifting for color manipulation and RGB value processing. When working with color values, shifts help in combining or extracting individual color components efficiently. For instance, building RGBA values from individual components uses bit shifting to position each color channel in its proper position. Additionally, bit shifting is valuable in embedded systems and microcontroller programming where resources are limited, as well as in cryptography and data compression algorithms where efficient bit manipulation is crucial.
Enjoy this blogpost? Want to learn Python with us? Sign up for 1:1 or small group classes.