6

I'm looking for a Pythonic way to count the number of trailing zeros in the binary representation of a positive integer n (which will indicate the highest power of 2 which divides n without remainder).

A simple solution:

def CountZeros(n):
    c = 0
    while (n % 2) == 0:
        n /= 2
        c += 1
    return c

But in order to do it in a more Pythonic manner, I think that I can make use of:

  • bin(n)[2:], which gives the binary representation of n
  • bin(n)[:1:-1], which gives the reversed binary representation of n

So my question can be reduced to counting the number of trailing zeros in a string.

Is there any single-statement way to do this?

My ultimate goal is a Pythonic way for computing the highest power of 2 which divides n without remainder, so any ways to do this not by counting the trailing zeros in a string are also appreciated.

o11c
  • 15,265
  • 4
  • 50
  • 75
barak manos
  • 29,648
  • 10
  • 62
  • 114
  • Compare the length of the string to the length of the string with those zeroes `strip`ped? Or perhaps there's a mathematical solution. – jonrsharpe Nov 15 '16 at 11:06
  • 3
    The so-called duplicate seems to be about counting decimal zeros (the question isn't explicit but all the answers assumed that decimal zeros were what was meant) while this one is about binary zeros. Voting to reopen. – plugwash Apr 09 '18 at 02:41
  • 1
    In Python 3, for positive `n` you can do `(n ^ (n-1)).bit_length()-1`, although that will evaluate to 0 for `n=0`. If you want `n=0` to give 1, then you can do `((n ^ (n-1))>>1).bit_length()` – PM 2Ring Aug 05 '18 at 12:13
  • 1
    @TigerhawkT3, can you reopen this question so that PM-2Ring's bit-twiddling solution can be posted as an answer? The other question is about decimal zeros, so it is not a duplicate and this answer wouldn't work there. – benrg Jan 17 '19 at 00:13

5 Answers5

15

It's twice as fast to avoid converting to string with bin, instead using a modified bithack, since we already have an efficient log2 implementation.

def ctz(v):
    return (v & -v).bit_length() - 1

The above returns -1 if the input is 0.

Using C makes it twice as fast again:

from gmpy2 import bit_scan1 as ctz

This version returns None if input is zero.


As an example, consider the infinite binary expansion if the input is 20:

v    000...00010100
~v   111...11101011 (not used directly, all bits opposite)
-v   111...11101100 (-v == ~v + 1; this causes all low 1 bits to overflow and carry)
v&-v 000...00000100 (has a single 1 bit, from the carry)

When the are anded, all the leading zero and one bits are opposite, but the last 1 bit and all trailing zeros are the same both ways.

Then .bit_length() tells us the integers is using 3 bits total, so just subtract 1 to only consider the zeros.

o11c
  • 15,265
  • 4
  • 50
  • 75
13

You could use str.rstrip:

def trailing(s):
    return len(s) - len(s.rstrip('0'))
TigerhawkT3
  • 48,464
  • 6
  • 60
  • 97
  • 1
    Thanks! Any Pythonic way to achieve my actual purpose (computing the highest power of `2` which divides `n` without remainder), perhaps by working directly on the integer value rather than its string representation? – barak manos Nov 15 '16 at 15:49
  • @barakmanos - Initialize a variable to 2, start an infinite loop, divide your number by that variable, save that variable if there's no remainder, return the variable if it's larger than your number, multiply the variable by 2. – TigerhawkT3 Nov 15 '16 at 17:33
  • Yeah, well, that's the coding example in my question in case you haven't noticed... Thanks anyway... – barak manos Nov 15 '16 at 19:14
4

This might do.

def trailing_zeros(n):
    s = str(n)
    return len(s)-len(s.rstrip('0'))
FallAndLearn
  • 4,035
  • 1
  • 18
  • 24
1

I'm not sure if this is the fastest solution, but it look like the most logical to me:

def trailing_zeros(n):
    for i in range(20):
        if n % (2<<i) != 0:
            return i

Since you asked for a single-line statement, here's one, but I don't think it's very legible (and its efficiency is worse than the other one):

max(i+1 for i in range(20) if n%(2<<i) == 0)
L3viathan
  • 26,748
  • 2
  • 58
  • 81
  • It does a great job but if the number has a zero in between, it fails by 1. For `1000200` it returns 3. – lycuid Nov 15 '16 at 11:16
  • @ssj.luffy As it should since `100200` is `11110100001100001000` in binary. – L3viathan Nov 15 '16 at 11:17
  • oh, I missed the `binary representation` part of the question. based on the code OP provided, I thought he needed the trailing zeroes for the number itself. great stuff. +1 – lycuid Nov 15 '16 at 11:19
0

Definitely use bit-pushing operations, if you are concerned specifically with the underlying binary representation of the number. Divide and modulo-divide are the most costly operations, and relate to arithmetic not hardware bits. So (untested code)

def fnzb( n):
    " return position of first non-zero bit in n"
    if n==0:
        # edge case, there ARE no nonzero bits
        return None
    for po2 in range(0, 128) # or whatever larger upper limit is desired
        if a & ( 1 << po2) != 0: return po2
    # edge case, n too large
    raise ValueError,  "'impossibly' large integer argument encountered"

If these integers might often be extremely large with very many trailing zeros (for cryptographical values of "large") it might make an important difference to efficiency to initialize test=1 and right-shift it one place every trip around the loop, rather than starting with 1 and shifting it po2 places.

nigel222
  • 7,582
  • 1
  • 14
  • 22