23

I found this strange if-statement in somebody else’s code:

if variable & 1 == 0:

I don't understand it. It should have two ==, right?

Can somebody explain this?

Bex
  • 2,905
  • 2
  • 33
  • 36
n0tis
  • 750
  • 1
  • 6
  • 27
  • 4
    What is probably confusing is the &. This is one of Python's bitwise operators. A good post examining that can be found [here](http://stackoverflow.com/questions/1746613/bitwise-operation-and-usage). – Anthony Dito Apr 16 '15 at 00:48
  • May I ask where you found that? I don't often see bitwise operators in Python. – TigerhawkT3 Apr 16 '15 at 00:54
  • 3
    It's the same as `variable % 2 == 0` for `int`egers. – Navith Apr 16 '15 at 01:00
  • 4
    The use of a bitwise operator rather than the much more readable `variable % 2 == 0` (or just `not variable % 2`) is exactly why I'm curious about the story behind this. – TigerhawkT3 Apr 16 '15 at 01:05
  • 2
    Looks like dark magic. You have evil compiler. – Bohdan Apr 16 '15 at 01:24
  • @Navith I'm trying to debug this: https://github.com/chpatrick/hubsan you can find this statement in the hubsan.py line 79 – n0tis Apr 16 '15 at 12:04
  • @TigerhawkT3: that depends on your background. Personally, I find the bitwise version more readable. I wouldn't have to think too hard to convert "is even" to "bit 0 is clear", but I would have to think. :-) – Harry Johnston Apr 16 '15 at 12:50
  • 1
    Is there a typo in your question? You ask if the statement should have two equals (`==`), but if do see those between the `1` and the `0`. – Brian J Apr 16 '15 at 13:02
  • you should probably parenthesize `variable & 1` since the bitwise and has a lower precedence than equal to in many languages http://www.lysator.liu.se/c/dmr-on-or.html – Steve Cox Apr 16 '15 at 15:13
  • @TigerhawkT3 depends on what is meant. For me `variable % 2 == 0` is "variable is not divisible by 2" (variable is a number) and `variable & 1 == 0` is "bit 0 is unset" (variable is a bit vector). In line 79 the code reads hardware register so thinking about it as number is not useful (operations like `+` or `*` don't make sense) - and so is checking if number is odd or even. OTOH checking if bit 0 or 1 are set do make sense. – Maciej Piechotka Apr 16 '15 at 17:10

4 Answers4

33

The conditional is a bitwise operator comparison:

>>> 1 & 1
1
>>> 0 & 1
0
>>> a = 1
>>> a & 1 == 0
False
>>> b = 0
>>> b & 1 == 0
True

As many of the comments say, for integers this conditional is True for evens and False for odds. The prevalent way to write this is if variable % 2 == 0: or if not variable % 2:

Using timeit we can see that there isn't much difference in performance.

n & 1("== 0" and "not")

>>> timeit.Timer("bitwiseIsEven(1)", "def bitwiseIsEven(n): return n & 1 == 0").repeat(4, 10**6)
[0.2037370204925537, 0.20333600044250488, 0.2028651237487793, 0.20192503929138184]

>>> timeit.Timer("bitwiseIsEven(1)", "def bitwiseIsEven(n): return not n & 1").repeat(4, 10**6)
[0.18392395973205566, 0.18273091316223145, 0.1830739974975586, 0.18445897102355957]

n % 2("== 0" and "not")

>>> timeit.Timer("modIsEven(1)", "def modIsEven(n): return n % 2 == 0").repeat(4, 10**6)
[0.22193098068237305, 0.22170782089233398, 0.21924591064453125, 0.21947598457336426]

>>> timeit.Timer("modIsEven(1)", "def modIsEven(n): return not n % 2").repeat(4, 10**6)
[0.20426011085510254, 0.2046220302581787, 0.2040550708770752, 0.2044820785522461]

Overloaded Operators:

Both the % and & operators are overloaded.

The bitwise and operator is overloaded for set. s.intersection(t) is equivalent to s & t and returns a "new set with elements common to s and t".

>>> {1} & {1}
set([1])

This doesn't effect our conditional:

>>> def bitwiseIsEven(n):
...   return n & 1 == 0

>>> bitwiseIsEven('1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in bitwiseIsEven
TypeError: unsupported operand type(s) for &: 'str' and 'int'
>>> bitwiseIsEven({1})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in bitwiseIsEven
TypeError: unsupported operand type(s) for &: 'set' and 'int'

The modulo operator will also throw TypeError: unsupported operand type(s) for most non-ints.

>>> def modIsEven(n):
...   return n % 2 == 0

>>> modIsEven({1})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in modIsEven
TypeError: unsupported operand type(s) for %: 'set' and 'int'

It is overloaded as a string interpolation operator for the old %-formatting. It throws TypeError: not all arguments converted during string formatting if a string is used for the comparison.

>>> modIsEven('1')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in modIsEven
TypeError: not all arguments converted during string formatting

This won't throw if the string includes a valid conversion specifier.

>>> modIsEven('%d')
False 
dting
  • 38,604
  • 10
  • 95
  • 114
  • 15
    It is also useful to note that (for integers), this trick will tell you if the number is even (`True`) or odd (`False`). – baum Apr 16 '15 at 03:06
  • 1
    @baum you should make that an answer - looks like you got it (: btw: afaik & is only defined for integers (and ducks that look like them ;) – drevicko Apr 16 '15 at 04:49
29

This code just checks if the lowest bit of variable is a 0. Based on operator precedence this is:

if (variable & 1) == 0:

First AND the lowest bit with one (extract just the lowest bit), then check if it is 0.

Brad Budlong
  • 1,775
  • 11
  • 10
14

The & is a bitwise operator. It returns an integer with 1 bit for every bit of its two operands that are both 1, and 0 in all other places. For example:

a = 10 # 0b1010
b = 6  # 0b0110
a & b  # 0b0010

Now, if you have variable & 1, you're comparing variable against 0b1 which will only return 1 if that last digit in the binary representation is a 1, otherwise a 0.

Drakes
  • 23,254
  • 3
  • 51
  • 94
MasterOdin
  • 7,117
  • 1
  • 20
  • 35
6

Your only concern is probably the operator &. It is a bitwise and which takes the binary format of the two operands and perform "logic and" on each pair of bits.

For your example, consider the following:

variable = 2  #0b0010
if variable & 1 == 0:
    print "condition satisfied" # satisfied, 0b0010 & 0b0001 = 0

variable = 5  #0b0101
if variable & 1 == 0:
    print "condition satisfied" # not satisfied, 0b0101 & 0b0001 = 1

Note:

variable = 6  #0b0110
if variable & 2 == 0:
    print "condition satisfied" # not satisfied, 0b0110 & 0b0010 = 2 (0b0010)
Thomas Hsieh
  • 731
  • 1
  • 6
  • 26