Bitwise Operations

Bitwise operations involve working with individual bits stored in a computer's memory. They perform actions on the individual bits within a binary representation of an integer value, which leads to an efficient manipulation and evaluation of binary data.

I have observed that many developers, even some experienced ones, lack a clear view on bitwise operations. In this article, I shine a light on them and hopefully help you get good with them. To aid you in the process, I have made an interactive playground along with this post! You can try all these operations out on there. I suggest you to finish reading this one, and then try out the playground.

#NOT, AND, OR, XOR

Bitwise NOT, AND, OR, operations are analogous to doing the corresponding logical operation bit by bit.

A description of these operations are given below, in case if you aren't familiar with them.

logical gatedescription
NOTnegates the output. 0s turns to 1s and vice versa
AND1 when both inputs are 1; 0 otherwise
OR0 when both inputs are 0; 1 otherwise
XOR1 when inputs are different; 0 when they are same

#SHIFTS

A bit shift operation moves all the bits by specific number of bits, in a specific direction. While doing so, some bits will be made vacant, and some will be pushed off. Shift operations are classified based on what happens to these pushed off and vacant bits.

NameVacant bitsPushed off bits
Arithmetic left shiftfilled with 0discarded
Arithmetic right shiftfor unsigned integers: filled with 0
for signed integers: filled with sign bit
Logical left shiftfilled with 0
Logical right shift
Circular left shiftvacant bit is filled with pushed off bit
Circular right shift

#Uses

Now, you might be asking why should we care about these bitwise operations at all? And here's why I think it's advantageous to learn about them.

Bitwise operations become extremely useful when optimizing algorithms and writing low-level system software.

As they operate on individual bits, they tend to be a lot faster compared to other operations such as division and modulo. We can optimize complex algorithms by switching out those expensive operations with equiavalent bitwise operations.

Bitwise operations are also used for efficient memory usage. Let's say we have to store 5 boolean values. If we declare a variable for each, totally we will allocate 5 bytes (40 bits) in memory. Although a boolean value can be expressed using a single bit, boolean variables are usually allocated with a byte (8 bits). On a memory-limited device, for example Arduino Nano, the unused 7 bits on each boolean could have been put to good use. Instead of declaring 5 variables, we can store all those values in a single 8-bit integer variable, where each bit point to each boolean value (5-bit integers are enough, but we can't have them). Bitwise operations allow us to read, write, and manipulate each value from that variable.

Note that bitwise operations come with cost: readability and maintainability. When a piece of code include one or more bitwise operations, many developers will probably have a hard time comprehending what the code does. Even the one who authored it, will need a few moments before all these funky-looking operations make sense. Be sure to keep it as readable as possible whenever you use bitwise operators.

Above, I have mentioned that these operations are faster than other operations. Faster in terms of CPU cycles, not seconds. Computers we now use can run more than a million CPU cycles in a second. And they have plenty of memory. They can run software written with no optimization in mind in reasonable amount of time. Bitwise operations wouldn't make much difference usually.
And considering the readability cost, we may not want or need to apply the above-mentioned optimizations using bitwise operations, generally.

However there may come a time when these optimizations are mandatory. Having a solid overview on these operations will be benefical in that case.