Although Python is not typically considered suitable for low-level programming, it still provides tools for this purpose. One such tool is bitwise (or binary) operators.
Bitwise operators in Python are designed for modifying binary code strings, which can be useful when working with cryptographic algorithms, device drivers, or network infrastructure. They can also be helpful for low-level graphics manipulation and any other tasks that require performing various operations on binary code.
The result of using bitwise operators in Python is the modification of an object at the bit level through different types of logical operations on binary code. This article will explore these operations.
Since we will be working with binary code, primarily with integers, let's first learn how to output these numbers in the required format. This is done very simply:
>>> bin(5)
'0b101'
Here, we get the binary representation of the number 5. But is it truly binary? In reality, the number 5 is represented by the last three digits (101), while the prefix 0b is used in Python to indicate binary notation (with -0b for negative values).
Now we can display any number in binary form. Below is what zero and the first ten numbers look like:
>>> bin(0)
0b0
>>> bin(1)
0b1
>>> bin(2)
0b10
>>> bin(3)
0b11
>>> bin(4)
0b100
>>> bin(5)
0b101
>>> bin(6)
0b110
>>> bin(7)
0b111
>>> bin(8)
0b1000
>>> bin(9)
0b1001
>>> bin(10)
0b1010
From this sequence, the principle of binary representation becomes clear: ones sequentially replace zeros, and when all values reach 1, a new bit position is added. Let's count up to 15:
>>> bin(11)
0b1011
>>> bin(12)
0b1100
>>> bin(13)
0b1101
>>> bin(14)
0b1110
>>> bin(15)
0b1111
The fourth bit position is now full (all ones), so a fifth position is introduced for numbers 16 and 17:
>>> bin(16)
0b10000
>>> bin(17)
0b10001
Thus, binary code follows strict mathematical rules:
In other words, a new bit position is added every time the number is doubled. This is useful when comparing operands with different bit widths: in such cases, you can pad the smaller operand with leading zeros immediately after 0b.
Now we are ready to operate with bits using the logic of the following tools:
&
(AND)|
(OR)^
(XOR)~
(NOT)Logic: When comparing two bits (in the same position), &
returns 1 (the bit is copied) if the bit exists in both operands and 0 if it is absent in at least one operand. The schematic representation is as follows:
1 & 1 = 1
1 & 0 = 0
0 & 1 = 0
0 & 0 = 0
This is the strictest condition, where a bit is returned (1) only if it was present in both operands.
Examples:
>>> 3 & 6
2
Because:
3 = 0b011
6 = 0b110
2 = 0b010
Adding a third bit to 3 for better representation, we see that only the middle bits match (both 1), so the result is 0b010, which is 2.
>>> 24 & 62
24
Because:
24 = 0b011000
62 = 0b111110
24 = 0b011000
An interesting result, as the matching bits align perfectly with the representation of the first number.
>>> 555 & 878
554
555 = 0b1000101011
878 = 0b1101101110
554 = 0b1000101010
Now, some more complex examples with numbers of different bit lengths:
>>> 80 & 755
80
80 = 0b0001010000
755 = 0b1011110011
80 = 0b0001010000
>>> 446 & 19
18
446 = 0b110111110
19 = 0b000010011
18 = 0b000010010
>>> 101 & 883
97
101 = 0b0001100101
883 = 0b1101110011
97 = 0b0001100001
Logic: When comparing two bits, |
returns 1 if the bit exists in at least one of the operands, and 0 if it is absent in both. The schematic representation:
1 | 1 = 1
1 | 0 = 1
0 | 1 = 1
0 | 0 = 0
Thus, the bit is returned in all cases except when both operands have 0.
Examples:
>>> 9 | 5
13
9 = 0b1001
5 = 0b0101
13 = 0b1101
A bit is not copied only in the second position (from the right) because both operands have 0 there.
>>> 87 | 59
127
87 = 0b1010111
59 = 0b0111011
127 = 0b1111111
>>> 846 | 657
991
846 = 0b1101001110
657 = 0b1010010001
991 = 0b1111011111
Now, some more complex cases:
>>> 80 | 755
755
80 = 0b0001010000
755 = 0b1011110011
755 = 0b1011110011
>>> 446 | 19
447
446 = 0b110111110
19 = 0b000010011
447 = 0b110111111
>>> 101 | 883
887
101 = 0b0001100101
883 = 0b1101110011
887 = 0b1101110111
Logic: When comparing two bits, ^
returns 1 if the operands are different, and 0 if they are the same. The schematic representation:
1 ^ 1 = 0
1 ^ 0 = 1
0 ^ 1 = 1
0 ^ 0 = 0
As we can see, XOR does not care whether the comparison involves two 1s or two 0s—in both cases, the bit is not returned. The bit is only returned when the values differ.
Examples:
>>> 5 ^ 2
7
5 = 0b0101
2 = 0b0010
7 = 0b0111
Only in the leftmost position do the operands match; in all other positions, they differ, so 1s are returned there.
>>> 90 ^ 92
6
90 = 0b1011010
92 = 0b1011100
6 = 0b0000110
>>> 352 ^ 686
974
352 = 0b0101100000
686 = 0b1010101110
974 = 0b1111001110
Some more complex cases with operands of different bit lengths:
>>> 80 ^ 755
675
80 = 0b0001010000
755 = 0b1011110011
675 = 0b1010100011
>>> 446 ^ 19
429
446 = 0b110111110
19 = 0b000010011
429 = 0b110101101
>>> 101 ^ 883
790
101 = 0b0001100101
883 = 0b1101110011
790 = 0b1100010110
~
does not compare values but inverts the bits in an integer value. Keep in mind that positive numbers will be converted to negative numbers with a shift of -1, and vice versa.
Examples:
>>> ~0
-1
>>> ~30
-31
>>> ~-30
29
>>> ~80
-81
>>> ~-80
79
>>> ~255
-256
>>> ~-255
254
The left shift is represented by the <<
symbol. The number being modified is written to the left, and the number of bits to shift is written to the right.
>>> 1 << 1
2
We shifted 1 by 1 bit to the left and got 2, because:
1 = 0b01
2 = 0b10
That is, the 1 moved one position to the left. What if we shift it by two positions?
>>> 1 << 2
4
Yes, we get 4, because:
1 = 0b001
4 = 0b100
It’s easy to guess what happens when shifting by 3 positions:
>>> 1 << 3
8
1 = 0b0001
8 = 0b1000
Here are some more examples with bit breakdowns:
>>> 10 << 1
20
10 = 0b01010
20 = 0b10100
>>> 10 << 2
40
10 = 0b001010
40 = 0b101000
The right shift is represented by the >>
symbol. Similar to the left shift, the number being modified is on the left, and the number of bits to shift is on the right. This is the reverse operation:
>>> 2 >> 1
1
>>> 4 >> 2
1
>>> 8 >> 3
1
>>> 40 >> 1
20
>>> 40 >> 2
10
One of the areas in IT where bitwise operations (especially shifts) are widely used is cryptography. Shift operations allow data values to be modified in such a way that decryption becomes impossible without the necessary keys storing the original values.
Another area of application for bitwise operations is networking technologies, where bit manipulations are essential for checking address and subnet matching.
Additionally, practicing bitwise AND (&
) and bitwise OR (|
) can help you better understand how the and and or operators work in Python, as well as how any programs based on Boolean logic (which relies on True (1) and False (0)) function.