6

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
Leandro Moreira
  • 377
  • 3
  • 16
  • May I ask why? It's possible the `bin` representation is confusing you. `~` does invert the bits; it just inverts an *infinite number* of bits, making it impossible to represent textually. `bin` thus does some messing around with `-` to make it work, but it ends up hiding the fact all the bits are inversed. – Veedrac Sep 10 '14 at 02:59
  • @Veedrac I'm implementing a 16bit vm. – Leandro Moreira Sep 10 '14 at 03:02
  • I think your ways good enough, don't think python has a native way of doing this. A minor change can be using bitshift instead of powers. – simonzack Sep 10 '14 at 03:02
  • 1
    If It's a virtual machine, you probably know the register width, so you can clean it up a touch by hardcoding the expression as `a ^ 0xFFFF`. – Bill Lynch Sep 10 '14 at 03:03
  • 1
    `(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 calling `bit_length`. – roippi Sep 10 '14 at 03:07
  • @roippi I tried to do `print bin(1 << b.bit_length() - 1)` but it ouputs `0b10000` – Leandro Moreira Sep 10 '14 at 03:13
  • I missed some parentheses, should be `((1 << b.bit_length()) - 1)` – roippi Sep 10 '14 at 03:14

2 Answers2

5

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

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
1

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
Yury Kirienko
  • 1,810
  • 1
  • 22
  • 32
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953