Logo

Understanding Bitwise Operations

Understanding Bitwise Operations

As you probably already know, computers work in bits (0's and 1's). This is the language of the computer. Now, imagine you’ve been given a task to use only one variable to indicate whether a user has read, write, or execute permissions for a file. You could use a boolean, but that can only hold two states (such as true for read and false for write). This is where bitwise operations can be helpful.

We will solve this problem later on, but first, let's go over the most common bitwise operations: bitwise AND (&), bitwise OR (|), bitwise XOR (^), bitwise NOT (~), left shift (<<), and right shift (>>).

Left shift (<<)

With a left shift, each bit is shifted to the left by a specified number of positions.

Bitwise left shift representation

Each bit is shifted to the left by one position, effectively adding a 0 bit at the end.

Here is code to represent this:

#include <iostream> int main() { constexpr int x = 3; constexpr int y = x << 1; std::cout << y; return 0; }

This will output 6, which is 110 in binary.

Right shift (>>)

This is similar to a left shift but in the opposite direction, as each bit is shifted to the right.

Bitwise Right shift representation

Here is a code to represent this;

#include <iostream> int main() { constexpr int x = 3; constexpr int y = x >> 1; std::cout << y; return 0; }

This will output 1, which is 001 in binary.

Bitwise AND (&)

With bitwise AND, we compare two bits, and the output bit is 1 only if both bits are 1.

Bitwise AND representation

Here is a code to represent this;

#include <iostream> int main() { constexpr int x = 3; constexpr int y = 6; constexpr int z = x & y; std::cout << z; return 0; }

This will output 2, which is 010 in binary.

Bitwise OR (|)

With bitwise OR, we also compare two bits, and the output bit is 1 if one or both bits are 1.

Bitwise OR representation

Here is a code to represent this;

#include <iostream> int main() { constexpr int x = 3; constexpr int y = 6; constexpr int z = x | y; std::cout << z; return 0; }

This will output 7, which is 111 in binary.

Bitwise XOR (^)

With this, we also compare two bits, and the output bit is 1 only if one of the bits is 1. If both bits are 1, the output is 0.

Bitwise XOR representation

Here is a code to represent this;

#include <iostream> int main() { constexpr int x = 3; constexpr int y = 6; constexpr int z = x ^ y; std::cout << z; return 0; }

This will output 5, which is 101 in binary.

Bitwise NOT (~)

With bitwise NOT, if a bit is 1, it becomes 0; if it is 0, it becomes 1.

Bitwise NOT representation

Here is a code to represent this;

#include <iostream> int main() { constexpr int x = 3; constexpr int y = ~x; std::cout << y; return 0; }

Now, if you run this, you will notice that you get -4 rather than 4. This is because we are using int, which by default has a signed bit to indicate whether the number is positive or negative (0 for positive and 1 for negative). With bitwise NOT, we are performing this operation on all bits, including the signed bit, which causes it to change to 1, indicating a negative number. It will look something like this for a 32-bit integer: 11111111 11111111 11111111 11111100.

In binary, this conversion to decimal is 4294967292, but since we are using two's complement, this number becomes -4 instead. You can watch this informative video on binary two's complement if you want to learn how this works: https://www.youtube.com/watch?v=sJXTo3EZoxM.

Our Mini Project

Now, let's work on our file system problem from earlier using what we've learned.

What we want is to use three bits to toggle the access types for the user:

#include <iostream> #define READ 0b01 // ie 1 in decimal to indicate the user has read access #define WRITE 0b10 // ie 2 in decimal to indicate the user has write access #define EXECUTE 0b100 // ie 4 in decimal to indicate the user has execute access
int main() { constexpr unsigned int access = READ | WRITE; if ((access & READ) != 0) { std::cout << "User has access to read file\n"; } if ((access & WRITE) != 0) { std::cout << "User has access to write file\n"; } if ((access & EXECUTE) != 0) { std::cout << "User has access to execute file\n"; } return 0; }

Our access variable is evaluated as 11 in binary. This is because we are performing a bitwise OR operation on 01 and 10. In our first condition (access & READ), it evaluates to 01, as we are doing a bitwise AND on 11 (our access value) and 01 (our READ value). Since 01 in binary is 1 in decimal and is not equal to 0, this means the user has read access, so this condition will run. The same applies to the second condition.

For the third condition, access remains as 11, but our EXECUTE value is 100. When you perform a bitwise AND on both, you will get 000, which is 0 in decimal, so our condition won't run, meaning the user doesn't have execute access.

Isn't it cool that we just handled three conditions using just one variable?

Feel free to play around with the access variable by granting the user execute access or removing write access.

Happy coding! 👋🏼