Back to Blog
Python

Python Bitwise NOT: How ~x Works and Why It Returns Negative Numbers

Understand Python's bitwise NOT operator (~), why ~x returns -x - 1, and how to use it to clear and toggle bits without surprises.

bitwise operatorsPython integerstwo's complementbit manipulationnegative numbersPython syntax
Diagram showing the bitwise NOT operation on a binary number with negation result

Python's bitwise NOT operator, written as ~, is a common source of confusion because it does not simply invert each bit in the way a beginner might expect. For an integer x, ~x evaluates to -x - 1. So ~5 returns -6, not 2 or 10. This behavior follows from Python's use of two's-complement semantics with an infinite number of leading sign bits.

What Python's Bitwise NOT Actually Does

The ~ operator is a unary operator that flips every bit of its operand. Because Python integers have arbitrary precision, the result is not an unsigned, fixed-width bitwise complement. Instead, the operation is defined mathematically as ~x = -x - 1. The following examples show the result:

print(~5) # -6 print(~-5) # 4 print(~0) # -1

For 5, the effective binary representation is ...000101 (with infinitely many leading zeros). Flipping all bits yields ...111010, which is the two's-complement representation of -6. The formula -x - 1 is the simplest way to reason about the result without writing out infinite bit strings.

Why Negative Numbers Appear: Two's Complement in Python

Python integers have arbitrary precision, and Python's bitwise operations treat negative integers as if they use two's complement with an infinite sequence of leading ones. Conceptually, a non-negative integer has an infinite sequence of leading zeros. When you apply ~, every bit is flipped, so the leading zeros of a positive number become leading ones, turning the value negative. Conversely, flipping a negative number's leading ones to zeros yields a non-negative result.

This is why ~5 becomes -6 rather than 2 or 10. The operation is not a simple bitwise NOT on a fixed-width binary number; it operates on the mathematical value of the integer. Understanding this representation is key to predicting the output of ~ on any integer.

Using Bitwise NOT to Clear Bits

A common practical use of ~ is to clear specific bits in an integer by combining it with the AND operator (&). For example, to clear bit 2 (value 4) from a number, you can use x & ~4:

def clear_bit(x, bit_position): mask = 1 << bit_position return x & ~mask # Clear bit 2 (value 4) from 13 (binary 1101) result = clear_bit(13, 2) # 9 (binary 1001) print(result)

~mask creates a value with all bits set except the one you want to clear. When ANDed with x, it forces that bit to zero while preserving all other bits. This pattern is idiomatic in low-level programming and is often used for flag manipulation or hardware register access.

Toggling Bits and Building Bit Masks

XOR (^) is the usual operator for toggling a bit: x ^ (1 << n) flips bit n. ~ is useful when you need a mask with all bits set except a few. For example, use ~0xF to build the complement of a lower-four-bit mask:

lower_four_mask = 0xF inverted_mask = ~lower_four_mask # all bits set except the lower 4 # Keep only the lower 4 bits of x result_low = x & lower_four_mask # Keep everything except the lower 4 bits result_high = x & inverted_mask

Note that inverted_mask is a negative number. The AND operation still works correctly, because Python's bitwise operations operate on the two's-complement representation. When combined with a positive x, the leading ones in the negative mask have no effect in the result because x has leading zeros.

Common Mistakes and Unexpected Results

A frequent mistake is expecting ~5 to produce 2 by flipping only the bits of 0b101, or 10 by flipping a fixed-width nibble 0b0101. Python integers are not fixed-width unsigned values, so neither of those results is returned; ~5 is -6.

Applying ~ to a bool also works because bool is a subclass of int: ~True returns -2. That is rarely the intended behavior and can lead to subtle bugs.

In addition, built-in support for ~ applies to integers, not floats. Attempting ~3.14 raises a TypeError. If you need to apply bitwise operations to floating-point data, first convert to an integer type, keeping in mind that precision may be lost.

Performance and Readability Considerations

From a performance standpoint, ~ is a built-in operator and is not a meaningful bottleneck in typical Python code. In tight low-level code, x & ~mask is a concise way to clear bits. The larger risk is readability: a reader who does not immediately recall two's-complement behavior may find ~mask confusing. If clarity is more important than a micro-optimization, prefer a helper function or add a comment explaining what ~mask accomplishes. For example, clear_bit(x, 2) is usually easier to understand at a call site than an inline x & ~4.

Handling Non-Integer Types and Edge Cases

Built-in support for ~ is defined for integers. Floats and strings raise a TypeError. Custom classes can opt in by implementing the __invert__ method, though that is uncommon and should be done carefully.

In the standard library, ~ is also defined for enum.IntFlag members. If a flag class has a member named READ, then ~READ represents the complement of READ within that flag set. This is a natural extension of bitwise NOT semantics.

For any integer, ~x is negative when x is non-negative, and vice versa. For example, ~x >= 0 is equivalent to x < 0, though the comparison is more readable. Understanding these edge cases helps you avoid surprising behavior when applying ~ in complex expressions.

Python Bitwise NOT Operator: How It Works and Practical Examples | RYUSLOG DEV