Do you recall my column, The Worm Turns, in which I first determined to build a 12 x 12 array of ping pong balls, each containing a tricolor WS2818 LED (a.k.a. NeoPixel)? If you are interested, you can see how this project is progressing in the videos on my Cool Beans Blog YouTube channel.

I’m also documenting the construction and programming of this bodacious beauty in a series of articles in Practical Electronics (PE), which is the UK’s premier electronics, computing, hobbyist, and maker magazine. In fact, I was just working on this month’s column and discussing a C/C++ function that features a mask operation using the & (bitwise AND) operator.

While I was writing, it struck me some of the readers may not be familiar with this concept. On the other hand, I’m sure that a lot of readers already know this stuff. So, since that column is already rather substantial, I decided to take this topic “offline” and present these concepts here.

The NOT, AND, OR, and XOR primitive logic gates (Click image to see a larger version — Image source: Max Maxfield)

Before we start, let’s remind ourselves of the functioning of the NOT, AND, OR, and XOR primitive gates. In the case of the NOT, the output has the opposite logical value to the input. In the case of the AND, the output is 1 only if both of its inputs are 1, otherwise its output is 0 (i.e., y is only 1 if both a and b are 1). By comparison,, in the case of the OR, the output is one if either of its inputs are 1 (i.e., y is 1 if either a or b are 1).

In fact, the OR should more properly be referred to as the “inclusive OR” because the output being 1 also includes the case where both of the inputs are 1. Contrast this with the XOR (“exclusive OR”), which excludes the case where both of the inputs are 1 (i.e., y is 1 if either a or b are 1, but not if both a and b are 1).

Sometimes we wish to perform similar functions in our C/C++ programs, which leads us to the bitwise operators, &, |, and ^. The ways in which these little rascals perform their magic are discussed below.

 

& (Bitwise AND)

The & (bitwise AND) operator accepts two integers as operands and performs a logical AND on each pair of corresponding bits. The result of each AND is 1 only if both of the bits are 1. For example, suppose we perform a & operation on two 32-bit values of 1100 0110 1011 0110 0100 0101 0111 0101 in binary (0xC6B64575 in hexadecimal) and 1110 1001 0001 1011 0011 1010 0001 0110 in binary (0xE91B3A16 in hexadecimal):

uint32_t myValueA = 0xC6B64575U;
uint32_t myValueB = 0xE91B3A16U;
uint32_t myValueY = myValueA & myValueB;

Note that adding ‘U’ (or ‘u’) characters on the end of the hexadecimal values directs the compiler to regard them as being unsigned (most of the time this won’t be necessary, but it doesn’t hurt, and every now and then…). A graphical representation of this is shown below:

Graphical representation of the & (bitwise AND) operation (Click image to see a larger version — Image source: Max Maxfield)

Observe how the 32-bit value is composed from four 8-bit bytes. If we start with the least-significant bit (LSB), bit 0, we see that myValueB has a 0 in this position, which means bit 0 in myValueY will be cleared to 0. In the case of bit 1, myValueA has a 0 in this position, so bit 1 in myValueY will be cleared to 0.

In the case of bit 2, both myValueA and myValueB have 1s in this position, which means bit 2 in myValueY will be set to 1. When we come to bit 3, both myValueA and myValueB have 0s in this position, which means bit 3 in myValueY will be cleared to 0. And so it goes…

Not surprisingly, unless the original values are identical, the result will have fewer 1s than either of them.

 

| (Bitwise OR)

The | (bitwise OR) operator accepts two integers as operands and performs a logical OR on each pair of corresponding bits. The result of each OR is 1 if either of the bits are 1. For example, suppose we perform a | operation on the same values we used in the previous example:

uint32_t myValueA = 0xC6B64575U;
uint32_t myValueB = 0xE91B3A16U;
uint32_t myValueY = myValueA | myValueB;

A graphical representation of this is shown below:

Graphical representation of the | (bitwise OR) operation (Click image to see a larger version — Image source: Max Maxfield)

Once again, if we start with bit 0, we see that myValueA has a 1 in this position, which means bit 0 in myValueY will be set to 1. In the case of bit 1, myValueB has a 1 in this position, so bit 1 in myValueY will be set to 1.

In the case of bit 2, both myValueA and myValueB have 1s in this position, which means bit 2 in myValueY will also be set to 1. When we come to bit 3, both myValueA and myValueB have 0s in this position, which means bit 3 in myValueY will be cleared to 0. And so forth for the remaining bits.

In this case, the result will have more 1s than either of the original values unless those values are identical.

 

^ (Bitwise XOR)

The ^ (bitwise XOR) operator accepts two integers as operands and performs a logical XOR on each pair of corresponding bits. The result of each XOR is 1 if the source bits are different (one 0 and the other 1), and 0 if the source bits are the same (both 0s or both 1s). For example, suppose we perform a ^ operation on the same values we used in our previous examples:

uint32_t myValueA = 0xC6B64575U;
uint32_t myValueB = 0xE91B3A16U;
uint32_t myValueY = myValueA ^ myValueB;

A graphical representation of this is shown below:

Graphical representation of the ^ (bitwise XOR) operation (Click image to see a larger version — Image source: Max Maxfield)

As before, if we start with bit 0, we see that myValueA and myValueB have different values, which means bit 0 in myValueY will be set to 1. Similarly, in the case of bit 1, myValueA and myValueB once again have different values, so bit 1 in myValueY will be set to 1.

In the case of bit 2, both myValueA and myValueB have 1s in this position, which means bit 2 in myValueY will be cleared to 0. Similarly, when we come to bit 3, both myValueA and myValueB have 0s in this position, which means bit 3 in myValueY will also be set to 1. You might want to check the remaining bits to make sure I haven’t made any mistakes.

One interesting thing about the ^ operator is that we can use it to invert selected bits (i.e., swap 0s for 1s and vice versa). Let’s suppose, for example, that we wish to invert bits 0 through 15 in myValueA. We could achieve this as follows (where ‘F’ in hexadecimal equates to 1111 in binary):

uint32_t myValueA = 0xC6B64575U;
uint32_t myValueY = myValueA ^ 0x0000FFFFU;

A graphical representation of this is shown below:

Using the ^ (bitwise XOR) to invert bits (Click image to see a larger version — Image source: Max Maxfield)

 

Masking Using the Bitwise Operators

In the context of computer science, a mask, or bitmask, can be used to clear one or more bits to 0, set one or more bits to 1, or invert one or more bits as required. We’ve already seen an example of masking when we used the ^ (bitwise XOR) to invert bits 0 through 15 of myValueA.

Let’s suppose that the only part of myValueA in which we are interested encompasses bits 16 through 23, and we wish to clear the remaining bits to 0. In this case, we could use a & (bitwise AND) as follows:

uint32_t myValueA = 0xC6B64575U;
uint32_t myValueY = myValueA & 0x00FF0000U;

A graphical representation of this is shown below:

Using the & (bitwise AND) to mask bits (Click image to see a larger version — Image source: Max Maxfield)

In many cases, we may wish to copy these eight bits of interest into an 8-bit variable. One way to do this would be to first perform the mask operation (resulting in 0x00B60000) as illustrated above, then use the >> operator to shift this value 16 bits to the right (resulting in 0x000000B6), and finally cast this value into an 8-bit variable, in which case only the least-significant 8 bits of the 32-bit value will be copied into the 8-bit field. An alternative approach would be as follows:

uint32_t myValueA = (0xC6B64575U >> 16);
uint32_t myValueY = myValueA & 0x000000FFU;

In this case, we first perform the shift operation (resulting in 0x0000C6B6), and we then perform a mask operation on the least-significant 8 bits using 0x000000FF (resulting in 0x000000B6).

As one final example, let’s suppose we wish to take whatever value is stored myValueA, preserve bits 0 through 15 and bits 24 through 31 as-is, and set bits 16 through 23 to be 1s. In this case, we could use a | (bitwise OR):

uint32_t myValueA = 0xC6B64575U;
uint32_t myValueY = myValueA | 0x00FF0000U;

A graphical representation of this is shown below:

Using the | (bitwise OR) to mask bits (Click image to see a larger version — Image source: Max Maxfield)

Observe that the only difference between this example and our previous example is that we swapped the & (bitwise AND) for the | (bitwise OR).

 

Testing Bits Using the Bitwise Operators

When writing programs, it’s very often the case that we want to test a bit buried deep in an integer to see whether it’s 0 or 1. This is really just an extension of the masking operations we’ve already discussed.

Let’s assume that we want to know if bit 19 of myValueA is a 0 or a 1. We could do this using a mask operation as follows:

uint32_t myValueA = 0xC6B64575U;
uint32_t myValueY = myValueA & 0x00080000U;

A graphical representation of this is shown below:

Using the & (bitwise AND) to test if a bit is 0 or 1 (Click image to see a larger version — Image source: Max Maxfield)

In this particular example, we already know that this bit is 0 because we set it to be so when we defined the original value of myValueA. However, we wouldn’t know what the value was if we had just generated myValueA as the result of another operation, or read this value from memory or from the outside world.

The key point to note here is that our mask operation has cleared every bit to be 0 apart from bit 19 . In the case of bit 19 itself, this will be a copy of the original bit from myValueA.

If we want to do something if this bit is 0, all we have to do is check to see if myValueY contains a zero value:

if (myValueY == 0)
{
// We know bit 19 is 0
}

Contra wise, if we want to do something if this bit is 1, all we have to do is check to see if myValueY contains a non-zero value:

if (myValueY != 0)
{
// We know bit 19 is 1
}

Furthermore, if we wish to check to see if several bits are set to 1 — for example, the third bit in each of the bytes forming our 32-bit myValueY — then we could declare a value called myMask and use it as follows:

uint32_t myMask = 0x08080808U;
if ((myValueY & myMask) == myMask)
{
    // All of the bits defined in myMask are 1 in myValueY
}

 

Setting and Clearing Bits Using the Bitwise Operators

As opposed to testing bits to see if they are 0 or 1, we can always use the bitwise operators to set bits to 1 or clear them to 0. Suppose we perform the following addition operation:

uint32_t myValueA = 0xC6B64575U;
uint32_t myValueB = 0xE91B3A16U;
uint32_t myValueY = myValueA + myValueB;

Do you know if bit 19 of the result is 0 or 1? I know I don’t. In this case, of course, we could perform the addition and look at the result, but in the real world we may not know what the original values of the operands were.

So, suppose we wish to ensure that bit 19 of the result is set to 1 while leaving all of the other bits “as-is.” We could do so using a | (bitwise OR) operator as follows:

myValueY = myValueY | 0x00080000U;

Alternatively, suppose we wish to ensure that bit 19 of the result is cleared to 0 while leaving all of the other bits “as-is.” We could do so using a & (bitwise AND) operator as follows:

myValueY = myValueY & 0xFFF7FFFFU;

Obviously 0xFFF7FFFF is the inverse of 0x00080000, so another way to do this would be to use the ~ (bitwise NOT) operator, which performs a ones complement operation and swaps 0s for 1s and vice versa. Suppose we’ve previously defined a mask:

uint32_t myMask = 0x00080000U;

Then to clear bit 19 we could use the following:

myValueY = myValueY & (~myMask);

Or we could use a compound assignment to more concisely achieve the same effect:

myValueY &= (~myMask);

This technique makes it more obvious as to which bit we are trying to set or clear. Furthermore, we can use the same technique to set and clear multiple bits. All we have to do is set the bits of interest to be 1s in the mask, and then use the | operator in conjunction with the mask to set these bits to 1, or the & operator in conjunction with the ~ operator to clear them to 0s. This approach is often used where multiple bit-fields exist in a register, thereby allowing an entire functional field to be set or cleared with a single statement.

All of this can be a bit tricky for beginners, but once we’re wrapped our heads around these concepts, we find that using the bitwise operators in general — and employing them to perform masking, bit testing, and bit setting/clearing operations in particular — can be extremely efficacious. As always, I welcome your comments, questions, and suggestions.