Reputation: 876
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?
Upvotes: 1
Views: 944
Reputation: 11875
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.
Upvotes: 1
Reputation: 876
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.
Upvotes: 1