Bitmasks are a very useful way to compress multiple boolean flags in a single variable. It can reduce memory usage and operations on bits are basically as fast as they can get. In practice, any time you want to have multiple flags describing something in your application, a bitmask could be the right tool for the job.
We could add many more, but for the purpose of explanation this will do.
And, just for contrast, let’s add a parrot:
bool canRun = true;
bool canBark = true;
bool hasTail = true;
bool canJump = true;
bool canFly = false;
bool isCute = false;
First thing worth considering is that it takes 6 variables and 6 bytes (48 bits) of memory space (for simplification I’m assuming that you use a language where a single boolean takes 1 byte in memory, like in C++, but this value can vary) to describe every animal. It is a personal taste if having many boolean flags is a good or bad choice in terms of readability, but undeniably there is a more efficient way of holding this data in our memory.
bool canRun = false;
bool canBark = false;
bool hasTail = true;
bool canJump = false;
bool canFly = true;
bool isCute = true;
The above sign << represent a bit shifting operator. I will explain it in detail later. This is easy to write because you don’t have to calculate the power of 2 for every new flag, just increment the value to the right of operator << when adding a new flag.
const unsigned int CAN_RUN = 1 << 0; // 1
const unsigned int CAN_BARK = 1 << 1; // 2
const unsigned int HAS_TAIL = 1 << 2; // 4
const unsigned int CAN_JUMP = 1 << 3; // 8
const unsigned int CAN_FLY = 1 << 4; // 16
const unsigned int IS_CUTE = 1 << 5; // 32
We have constants defining the value for each property, and we have a base animal that will hold a single unsigned integer. 0 means that no masks have been assigned to the animal which translates to having all booleans set to false by default. Let’s make our new dog and parrot now.
unsigned int properties = 0;
unsigned int properties = 15;
To come up with these numbers, we simply add all the necessary masks together using bitwise OR operator, a | pipe character:
unsigned int properties = 52;
CAN_RUN (1) | CAN_BARK (2) | HAS_TAIL (4) | CAN_JUMP (8) = 15
If some time will pass, our parrot could stop being cute as it repeats something annoying over and over again. We could then toggle the IS_CUTE flag from the properties bitmask with the ^ sign. It will either remove or add it depending on the current state of the bitmask.
HAS_TAIL (4) | CAN_FLY (16) | IS_CUTE (32) = 52
Or make sure to remove it no matter the current state by unsetting the IS_CUTE flag with bitwise AND & operation on the flag, unset by the bitwise NOT ~ operator. In this example, ~IS_CUTE means every flag except IS_CUTE is set to true and we want to get the intersection of it with parrot properties. The result is all the flags that were already set, except IS_CUTE.
parrot.properties ^= IS_CUTE;
parrot.properties &= ~IS_CUTE;
The above translates to: give me every mask that exists both to the left and to the right of the & ampersand character and compare == to see if what I need is the result. The above will return true since our dog has a tail.
if ((dog.properties & HAS_TAIL) == HAS_TAIL)
The | pipe character will give us a bitmask consisting of every flag that is in a dog or in a parrot and & ampersand will check if there is a HAS_TAIL flag in the result, filtering all the rest out.
if (((dog.properties | parrot.properties) & HAS_TAIL) == HAS_TAIL)
And the last operator is ~ tilde, bitwise NOT operator. It inverts the bitmask, turning true values to false and vice versa.
if (((dog.properties ^ parrot.properties) & HAS_TAIL) == HAS_TAIL)
if ((~dog.properties & CAN_FLY) == CAN_FLY) // returns true, as our dog cannot fly
Now let’s add our CAN_RUN(1) flag to the properties and see what will happen.
unsigned int properties = 0;
// Is represented in memory as:
// 00000000 00000000 00000000 00000000
First bit starting from the right side turned to 1, which can be read by out application as an unsigned integer of value 1. Let’s add CAN_BARK (2) flag
properties |= CAN_RUN;
// 00000000 00000000 00000000 00000001
Now our properties hold 2 flags: CAN_RUN (1) and CAN_BARK (2), which also can be read as an unsigned integer of value 3.
properties |= CAN_BARK;
// 00000000 00000000 00000000 00000011
Since our IS_CUTE flag is the 6th flag starting from the beginning, to turn it on we just change the value of the 6th bit counting from the right side to a value of 1. These bytes can be read by our application as an unsigned integer of value 35 (32 + 2 + 1, values from 3 of our flags turned on).
properties |= IS_CUTE;
// 00000000 00000000 00000000 00100011
We can move all of our bits by using << left shift operator or >> right shift operator. Let's see what will happen when we try to move bits of our dog with << left shift operator.
unsigned int properties = CAN_RUN | CAN_BARK | HAS_TAIL | CAN_JUMP;
// 00000000 00000000 00000000 00001111
This moved all our flags forward, which essentially made our dog not being able to run anymore but gave him the ability to fly.
properties = properties << 1;
// 00000000 00000000 00000000 00011110
Shifting bits is faster than regular calculations like multiplying or division, but this kind of microoptimization is something that your compiler might be doing on its own.
properties = properties >> 1;
// 00000000 00000000 00000000 00000111
That’s essentially it for bit shifting. There is just one more thing you should know about bitmasks.
properties = 1 << 3;
// 00000000 00000000 00000000 00001000