18

C++ has a set of functions, ffs(), ffsl(), and ffsll(), that return the least significant bit that is set in a given binary integer.

I'm wondering if there is an equivalent function already available in Python. I don't see one described for bitarray, but perhaps there's another. I am hoping to avoid calculating the answer by looping through all possible bit masks, though of course that's an option of last resort; ffs() simply returns a single integer and I'd like to know of something comparable in Python.

brannerchinese
  • 1,909
  • 5
  • 24
  • 40
  • 1
    All possible bitmasks? 32 of them? Why not use `>>` right shift until the value is odd? – S.Lott Apr 02 '11 at 02:13
  • I used repeated right shift in C++ until I found ffs(). I'd rather not write loops at all; I was hoping there was a fast native method. But it doesn't sound as though there is. – brannerchinese Apr 02 '11 at 03:37

9 Answers9

31

Only in Pythons 2.7 and 3.1 and up:

def ffs(x):
    """Returns the index, counting from 0, of the
    least significant set bit in `x`.
    """
    return (x&-x).bit_length()-1

Example:

>>> ffs(136)
3
David Jones
  • 4,766
  • 3
  • 32
  • 45
  • What does `&-` mean? – flashburn Dec 09 '16 at 01:24
  • 3
    @flashburn It's `x & (-x)`: bit-wise and of `x` and negative `x`. – zeeMonkeez Dec 09 '16 at 02:15
  • @zeeMonkeez I thought it some mysterious operator. :D Thanks – flashburn Dec 09 '16 at 02:25
  • 5
    To explain how this works, in twos complement integer negation is equivalent to bitwise negation followed by adding one. Adding one to a bit flips it and if the bit was previously one produces a carry and thereby adding one to the next bit. The result of this is the only bit that is 1 in both x and -x is the least significant bit that was 1 in x. – plugwash Apr 25 '19 at 15:53
6

It is possible to load functions from shared libraries (DLLs for Windows users) using the ctypes module. I was able to load the ffs() function from the C standard library, contained in libc.so.6 on Ubuntu 10.10:

>>> import ctypes
>>> libc = ctypes.cdll.LoadLibrary('libc.so.6')
>>> libc.ffs(136)
4

(Note that this uses 1-based indexing). Obviously, this is not cross-platform compatible as-is; you'll need to change the filename of the library to load based on which system you're operating under (detected from sys.platform or similar). I wouldn't even be 100% sure it'd be the same on different Linux distributions.

It'd also be worth doing some proper benchmarking to see if its really worth it. If its called frequently it could be, but if its only used occasionally, the performance benefit over a Python implementation would probably be negligible compared to the maintenance to ensure it keeps working on different platforms.

An alternative would be to write your own implementation of the function in C and come up with a Python wrapper. You'd then have to compile it for each platform you want, but you lose the hassle of finding the correct library name while retaining the speed benefits.

Blair
  • 15,356
  • 7
  • 46
  • 56
6

It is available in the gmpy wrapper for the GNU Multi-Precision library. On my system, it is about 4x faster than the ctypes solution.

>>> import gmpy
>>> gmpy.scan1(136)
3
>>> bin(136)
'0b10001000'
casevh
  • 11,093
  • 1
  • 24
  • 35
4

Really all these answers with external modules, defining functions, etc. for a... bitwise operation???

(1 + (x ^ (x-1))) >> 1

will return the least significant power of 2 in x.

For instance, with x=136, answer will be 2^3 = 8.

Trick is remembering that x-1 has the same digits as x except all least significant 1 and all following zeros; then performing a bitwise XOR bitwin X and X+1 extracts these digits.

Then, you can extract the index with the bit_length() method.

user2986485
  • 73
  • 1
  • 1
  • 2
    I think you meant `x & -x`. – primo May 05 '14 at 11:06
  • Yes, really. Other answers compute log(your answer): for your example, the OP wants 3, not 8. This really is a bit annoying to get without processor support—which _e.g._ on x86 has been present since the introduction of the `BSF` and `BSR` instructions on the 80386 in 1985, so not exactly cutting edge technology, but availability in high-level languages is another matter. – Alex Shpilkin Dec 23 '20 at 23:11
  • Actually he did not mean `x & -x`, as XOR is also possible here. Please see: https://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear for a C variant. `def lsbIndex(x): return ((1 + (x ^ (x-1))) >> 1).bit_length()` is enough: `print([lsbIndex(x) for x in range(16)])` yields `[0, 1, 2, 1, 3, 1, 2, 1, 4, 1, 2, 1, 3, 1, 2, 1]`. The indexes are 1-based, and 0 indicates no index, might be better to subtract 1 if this is being used in context of Gray code index changes. – Gregory Morse Oct 29 '21 at 19:17
3

To flesh out S.Lott's comment, if the LSB is set, the value will be odd and if it is clear, the value will be even. Hence we can just keep shifting the value right until it becomes odd, keeping track of how many shifts it takes for this to occur. Just remember to check that there is a bit set first, otherwise the loop will become endless when it is given the value 0...

>>> def ffs(num):
...     # Check there is at least one bit set.
...     if num == 0:
...         return None
...     # Right-shift until we have the first set bit in the LSB position.
...     i = 0
...     while (num % 2) == 0:
...         i += 1
...         num = num >> 1
...     return i
...
>>> num = 136
>>> bin(num)
'0b10001000'
>>> ffs(num)
3
>>> ffs(0) is None
True

Note that this treats the LSB as bit 0; simply initialise i to 1 if you'd rather have 1-based indexing.

Blair
  • 15,356
  • 7
  • 46
  • 56
  • Thanks. Actually, I was hoping to find a way to avoid this, using a native function for the sake of speed. – brannerchinese Apr 02 '11 at 03:49
  • AFAIK, there isn't a built-in version. You can load the function from the systems C library using `ctypes` - see my other answer for details. Unless speed is a real bottleneck, a Python implementation would be much easier to maintain though. – Blair Apr 02 '11 at 04:14
  • Rē ease of maintenance: yes, indeed. I hope that never gets lost. C++ seems to me to involve needless anguish whenever I touch it. – brannerchinese Apr 04 '11 at 20:14
1

I know there's a selected answer already, but had a crack at the problem myself, because I could. The solution is based around the idea that if the value is a power of two you can take the log base two to find its position. The rest of the implementation revolves around transforming the value so that we can simply take the log.

I haven't benchmarked it, but the transformation is O(1) where n is the number of bits (and this view somewhat unfairly ignores the complexity introduced by the log(), though maybe it's around O(log(n))? 1). The implementation is loosely based on the 'decrement and compare' power-of-two method from 2:

import math

def ffs(value):
    """Find the first set bit in an integer, returning its index (from zero)"""
    if 0 > value:
        raise ValueError("No negative values!")
    if 0 == value:
        # No bits are set in value
        return None
    if (value & (value - 1)) != 0:
        # Multiple bits are set in value. Transform value such that only the
        # lowest bit remains set.
        value &= ~ (value - 1)
    # Only one bit is set in value, find its index from zero
    return int(math.log(value, 2))
MattAllegro
  • 6,455
  • 5
  • 45
  • 52
  • Careful with this in python2. `int(math.log(4, 2))` yields 2. `int(math.log(4L, 2))` yields 1. (Note the 2nd example use a long integer) – Chris Oct 28 '16 at 21:04
1

You can implement any of the algorithms identified here: http://graphics.stanford.edu/~seander/bithacks.html#ZerosOnRightLinear

I'm not aware of any native method to do so. (You could also write an extension to export the C function to Python, but that would probably not be worth the trouble :-)

Emil Sit
  • 22,894
  • 7
  • 53
  • 75
1

It's a little silly to try and aggressively optimize Python code, so a simple for loop with counter and right-shift should be fine. If you wanted to go faster (which would make more sense in C, Java, or Assembly) you could binary-search for the right-most 1-bit and even use lookup tables to help you.

Suppose x is 64-bits and you want the LSB. Mask off the lower 32-bits. Assume x is nonzero:

if x & 0xffffffff == 0:
  if x & 0xffff00000000 == 0:
    # the LSB is in the highest two bytes
  else:
    # the LSB is in the 5th or 6th byte
else:
  if x & 0xffff0000:
    # the LSB is in the 3rd or 4th byte
  else:
    # the LSB is in the 1st or 2nd byte

How you handle the commented section above depends on how aggressive you want to be: you could do further binary searching analogous to what we have, or you could use a lookup table. As it stands, we have 16-bits of uncertainty, so our table would be 65,536 entries. I have actually made tables like this for extremely performance-sensitive code, but that was a C program that played Chess (the 64-bit string there was a binary representation of the board).

Fixee
  • 1,581
  • 2
  • 15
  • 25
  • Thanks. As a principle, the idea of a huge lookup table as the price for speed is rather appealing, but I rarely get around to doing it. I always starting thinking of ways to optimize construction of the the table, too. – brannerchinese Apr 02 '11 at 03:44
  • As for the futility of aggressively optimizing Python code, since its running times will never approach those of more granual languages, I understand your point but it's hard not to indulge oneself, even so. Optimization is a central part of the philosophy of computer science. I wonder whether, when development begins again in earnest after the end of the moratorium, Python will not move to encompass lower-level coding that we presently do only by embedding. – brannerchinese Apr 02 '11 at 03:53
  • @texmad: Honestly I hope Python does **not** start trying to be a low-level language. We have plenty of those already; Python is great at being Python and I hope the developers resist pressures to ruin the language. – Fixee Apr 02 '11 at 04:57
  • 1
    @texmad: "huge lookup table"? It has 32 rows. One for each LSB position. – S.Lott Apr 02 '11 at 11:20
0
from math import log2
def bit_pos(n):
    """Returns the index, counting from 0, of the
    least significant set bit in `n`. If no bit is set, returns None.
    """   
    return int(log2(n^(n&(n-1)))) if n else None

bit_pos(32)
# returns 5