That's what ~ does already. The tricky part is that Python has unlimited length integers, so when you invert a number it is sign extended with--at least conceptually speaking--an infinite number of 1's. Meaning you get negative numbers.
>>> bin(~0b101)
'-0b110'
>>> bin(~0b10101)
'-0b10110'
To convert these to unsigned numbers, you need to decide how many bits you care about. Maybe you are working with 8-bit bytes. Then you could AND them with a byte's worth of 1 bits:
>>> bin(~0b101 & 0xFF)
'0b11111010'
>>> bin(~0b10101 & 0xFF)
'0b11101010'
Or if you want to match the exact bit length of the input numbers, your solution is reasonable. For efficiency you could switch the exponent for a left shift. And it might be clearer to use ~ and & instead of ^.
>>> bin(~a & ((1 << a.bit_length()) - 1))
'0b10'
>>> bin(~b & ((1 << b.bit_length()) - 1))
'0b1010'
(I suspect a hardcoded mask like & 0xFFFF will be the right solution in practice. I can't think of a good real world use case for the bit_length()-based answer.)
binrepresentation is confusing you.~does invert the bits; it just inverts an infinite number of bits, making it impossible to represent textually.binthus does some messing around with-to make it work, but it ends up hiding the fact all the bits are inversed.a ^ 0xFFFF.(1 << b.bit_length() - 1)is going to be faster than(2 ** b.bit_length() - 1)- and yes, hardcode a mask if you can instead of callingbit_length.