Cheiron
Cheiron

Reputation: 3746

Comparing a bit to a boolean

Say I have a set of flags, encoded in a uint16_t flags. For example, AMAZING_FLAG = 0x02. Now, I have a function. This function needs to check if I want to change the flag, because if I want to do that, I need to write to flash. And that is expensive. Therefore, I want a check which tells me if flags & AMAZING_FLAG is equal to doSet. This is the first idea:

setAmazingFlag(bool doSet)
{
    if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0)) {
        // Really expensive thing
        // Update flags
    }
}

This is not an intuitive if statement. I feel like there should be a better way, something like:

if ((flags & AMAZING_FLAG) != doSet){

}

But this does not actually work, true seems to be equal to 0x01.

So, is there a neat way to compare a bit to a boolean?

Upvotes: 12

Views: 2012

Answers (5)

chqrlie
chqrlie

Reputation: 145307

There are multiple ways to perform this test:

The ternary operator might generate costly jumps:

if ((flags & AMAZING_FLAG) != (doSet ? AMAZING_FLAG : 0))

You can also use boolean conversion, which may or may not be efficient:

if (!!(flags & AMAZING_FLAG) != doSet)

Or its equivalent alternative:

if (((flags & AMAZING_FLAG) != 0) != doSet)

If multiplication is cheap, you can avoid jumps with:

if ((flags & AMAZING_FLAG) != doSet * AMAZING_FLAG)

If flags is unsigned and the compiler is very smart, the division below might compile to a simple shift:

if ((flags & AMAZING_FLAG) / AMAZING_FLAG != doSet)

If the architecture uses two's complement arithmetic, here is another alternative:

if ((flags & AMAZING_FLAG) != (-doSet & AMAZING_FLAG))

Alternately, one could define flags as a structure with bitfields and use a much simpler an readable syntax:

if (flags.amazing_flag != doSet)

Alas, this approach is usually frowned upon because the specification of bitfields does not allow precise control over bit-level implementation.

Upvotes: -1

Alex Lop.
Alex Lop.

Reputation: 6875

You can create a mask based on doSet value:

#define AMAZING_FLAG_IDX 1
#define AMAZING_FLAG (1u << AMAZING_FLAG_IDX)
...

uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

Now your check can look like this:

setAmazingFlag(bool doSet)
{
    const uint16_t set_mask = doSet << AMAZING_FLAG_IDX;

    if (flags & set_mask) {
        // Really expensive thing
        // Update flags
    }
}

On some architectures, !! may be compiled to a branch and by this, you may have two branches:

  1. Normalisation by !!(expr)
  2. Compare to doSet

The advantage of my proposal is a guaranteed single branch.

Note: make sure you don't introduce undefined behaviour by shifting left by more than 30 (assuming integer is 32 bits). This can be easily achieved by a static_assert(AMAZING_FLAG_IDX < sizeof(int)*CHAR_BIT-1, "Invalid AMAZING_FLAG_IDX");

Upvotes: 1

Lundin
Lundin

Reputation: 215174

You need to convert the bit mask to a boolean statement, which in C is equivalent to values 0 or 1.

  • (flags & AMAZING_FLAG) != 0. The most common way.

  • !!(flags & AMAZING_FLAG). Somewhat common, also OK to use, but a bit cryptic.

  • (bool)(flags & AMAZING_FLAG). Modern C way from C99 and beyond only.

Take any of the above alternatives, then compare it with your boolean using != or ==.

Upvotes: 2

Ctx
Ctx

Reputation: 18430

From a logical point of view, flags & AMAZING_FLAG is only a bit operation masking all other flags. The result is a numerical value.

To receive to a boolean value, you would use a comparison

(flags & AMAZING_FLAG) == AMAZING_FLAG

and can now compare this logical value to doSet.

if (((flags & AMAZING_FLAG) == AMAZING_FLAG) != doSet)

In C there may be abbreviations, because of the implicit conversion rules of numbers to boolean values. So you could also write

if (!(flags & AMAZING_FLAG) == doSet)

to write that more terse. But the former version is better in terms of readability.

Upvotes: 1

To convert any non-zero number to 1 (true), there is an old trick: apply the ! (not) operator twice.

if (!!(flags & AMAZING_FLAG) != doSet){

Upvotes: 18

Related Questions