Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"Bitwise Not" in Python disconsidering 2's complement

I need to do a "~" operation in Python but not taking account of 2's complement. I managed to do that by using XOR, do you know another way to do this? (more efficient)

a = 0b101
b = 0b10101

print bin(a ^ (2 ** a.bit_length() - 1)) #0b10
print bin(b ^ (2 ** b.bit_length() - 1)) #0b1010
like image 218
Leandro Moreira Avatar asked Sep 10 '14 02:09

Leandro Moreira


People also ask

How do you convert to 2's complement in Python?

The way to compute the Two's complement is by taking the One's complement and adding 1 . 1111 1100 would then be -12 in Two's complement. The advantage over One's complement is that there isn't a duplicate representation of 0 with 0000 0000 and 1111 1111 , hence allowing one more value to be represented -128 .

How do you calculate Bitwise not?

Bitwise NOT (~) That is, the presence of the most significant bit is used to express negative integers. Bitwise NOTing any number x yields -(x + 1) . For example, ~-5 yields 4 . Note that due to using 32-bit representation for numbers both ~-1 and ~4294967295 (232 - 1) results in 0 .

Does Python use 2s complement?

The first bitwise-and operation is used to sign-extend negative numbers (most significant bit is set), while the second is used to grab the remaining 11 bits. This works since integers in Python are treated as arbitrary precision two's complement values.


2 Answers

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.)

like image 64
John Kugelman Avatar answered Sep 20 '22 23:09

John Kugelman


Another way, though some (including myself) may dispute it's any better, is:

tbl = str.maketrans("01","10")

int(bin(42)[2:].translate(tbl),2)

The first bit just sets up a translation table to invert 1 and 0 bits in a string.

The second bit gets the binary representation (42 -> 0b101010), strips off the 0b at the front, and inverts the bits through translation. Then you just use int(,2) to turn that binary string back into a number.


If you can limit it to a specific width rather than using the width of the number itself, then it's a simple matter of (using the example of 32 bits):

val = val ^ 0xffff
like image 43
paxdiablo Avatar answered Sep 19 '22 23:09

paxdiablo