1

Given a value, I want to check if some of its bits are set and some of them unset at the same time, while I don't care about the value of others.

I can do this with two bit masks, one for set bits one for unset bits like so:

#include <iostream> bool checkMask(uint8_t value, uint8_t setmask, uint8_t unsetmask) { return (value & setmask) == setmask && (~value & ~unsetmask) == ~unsetmask; } int main() { uint8_t setmask = 0b0100'0001; uint8_t unsetmask = 0b1111'1011; uint8_t valueMatches = 0b01101'1011; uint8_t valueFails1 = 0b00101'1010; uint8_t valueFails2 = 0b01101'1111; std::cout << "matches " << checkMask(valueMatches, setmask, unsetmask) << std::endl; std::cout << "fails1 " << checkMask(valueFails1, setmask, unsetmask) << std::endl; std::cout << "fails2 " << checkMask(valueFails2, setmask, unsetmask) << std::endl; } 

(May be buggy, it's an example)

Of course, this can also be done with a string, where I can represent more than 0 and 1 with a wildcard value like:

string bitmask = ".1...001"; 

and then check bit by bit from the string, ignoring '.' and checking 0s and 1s match.

In my solutions there's a tradeoff, either using 2 values, which makes it less intuitive (specially the unset mask), or ussing a string which is more inefficient, but really clear.

Are there other options?

3
  • 1
    As the mask is usually going to be known in advance, I would write a constexpr that would transform the string bitmask into class TwoMasks in compile-time, letting the user represent mask as a string and compiler speed up runtime with two masks. You could also write a custom constructor with 8 arguments, one argument for one bit (ex. -1 represents any bit), to create such "two masks". Or a constructor with a list of pairs, representing bit position and value.You could also write a custom literal. As such, I believe the question Is there a better way? is opinion based - "better" based on? Commented Feb 1, 2021 at 8:26
  • Nice idea to use constexpr for that. In my case I'd use it in a config, so it's not really known on compile time. But that's a good answer. Commented Feb 1, 2021 at 8:37
  • checkMask can be more simply expressed as (value & unsetmask) == (value | setmask). Commented Feb 2, 2021 at 19:29

2 Answers 2

1

Some mostly generic possibilities based off of @KamilCuk comment

#include <iostream> #include <utility> #include <array> #include <bitset> #include <string> constexpr uint8_t BYTE_BITS = 8; template <typename D> class BitMask { public: constexpr BitMask(const D setmask, const D unsetmask) : m_setmask(setmask), m_unsetmask(unsetmask) {} constexpr BitMask(const char * bits) { m_setmask = 0; m_unsetmask = 0; for (int i = 0; i < sizeof(D)*BYTE_BITS; i++) { m_setmask = m_setmask << 1; m_unsetmask = m_unsetmask << 1; m_setmask += bits[i] == '1' ? 1 : 0; m_unsetmask += bits[i] == '0' ? 0 : 1; } } constexpr BitMask(const std::array<D,sizeof(D)*BYTE_BITS>& bits) { m_setmask = 0; m_unsetmask = 0; for (int i = 0; i < sizeof(D)*BYTE_BITS; i++) { m_setmask = m_setmask << 1; m_unsetmask = m_unsetmask << 1; m_setmask += bits[i] == 1 ? 1 : 0; m_unsetmask += bits[i] == 0 ? 0 : 1; } } constexpr bool check(const D value) const { return BitMask::check(value, m_setmask, m_unsetmask); } static bool check(const D value, const D setmask, const D unsetmask) { return (value & setmask) == setmask && (~value & ~unsetmask) == ~unsetmask; } void print() const { std::cout << "Set mask: " << std::bitset<sizeof(D)*BYTE_BITS>(m_setmask) << '\n' << "Unset mask: " << std::bitset<sizeof(D)*BYTE_BITS>(m_unsetmask) << '\n'; } private: D m_setmask; D m_unsetmask; }; int main() { constexpr BitMask<uint8_t> mask1(0b0100'0001, 0b1111'1011); constexpr BitMask<uint8_t> mask2(std::array<uint8_t, sizeof(uint8_t)*BYTE_BITS>{2,1,2,2,2,0,2,1}); constexpr BitMask<uint8_t> mask3("?1???0?1"); uint8_t valueMatches = 0b01101'1011; uint8_t valueFails1 = 0b00101'1010; uint8_t valueFails2 = 0b01101'1111; std::cout << "matches " << mask1.check(valueMatches) << std::endl; std::cout << "fails1 " << mask1.check(valueFails1) << std::endl; std::cout << "fails2 " << mask1.check(valueFails2) << std::endl; mask1.print(); mask2.print(); mask3.print(); return 0; } 

It may be that the string option is not as slow as I initially thought. At least given how intuitive it is to use.

Problems include, no checking the string length. In constexpr it will just not compile if the string is too short, which is good, but will ignore if it's longer. Can't create std::string in constexpr context to check the size matches the type, though strlen should be constexpr.

Sign up to request clarification or add additional context in comments.

1 Comment

@KamilCuk only for the char* case. For the array of bytes I'd expect decimal 1s and 0s.
1

Depending on the use cases of code using this, you could as well try the following:

bool check(uint8_t value, uint8_t pattern, uint8_t relevant_bits) { return (relevant_bits & pattern) == (relevant_bits & (value ^ ~pattern)); } 

If you have named masks for the bits (e.g. in a control register) akin to:

constexpr uint8_t F1 = 0x01; ... constexpr uint8_t F8 = 0x80; 

you can use this function like this:

 if (check(value, F1|F4|F6, F1|F2|F3|F4|F6)) { std::cout << "match!" << std::endl; } else { std::cout << "not a match!" << std::endl; } 

pattern lists the wanted unset bits implicitly, while relevant_bits still shows, they are well... relevant.

You be the judge if this way of handling things is more convenient to your use case.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.