What does ~n return for a Python int, and how do you get fixed-width bitwise results?
answer
- Bits, but with no width anywhere
- Think of the sign bit repeating forever
- Complement flips the sign too
- It is minus n minus one
- Mask with & ((1 << width) - 1) for fixed width
basics
~20 sIn Python, ~n is -n - 1, so ~5 is -6 and never 250. Ints behave as two's complement with an infinite run of sign bits, so mask with & ((1 << width) - 1) whenever you need a fixed-width answer.
solid answer
~40 sPython's `&`, `|`, `^` and `~` are defined on a model where every `int` is two's complement extended infinitely to the left with copies of its sign bit. Under that model `~n` is exactly `-n - 1`, so `~5` is `-6`; there is no width in which the bits could become 250. `>>` is an arithmetic shift that floors, so `-8 >> 1` is `-4` and `-1 >> 100` stays `-1`, and there is no unsigned-shift operator because there is no width to fill with zeros. `<<` never overflows - it just allocates a bigger number. When you need C-like fixed-width behaviour, mask explicitly: `~n & 0xFFFFFFFF` gives the 32-bit complement, and you convert back to signed by subtracting `1 << 32` when the result has the high bit set.
code
pycon · 12 lines>>> ~5
-6
>>> ~5 & 0xFF
250
>>> -8 >> 1
-4
>>> -1 >> 100
-1
>>> 1 << 100
1267650600228229401496703205376
>>> (5).bit_count()
2go deeper
You are unlikely to be asked this, but recognising that ~5 is -6 and that Python bit operations never wrap will keep you from writing a mask that only works for small positive values.
Explain the infinite-sign-extension model and derive the results from it: ~n is -n - 1, >> floors, << never overflows, and there is no unsigned shift because there is no width.
Demonstrate the discipline of porting width-dependent code: mask after every step, convert back to signed deliberately, and treat a shift count from input as a memory-allocation concern rather than an arithmetic one.
Decide when a bit-level representation is worth it at all versus a structured one, and if it is kept, insist the width be a named constant with round-trip tests rather than a mask copied through the codebase.
## The mental model: infinite sign extension Python's bitwise operators are total functions on unbounded integers, and the way to keep them straight is a single rule: imagine every `int` written in two's complement and extended infinitely to the left with copies of its sign bit. A non-negative number has infinitely many leading zeros; a negative number has infinitely many leading ones. `&`, `|`, `^` and `~` then operate bit by bit on those infinite strings, and the result always ends up being a finite integer again because the leading bits are all identical. That single rule produces everything else: - `~n` flips every bit, including the infinite sign run, which turns the sign around. Arithmetically it is exactly `-n - 1`, so `~5` is `-6` and `~-1` is `0`. - `-1` is the all-ones value, so `n ^ -1` is `~n` and `n & -1` is `n`. - `&` between two negatives stays negative, because both sign runs are ones. `5 & -1` is `5`, `-2 & -3` is `-4`. ```pycon >>> ~5 -6 >>> 5 ^ -1 -6 >>> ~0 -1 >>> -2 & -3 -4 ``` The most common wrong answer in an interview is `~5 == 250`. That is the 8-bit answer, and it is only correct in a language where the value has a declared width. Python has no width to complement inside. ## Shifts `<<` is an ordinary multiplication by a power of two and never overflows or wraps; `1 << 200` simply allocates a larger integer. The practical limit is memory, and shifting by an enormous count allocates memory proportional to the result, so a shift count read from untrusted input is a resource concern rather than an arithmetic one. `>>` is an arithmetic shift that floors toward negative infinity, consistent with the infinite-sign-extension model: `-8 >> 1` is `-4`, `-7 >> 1` is `-4` (not `-3`), and `-1 >> 100` is still `-1`, because shifting an infinite run of ones rightwards leaves an infinite run of ones. There is no `>>>` operator in Python. It would be meaningless: an unsigned right shift is defined by the width it shifts zeros into, and there is no width. A negative shift count raises `ValueError`, and shifting or masking a `float` raises `TypeError` - bitwise operators accept only integers, which includes booleans since `bool` is an `int` subclass. ## Emulating fixed width When you are reimplementing an algorithm that was specified for 32-bit or 64-bit registers - a checksum, a hash function, a protocol field - you must add the width back yourself. Mask after every operation that can grow or go negative, and convert back to a signed interpretation only when you need one: ```python MASK32 = (1 << 32) - 1 def not32(n: int) -> int: return ~n & MASK32 def as_signed32(n: int) -> int: n &= MASK32 return n - (1 << 32) if n >= 1 << 31 else n print(not32(5)) # 4294967290 print(as_signed32(not32(5))) # -6 ``` The masking discipline is the whole trick: `& MASK32` after each step keeps values in range, and it must be applied after shifts and additions too, because both can push a value past 32 bits. Forgetting one mask usually produces a result that is correct for small inputs and wrong for large ones, which is the worst kind of bug to find later. ## Measuring bits `int.bit_length()` returns the bit count of the absolute value with no leading zeros, so `(255).bit_length()` is 8, `(-1).bit_length()` is 1 and `(0).bit_length()` is 0. It never counts a sign bit, because there is no fixed field for one. `int.bit_count()`, added in Python 3.10, counts the one bits of the absolute value, so `(5).bit_count()` and `(-5).bit_count()` are both 2. If you need the popcount of a negative value under a specific width, mask it first. ## Why the design is the way it is Any other definition would need a width, and the language deliberately does not have one. Defining bitwise operators on the infinitely sign-extended representation keeps them consistent with arithmetic - `x >> 1` equals `x // 2` for every integer, positive or negative - and keeps every operator total. The cost is that programmers arriving from a fixed-width language must supply the width themselves, explicitly, with a mask.
- Why does -1 >> 100 stay -1 in Python instead of eventually reaching zero?`>>` is an arithmetic shift that floors toward negative infinity, and `-1` is the value whose two's complement representation is an infinite run of one bits. Shifting that run rightwards discards ones and shifts ones in, so the value is unchanged no matter how far you shift. Equivalently, `x >> n` equals `x // (2 ** n)`, and `-1 // 2 ** 100` floors to `-1`.
- How would you compute the number of one bits of a negative integer under a 16-bit interpretation?Mask first, then count: `(n & 0xFFFF).bit_count()`. `int.bit_count()` on its own reports the ones of the absolute value, so `(-5).bit_count()` is 2, which is not the 16-bit answer. Masking materialises the width you actually mean, and the same pattern - mask, then measure - applies to bit_length as well.
- What happens if you apply & to a float, or shift by a negative amount?Both raise. Bitwise operators are defined only for integers, so `1.0 & 1` raises `TypeError` even though the float is integral - convert explicitly first. A negative shift count raises `ValueError`. Booleans work fine, since `bool` is an `int` subclass, which is why `True | 2` returns the integer 3.
Picture the number written on a ribbon that runs off to the left forever, printed with its sign bit over and over; complementing flips the ribbon too, which is why the sign changes.
saying these in an interview costs you the question
- Says ~5 is 250 or 0b11111010
- Expects a >>> unsigned-shift operator in Python
- Thinks << eventually overflows or wraps
- Believes bit_length counts a sign bit
- Assumes & on negative ints is undefined
- Reimplements a 32-bit hash without masking each step