49

In order to test building an Xor operation with more basic building blocks (using Nand, Or, and And in my case) I need to be able to do a Not operation. The built-in not only seems to do this with single bits. If I do:

x = 0b1100
x = not x

I should get 0b0011 but instead I just get 0b0. What am I doing wrong? Or is Python just missing this basic functionality?

I know that Python has a built-in Xor function but I've been using Python to test things for an HDL project/course where I need to build an Xor gate. I wanted to test this in Python but I can't without an equivalent to a Not gate.

Yu Hao
  • 119,891
  • 44
  • 235
  • 294
Lauren
  • 883
  • 2
  • 9
  • 7
  • 1
    You need to watch out. not is not a bitwise operator. These are bit wise operators (they actually map to gates): https://wiki.python.org/moin/BitwiseOperators – rady Jul 01 '15 at 01:29
  • In fact you are not getting `0b0` but the `False` singleton, since `not` always returns the opposite of the truthiness of its operand, no matter what that is. – Bachsau Jun 22 '19 at 22:04

7 Answers7

57

The problem with using ~ in Python, is that it works with signed integers. This is also the only way that really makes sense unless you limit yourself to a particular number of bits. It will work ok with bitwise math, but it can make it hard to interpret the intermediate results.

For 4 bit logic, you should just subtract from 0b1111

0b1111 - 0b1100  # == 0b0011

For 8 bit logic, subtract from 0b11111111 etc.

The general form is

def bit_not(n, numbits=8):
    return (1 << numbits) - 1 - n
John La Rooy
  • 295,403
  • 53
  • 369
  • 502
  • Thanks! It would be nice if Python allowed unsigned numbers, but that would be a huge change, so this works. :) – Lauren Jul 04 '15 at 01:18
  • 1
    Note that you can also phrase this as constructing a mask and ANDing with it (Python's pseudo-two's complement system handles that properly by reducing to a positive number constrained by the mask), e.g. `return ~n & ((1 << numbits) - 1)`. You solution works fine, but feels sort of backwards to me, simply because I prefer to think in terms of actually flipping the bits, then masking to the proper width. – ShadowRanger May 23 '19 at 14:38
  • John's method's faster though – Nicolas David Mar 06 '21 at 15:44
  • I don't see why the python Mighty Ones don't add 'xor' to the 'operator' module. We have bitwise 'and' and 'or, so why not 'xor' and 'not'? – John White May 21 '22 at 09:34
9

Another way to achieve this, is to assign a mask like this (should be all 1's):

mask = 0b1111

Then xor it with your number like this:

number = 0b1100
mask = 0b1111
print(bin(number ^ mask))

You can refer the xor truth table to know why it works.

ShadowRanger
  • 143,180
  • 12
  • 188
  • 271
PKBEST
  • 321
  • 2
  • 13
  • 1
    For the record, this relies on the input already falling in that range, or the output will remain outside the range; you can't use it for cutdown. If you want it to work for numbers that might exceed the desired range, `~number & mask` works with only slightly more work, and looks closer to the logic of what you intend. e.g. `0x12345 ^ 0xffff` gets `0x1dcba`, while `~0x12345 & 0xffff` gets `0xdcba`. – ShadowRanger May 23 '19 at 14:25
9

Python bitwise ~ operator invert all bits of integer but we can't see native result because all integers in Python has signed representation.

Indirectly we can examine that:

>>> a = 65
>>> a ^ ~a
-1

Or the same:

>>> a + ~a
-1

Ther result -1 means all bits are set. But the minus sign ahead don't allow us to directly examine this fact:

>>> bin(-1)
'-0b1'

The solution is simple: we must use unsigned integers. First way is to import numpy or ctypes modules wich both support unsigned integers. But numpy more simplest using than ctypes (at least for me):

import numpy as np
a = np.uint8(0b1100)
y = ~x

Check result:

>>> bin(x)
'0b1100'
>>> bin(y)
'0b11110011'

And finally check:

>>> x + y
255

Unsigned integer '255' for 8-bits integers (bytes) mean the same as '-1' becouse has all bits set to 1. Make sure:

>>> np.uint8(-1)
255

And another simplest solution, not quite right, but if you want to include additional modules, you can invert all bits with XOR operation, where second argument has all bits are set to 1:

a = 0b1100
b = a ^ 0xFF

This operation will also drop most significant bit of signed integer and we can see result like this:

>>> print('{:>08b}'.format(a))
00001100
>>> print('{:>08b}'.format(b))
11110011

Finally solution contains one more operation and therefore is not optimal:

>>> b = ~a & 0xFF
>>> print('{:>08b}'.format(b))
11110011
FLAK-ZOSO
  • 3,873
  • 4
  • 8
  • 28
An0ther0ne
  • 324
  • 3
  • 8
1

Try this, it's called the bitwise complement operator:

~0b1100
Óscar López
  • 232,561
  • 37
  • 312
  • 386
1

The answers here collectively have great nuggets in each one, but all do not scale well with depending on edge cases.

Rather than fix upon an 8-bit mask or requiring the programmer to change how many bits are in the mask, simply create a mask based on input via bit_length():

def bit_not(num):
    return num ^ ((1 << num.bit_length()) - 1)
Flair
  • 2,609
  • 1
  • 29
  • 41
0

string of binary can be used to preserve the left 0s, since we know that:

bin(0b000101) # '0b101'
bin(0b101)    # '0b101'

This function will return string format of the NOT of input number

def not_bitwise(n):
     ''' 
     n: input string of binary number (positive or negative)
     return: binary number (string format)
     '''
     head, tail = n.split('b')
     not_bin = head+'b'+tail.replace('0','a').replace('1','0').replace('a','1')
     return not_bin

Example:

In[266]: not_bitwise('0b0001101')
Out[266]: '0b1110010'

In[267]: int(not_bitwise('0b0001101'), 2)

Out[267]: 114

In[268]: not_bitwise('-0b1010101')

Out[268]: '-0b0101010'

In[269]: int(not_bitwise('-0b1010101'), 2)
Out[269]: -42
imanzabet
  • 2,752
  • 2
  • 26
  • 19
-1

The general form given by John La Rooy, can be simplified in this way (python == 2.7 and >=3.1):

def bit_not(n):
    return (1 << n.bit_length()) - 1 - n
consultit
  • 57
  • 2
  • 3
  • This only works if `n`'s logical high bit is set to `1`. If you're logically working with eight bit numbers, then this will work for `n in range(128, 256)`, but for `n in range(128)`, it will fail to set the high bits. This also makes it non-reversible (because the number will continually shrink on each reversal as the high bits that should have been preserved are lost); even when the high bit is set, the result won't have it set, so the next reversal becomes wrong. – ShadowRanger May 23 '19 at 14:34