Is & faster than % when checking for odd numbers?

Question:

To check for odd and even integer, is the lowest bit checking more efficient than using the modulo?

>>> def isodd(num):
        return num & 1 and True or False

>>> isodd(10)
False
>>> isodd(9)
True
Asked By: riza

||

Answers:

Yep. The timeit module in the standard library is how you check on those things. E.g:

$ python -m timeit -s 'def isodd(x): x & 1' 'isodd(9)'
1000000 loops, best of 3: 0.446 usec per loop
$ python -m timeit -s 'def isodd(x): x & 1' 'isodd(10)'
1000000 loops, best of 3: 0.443 usec per loop
$ python -m timeit -s 'def isodd(x): x % 2' 'isodd(9)'
1000000 loops, best of 3: 0.461 usec per loop
$ python -m timeit -s 'def isodd(x): x % 2' 'isodd(10)'
1000000 loops, best of 3: 0.453 usec per loop

As you see, on my (first-day==old==slow;-) Macbook Air, the & solution is repeatably between 7 and 18 nanoseconds faster than the % solution.

timeit not only tells you what’s faster, but by how much (just run the tests a few times), which usually shows how supremely UNimportant it is (do you really care about 10 nanoseconds’ difference, when the overhead of calling the function is around 400?!-)…

Convincing programmers that micro-optimizations are essentially irrelevant has proven to be an impossible task — even though it’s been 35 years (over which computers have gotten orders of magnitude faster!) since Knuth wrote

We should forget about small
efficiencies, say about 97% of the
time: premature optimization is the
root of all evil.

which as he explained is a quote from an even older statement from Hoare. I guess everybody’s totally convinced that THEIR case falls in the remaining 3%!

So instead of endlessly repeating "it doesn’t matter", we (Tim Peters in particular deserves the honors there) put in the standard Python library module timeit, that makes it trivially easy to measure such micro-benchmarks and thereby lets at least some programmers convince themselves that, hmmm, this case DOES fall in the 97% group!-)

Answered By: Alex Martelli

To be totally honest, I don’t think it matters.

The first issue is readability. What makes more sense to other developers? I, personally, would expect a modulo when checking the evenness/oddness of a number. I would expect that most other developers would expect the same thing. By introducing a different, and unexpected, method, you might make code reading, and therefore maintenance, more difficult.

The second is just a fact that you probably won’t ever have a bottleneck when doing either operation. I’m for optimization, but early optimization is the worst thing you can do in any language or environment. If, for some reason, determining if a number is even or odd is a bottleneck, then find the fastest way of solving the problem. However, this brings me back to my first point – the first time you write a routine, it should be written in the most readable way possible.

Answered By: Thomas Owens

“return num & 1 and True or False” ? Wah! If you’re speed-crazy (1) “return num & 1” (2) inline it: if somenumber % 2 == 1 is legible AND beats isodd(somenumber) because it avoids the Python function call.

Answered By: John Machin

John brings up a good point. The real overhead is in the function call:

me@localhost ~> python -mtimeit -s'9 % 2'
10000000 loops, best of 3: 0.0271 usec per loop
me@localhost ~> python -mtimeit -s'10 % 2'
10000000 loops, best of 3: 0.0271 usec per loop

me@localhost ~> python -mtimeit -s'9 & 1'
10000000 loops, best of 3: 0.0271 usec per loop
me@localhost ~> python -mtimeit -s'9 & 1'
10000000 loops, best of 3: 0.0271 usec per loop

me@localhost ~> python -mtimeit -s'def isodd(x): x % 2' 'isodd(10)'
1000000 loops, best of 3: 0.334 usec per loop
me@localhost ~> python -mtimeit -s'def isodd(x): x % 2' 'isodd(9)'
1000000 loops, best of 3: 0.358 usec per loop

me@localhost ~> python -mtimeit -s'def isodd(x): x & 1' 'isodd(10)'
1000000 loops, best of 3: 0.317 usec per loop
me@localhost ~> python -mtimeit -s'def isodd(x): x & 1' 'isodd(9)'
1000000 loops, best of 3: 0.319 usec per loop

Interestingly both methods remore the same time without the function call.

Answered By: Jeffrey Hulten

The best optimization you can get is to not put the test into a function. ‘number % 2‘ and ‘number & 1’ are very common ways of checking odd/evenness, experienced programmers will recognize the pattern instantly, and you can always throw in a comment such as ‘# if number is odd, then blah blah blah’ if you really need it to be obvious.

# state whether number is odd or even
if number & 1:
    print "Your number is odd"
else:
    print "Your number is even"
Answered By: too much php

Apart from the evil optimization, it takes away the very idiomatic “var % 2 == 0” that every coder understands without looking twice. So this is violates pythons zen as well for very little gain.

Furthermore a = b and True or False has been superseded for better readability by

return True if num & 1 else False

Answered By: Tom

Was really surprised none of the above answers did both variable setup (timing literal is different story) and no function invocation (which obviously hides “lower terms”). Stuck on that timing from ipython’s timeit, where I got clear winner x&1 – better for ~18% using python2.6 (~12% using python3.1).

On my very old machine:

$ python -mtimeit -s 'x = 777' 'x&1'
10000000 loops, best of 3: 0.18 usec per loop
$ python -mtimeit -s 'x = 777' 'x%2'
1000000 loops, best of 3: 0.219 usec per loop

$ python3 -mtimeit -s 'x = 777' 'x&1'
1000000 loops, best of 3: 0.282 usec per loop
$ python3 -mtimeit -s 'x = 777' 'x%2'
1000000 loops, best of 3: 0.323 usec per loop
Answered By: gorlum0

Using Python 3.6 the answer is no. Using the code bellow on a 2017 MBP shows that using modulo is faster.

# odd.py
from datetime import datetime

iterations = 100_000_000


def is_even_modulo(n):
    return not n % 2


def is_even_and(n):
    return not n & 1


def time(fn):
    start = datetime.now()
    for i in range(iterations, iterations * 2):
        fn(i)
    print(f'{fn.__name__}:', datetime.now() - start)


time(is_even_modulo)
time(is_even_and)

Gives this result:

$ python3 -m odd
is_even_modulo: 0:00:14.347631
is_even_and: 0:00:17.476522
$ python3 --version
Python 3.6.1

As suggested in other answers, the function calls is a large overhead, however, removing it shows that modulo is still faster than bitwise and in Python 3.6.1:

# odd.py
from datetime import datetime

iterations = 100_000_000


def time_and():
    start = datetime.now()
    for i in range(iterations):
        i & 1 
    print('&:', datetime.now() - start)


def time_modulo():
    start = datetime.now()
    for i in range(iterations):
        i % 2
    print('%:', datetime.now() - start)


time_modulo()
time_and()

Results:

$ python3 -m odd
%: 0:00:05.134051
&: 0:00:07.250571

Bonus: it turns out this takes about double the time to run in Python 2.7.

$ time python2 -m odd
('&:', '0:00:20.169402')
('%:', '0:00:19.837755')

real    0m41.198s
user    0m39.091s
sys 0m1.899s
$ time python3 -m odd
&: 0:00:11.375059
%: 0:00:08.010738

real    0m19.452s
user    0m19.354s
sys 0m0.042s
Answered By: antonagestam

Updating the results for latest python version, % is faster than &

➜ ✗ python -m timeit -s 'def isodd(x): x & 1' 'isodd(9)'
5000000 loops, best of 5: 78.4 nsec per loop
➜ ✗ python -m timeit -s 'def isodd(x): x & 1' 'isodd(10)'
5000000 loops, best of 5: 79.6 nsec per loop
➜ ✗ python -m timeit -s 'def isodd(x): x % 2' 'isodd(9)'
5000000 loops, best of 5: 65.8 nsec per loop
➜ ✗ python -m timeit -s 'def isodd(x): x % 2' 'isodd(10)'
5000000 loops, best of 5: 68.5 nsec per loop
Answered By: eroot163pi