When you have a fixed number of bits, inverting them is easy — just invert them. So, for example, it we are working with 4-bit integers, the inverse of 5 (0b0101) is 0b1010
However, Python has arbitrary-sized integers, which makes things a bit tricker. If we invert 5 (0b101), is the result 1010 or 11111010 or 1111111111111010 or what? So Python defines inverting in terms of integers (in the mathematical sense, as opposed to strings of bits); ~x ≝ -(x+1)
. Why this definition? When working with two’s-complement representation of negatives, neg(x) = inv(x)+1
so it makes sense that inv(x) = neg(x)-1 = -(x+1)
. This is why ~5
comes out to -6
.
So that is why you are getting negative numbers, but how do you get the positive value that you expect? The language spec does not say it explicitly, but the bitwise &
and |
operators seem to treat negatives as having “enough” ones on the left. This means that if you mask out only as many bits to the right as you want, you get a positive number. For example, if we want to invert a 4-bit number 5, we can do bin(~0b101 & 0b1111
and get the expected 0b1010
.
You seem to be expecting 8-bit numbers, so for example bin(~0b00100000 & 0xff)
gives you 0b11011111
.