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.
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.
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.
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.
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.
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.
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! 👋🏼