What is the fastest way to get the lowest set bit of a C++ std::bitset?

1.4k views Asked by At

I noticed that std::bitset does not have a function for returning or popping the lowest set bit of a bitset (nor the highest set bit for that matter). What is the fastest way to accomplish this task, specifically for std::bitset objects not guaranteed to be a certain number of bits long? I looked in the g++ compiler extensions and C++20 numerics library and didn't find anything related to my problem.

Two obvious methods to consider would be looping over the length of the bitset and using the operator[] or gradually shifting the bitset using operator>> until the first set bit is found.

4

There are 4 answers

0
ramsay On

You can do something like this:

#include <bitset>
#include <iostream>
int main(int argc, char **argv) {
  std::bitset<32> your_bit{0B1010000};
  int lsb = your_bit[0];
  std::cout << lsb << '\n';
}
12
David C. Rankin On

There is no .lsb() or .msb() member functions, but std::bitset does provide .size() and .test() (and .any(), credit to @phuctv for use of .any() over .count()) with which you can construct the lsb and msb routines.

Presuming a valid std::bitset you can verify that at least one bit is set true using .any() (or just check the unsigned value). After verifying at least one bit is true, simply loop from bit-0 to bit-(bitset.size() - 1) checking for a set bit with .test() to obtain the LSB. Then just loop in reverse with the same test to find the MSB.

A short implementation would be:

#include <iostream>
#include <bitset>

int main () {
  
  size_t i = 0;                 /* bit indiex */
  std::bitset<8> bits (236);    /* bitset '11101100' */
  
  if (!bits.any()) {  /* validate at least 1 bit set */
    std::cerr << "pop count is zero.\n";
    return 1;
  }
  
  /* loop bit 0 to bits.size() - 1 for LSB */
  do {
    if (bits.test(i))             /* test if bit set */
      break;
  } while (++i < bits.size());
  
  std::cout << "lsb in '" << bits << "' is: " << i << '\n';
  
  /* loop bit bits.size() - 1 to 0 for MSB */
  i = bits.size();
  while (i--) {
    if (bits.test(i))             /* test if bit set */
      break;
  }
  
  std::cout << "msb in '" << bits << "' is: " << i << '\n';
}

Example Use/Output

$ ./bin//bitset_test
lsb in '11101100' is: 2
msb in '11101100' is: 7

Extend std::bitset and Add .lsb() and .msb() Member Functions

In addition to simply writing a couple of functions, you can just derive from std::bitset and add .lsb() and .msb() member functions to the derived class.

A short class declaration using the same implementations above could be:

template<size_t Nb>
class mybitset : public std::bitset<Nb> {
  
  std::bitset<Nb> bits;
  
 public:
  mybitset (const std::bitset<Nb>&b) : std::bitset<Nb>{b} { bits = b; }
  
  size_t lsb();     /* extend std::bitset with .lsb() and .msb() members */
  size_t msb();
  
  template<size_t NB>
  friend std::ostream& operator << (std::ostream& os, const mybitset<NB>& b);
};

Then you can simply use the .lsb() and .msb() members directly, e.g.

int main () {
  
  mybitset<8> bits (236);       /* derived class */
  
  if (!bits.any()) {  /* validate at least one bit set */
    std::cerr << "bitset value is zero -- zero pop count.\n";
    return 1;
  }
  
  /* output LSB and MSB */
  std::cout << "lsb in '" << bits << "' is: " << bits.lsb() <<
             "\nmsb in '" << bits << "' is: " << bits.msb() << '\n';
}

(same output)

2
phuclv On

I'm going to use trailing zero like most modern implementations to make it easy to deal with the case where there's no set bit. To utilize the hardware count trailing zero instruction we can use to_ullong(), but to make it work we'll need to mask the value to make it fit in an unsigned long long

#include <bitset>
#include <bit>
#include <climits>

template<size_t N>
size_t count_trailingzero(std::bitset<N> b)
{
    if (b.none())
        return N;                   // The whole bitset was zero

    const decltype(b) mask(-1ULL);  // Mask to get the lowest unsigned long long
    size_t tz = 0;                  // The number of trailing zero bits
    const int width = sizeof(unsigned long long)*CHAR_BIT;
    do {
        auto lsw = (b & mask).to_ullong();  // The least significant word
        auto lsb = std::countr_zero(lsw);   // Position of the least significant bit

        if (lsb < width)                    // Found the first set bit from right
            return tz + lsb;
        
        // A set bit was not found because the lsw is all zero
        // so we'll increase the number of trailing zero bits
        tz += width;

        // Shift the bitset to get the next higher significant word
        b >>= width;
    } while (b.any());

    return tz;
}

Demo on Godbolt

This way no looping over individual bits is required and hardware acceleration can be used. But it's still not the most efficient method because each iteration the whole bitset still needs to be masked off. That's why std::bitset isn't a good tool for operating on bits in general and I always avoid them in practice. A class wrapping an array for bit operations will be much better in performance and flexibility

1
daveb On

A bit of a late answer, C++20 includes <bit> which has the functions you want.

In particular std::countr_zero which returns the number of consecutive 0 bits starting from the least significant bit.

#include <bit>
#include <iostream>

// This requires C++20 (or later)

int main() {
  std::cout << "lsb of 0b0100u is " << std::countr_zero(0b0100u) << "\n";
}

// std::countr_zero(0b0100u) is 2.