Python’s bitwise NOT operator ~x inverts each bit from the binary representation of integer x so that 0 becomes 1 and 1 becomes 0. This is semantically the same as calculating ~x == -x-1. For example, the bitwise NOT expression ~0 becomes -1, ~9 becomes -10, and ~32 becomes -33.
As you go over the article, you can watch my explainer video here:
β₯οΈ Info: Are you AI curious but you still have to create real impactful projects? Join our official AI builder club on Skool (only $5): SHIP! - One Project Per Month
In this example, you apply the bitwise NOT operator to integer 32:
>>> ~32 -33
The expression ~32 operates on the bit representations "0100000" (decimal 32) and performs bitwise NOT resulting in binary "1011111". This corresponds to the negative decimal number -33.
x | 0 | 1 | 0 | 0 | 0 | 0 | 0 |
~x | 1 | 0 | 1 | 1 | 1 | 1 | 1 |
How do you transform the binary “1011111” to a decimal number again? By using the following steps:
- Flip each bit back to
0100000. - Get the corresponding decimal number
32. - Increase it by one to
33. - Prefix it with the negative symbol
-33.
To understand this inverse method from a negative binary to an integer, you need to learn some background first. But don’t worry, it’s just a couple of minutes! ?
Representing Negative Integers in Binaries
Python uses so-called complementary binaries to represent negative integers. The first bit of a complementary binary is the sign (0: positive, 1: negative). All remaining bits encode the number. You write a negative number -x as the bit pattern for (x-1) and flip all bits from 1 to 0 and from 0 to 1 (complement).
Here are two simple examples:
- To represent
x = -1using 8 bits you first calculate(1-1) = 0and then flip all bits to calculate"11111111". - To represent
x = -10using 8 bits you first calculate(10-1) = 9which is"00001001"in binary format. Then, you complement all bits to determine the negative (complementary) binary"11110110".
? In fact, Python uses signed integers for its bitwise operators. You may ask: what are signed integers?
- A signed integer, for example using 32 bits, encodes an integer in the range
[-2147483648 to 2147483647]. - An unsigned integer encodes a positive integer in the range
[0 to 4294967295]. The signed integer is represented in twos complement notation.
Python Bitwise NOT Operator Example
Here’s the result of the bitwise NOT operator ~x when applied to a couple of example integer operands x:
| x (int) | x (binary) | ~x (binary) | ~x (int) |
|---|---|---|---|
0 | '00' | '11' | -1 |
1 | '01' | '10' | -2 |
3 | '011' | '100' | -4 |
9 | '01001' | '10110' | -10 |
11 | '01011' | '10100' | -12 |
256 | '0100000000' | '1011111111' | -257 |
You can see those examples in the following Python script:
>>> ~0 -1 >>> ~1 -2 >>> ~3 -4 >>> ~9 -10 >>> ~11 -12 >>> ~256 -257
Let’s use this knowledge in a couple of examples to showcase the working of the bitwise NOT operator on negative integers:
Python Bitwise NOT Examples on Negative Integers
Here’s the result of the bitwise NOT operator ~x when applied to a negative integer operand x:
| x (int) | ~x (int) |
|---|---|
-0 | -1 |
-1 | 0 |
-3 | 2 |
-9 | 8 |
-11 | 10 |
-256 | 255 |
You can see those examples in the following script:
>>> ~-0 -1 >>> ~-1 0 >>> ~-3 2 >>> ~-9 8 >>> ~-11 10 >>> ~-256 255
Python Bitwise NOT Overloading
You can define your own bitwise NOT operator on a custom class by overloading the __invert__ method (dunder method, magic method) with a reference to self as an argument. This allows the expression ~x on your custom objects without raising an error.
Here’s an example:
class Data:
def __init__(self, data):
self.data = data
def __invert__(self):
return Data(~self.data)
x = Data(3)
res = ~x
print(res.data)
# -4
Note: if you forget to overwrite the __invert__ method and still try to use the expression ~x, Python will raise a TypeError: bad operand type for unary ~. You can fix it by defining the dunder method __invert__(self) in your class definition.
class Data:
def __init__(self, data):
self.data = data
x = Data(3)
res = ~x
print(res.data)Output:
Traceback (most recent call last):
File "C:\Users\xcent\Desktop\code.py", line 8, in
res = ~x
TypeError: bad operand type for unary ~: 'Data'To fix this TypeError, simply define the __invert__ method as shown in the previous working example.
Bitwise Operators
Bitwise operators perform operations on the binary (bit) representation of integers. The following table gives a short overview of all existing bitwise operators. Note that we also provide the binary representation 100 for the decimal integer 4, and 101 for the decimal integer 5 as a comment in the right column.
| Operator | Name | Description | Example |
|---|---|---|---|
x = 4, y = 5 | |||
| & | Bitwise AND | Performs logical AND on a bit-by-bit basis | x & y |
| | | Bitwise OR | Performs logical OR operation on a bit-by-bit basis | x | y |
| ~ | Bitwise NOT | Performs logical NOT on a bit-by-bit basis, inverting each bit so that 0 becomes 1 and 1 becomes 0. Same as -x-1. | ~x |
| ^ | Bitwise XOR | Performs logical “exclusive or” operation on a bit-by-bit basis | x ^ y |
| >> | Bitwise right shift | Shifts binary of left operand to the right by the number of positions specified in right operand | x >> 2 |
| << | Bitwise left shift | Shifts binary of left operand to the left by the number of positions specified in right operand | x << 2 |
Here’s a short overview of the Bitwise operators’ magic methods:
| Bitwise Operator | Magic “Dunder” Method |
|---|---|
& | __and__(self, other) |
| | __or__(self, other) |
^ | __xor__(self, other) |
~ | __invert__(self) |
<< | __lshift__(self, other) |
>> | __rshift__(self, other) |
Here’s an example of how to accomplish these bitwise operators on a custom class Data. We marked this respective operator in the code:
class Data:
def __init__(self, data):
self.data = data
def __and__(self, other):
return Data(self.data & other.data)
def __or__(self, other):
return Data(self.data | other.data)
def __xor__(self, other):
return Data(self.data ^ other.data)
def __invert__(self):
return Data(~self.data)
def __lshift__(self, other):
return Data(self.data << other.data)
def __rshift__(self, other):
return Data(self.data >> other.data)
x = 2
y = 3
print('Operands: \n', 'x =', x, '\n', 'y =', y)
print()
print('Bitwise AND: ', x & y)
print('Bitwise OR: ', x | y)
print('Bitwise XOR: ', x ^ y)
print('Bitwise NOT: ', ~x)
print('Bitwise LEFT-SHIFT: ', x << y)
print('Bitwise RIGHT-SHIFT: ', x >> y)
The output is:
Operands: x = 2 y = 3 Bitwise AND: 2 Bitwise OR: 3 Bitwise XOR: 1 Bitwise NOT: -3 Bitwise LEFT-SHIFT: 16 Bitwise RIGHT-SHIFT: 0