Bitwise Operators and WHY we use them

Alex Hyett - Oct 31 '22 - - Dev Community

Bitwise operators are one of those concepts that a lot of programmers don’t understand. These are not used a great deal anymore so you can get away with not knowing them but they can still come in handy for a number of different scenarios.

If you end up writing algorithms for encryption and video compression then it is definitely something you need to know.

As you can probably guess from the name, bitwise operators work on the individual bits. If you want to perform operations on binary numbers then you need to use bitwise operators.

Bitwise operations aren’t the most intuitive of things so before I go through the different types of bitwise operators it is worth looking at why we use them.

Why use bitwise operators?

Bitwise operators were used a lot more in programming when computers didn’t have as much memory in them as they do now. Bitwise operators are still used for those working on embedded devices that have memory limitations.

The smallest amount of space a variable can take up is 1 byte. The reason for this is that it is the smallest unit of addressable space that a CPU can reference.

Even a boolean that only has the values true and false and therefore should only take up 1 bit still uses a full byte and has the remaining 7 bits padded out.

0000 0001 = true
0000 0000 = false
Enter fullscreen mode Exit fullscreen mode

This isn’t great if you are trying to save memory.

Most applications have multiple flags, especially those that are referencing physical hardware.

To make the most out of the available space programmers would combine multiple flags into a single byte allowing them to store up to 8 different boolean values.

Which is just what you need if you are trying to conserve memory on a small device.

The one place you might have seen multiple flags being stored is with unix file permissions. Typically you might see file permissions denoted as numbers like 644 or 777.

The first number is the permissions for the file owner, the second is the permissions for the group and the third is for everyone else.

The number tells you whether they have permission to read a file, write to a file or execute a file.

To understand what each number means you need to convert the number into binary first.

7 in binary is 111 which means they have the ability to read, write and execute.6 in binary is 110 which means they have the ability to read and write but not execute.4 in binary is 100 so they only have permission to read the file.

Now we have an example in mind let’s have a look at what the bitwise operators are.

What are the Bitwise Operators?

There are 6 bitwise operators and each has a different purpose.

AND Operator &

In most programming languages we use a single ampersand (&) for the AND operator.

The best way to describe how the operators work is to use truth tables.

Using the AND operator with 2 numbers will leave you with the bits that match each number.

For example, if we take 7 and 6:

7 & 6 = 6
---------
1 1 = 1
1 1 = 1
1 0 = 0
Enter fullscreen mode Exit fullscreen mode

Here we can see that the first 2 rows have a matching bit but the last one doesn’t so the final result is 110 or 6 as a decimal.

Going back to our file permissions example. The AND operator can be used to test whether the user has permission to access something.

As the AND operator will give you only the bits back that match you can test to see if your user has those permissions.

READ_PERMISSION = 4
WRITE_PERMISSION = 2
EXECUTE_PERMISSION = 1

userPermissions = 6

if (userPermissions & READ_PERMISSION) == READ_PERMISSION:
    print("Can Read")
else:
    print("Cannot Read")

print(userPermissions & READ_PERMISSION)
Enter fullscreen mode Exit fullscreen mode

Output:

Can Read
4
0b100
Enter fullscreen mode Exit fullscreen mode

OR Operator |

The OR operator compares each bit and will give you the result if either one or the other is set to 1.

If we look at 5 and 6 using the OR operator and compare each bit we can see at least one of the rows the bit is set. The final result is therefore going to be 111 in binary or 7 in decimal.

5 | 6 = 7
---------
1 1 = 1
0 1 = 1
1 0 = 1
Enter fullscreen mode Exit fullscreen mode

You can use the OR operator to combine multiple flags together into a final result.

Here we put together the read-and-write permission using the OR operator and the final result we can see is 6.

READ_PERMISSION = 4
WRITE_PERMISSION = 2
EXECUTE_PERMISSION = 1

userPermissions = READ_PERMISSION | WRITE_PERMISSION

print(userPermissions)
print(bin(userPermissions))
Enter fullscreen mode Exit fullscreen mode

Output:

6
0b110
Enter fullscreen mode Exit fullscreen mode

You can even use the OR operator when assigning a value to an existing value.

Say we want to give the user the execute permission again we can add that using the OR assignment operator

READ_PERMISSION = 4
WRITE_PERMISSION = 2
EXECUTE_PERMISSION = 1

userPermissions = READ_PERMISSION | WRITE_PERMISSION

userPermissions |= EXECUTE_PERMISSION

print(userPermissions)
print(bin(userPermissions))
Enter fullscreen mode Exit fullscreen mode

Output:

7
0b111
Enter fullscreen mode Exit fullscreen mode

Exclusive OR (XOR) Operator (^)

This operator requires each bit to be the opposite of the other. When one is zero the other one has to be one.

If we look at the values 5 and 6 again. For the first bit, they both have the value 1 and therefore the exclusive or will return zero. The last 2 bits are both opposites and so that will return one. The final result is 011 which is 3 in decimal.

5 ^ 6 = 3
---------
1 1 = 0
0 1 = 1
1 0 = 1
Enter fullscreen mode Exit fullscreen mode

If we go back to our permissions example you can use the exclusive or remove permissions from the existing permission set.

Here we have a user who has the permissions to read and write. With the exclusive or assignment, we can remove the write permission from the user.

READ_PERMISSION = 4
WRITE_PERMISSION = 2
EXECUTE_PERMISSION = 1

userPermissions = READ_PERMISSION | WRITE_PERMISSION

userPermissions ^= WRITE_PERMISSION

print(userPermissions)
print(bin(userPermissions))
Enter fullscreen mode Exit fullscreen mode

Output:

4
0b100
Enter fullscreen mode Exit fullscreen mode

NOT Operator

The NOT operator ~ will give you the opposite value for each of the bits in your number.

Unlike the other operators, it doesn’t work on two values but just flips the value of each of the bits in the number.

~5
1 = 0
0 = 1
1 = 0
Enter fullscreen mode Exit fullscreen mode

If you do this example in python with a number you are going to get a negative number which might be a bit surprising at first.

As I mentioned in my last video on binary numbers the first bit of a number denotes the sign of the number with 0 being positive and 1 being negative.

When you use the NOT operator it flips all the bits in the number so the first bit becomes 1, therefore, making it negative.

As Python uses an 8-bit number to represent small numbers, 5 would be written like this.

0000 0101
Enter fullscreen mode Exit fullscreen mode

When this is reversed it becomes:

1111 1010
Enter fullscreen mode Exit fullscreen mode

Binary numbers use something called the two’s complement representation. All you need to understand is that in this case, an 8-bit negative number will start from -128.

We can then add each bit to this which gives us the final result of -6.

-128 + 2^6 + 2^5 + 2^4 + 2^3 + 2^1
Enter fullscreen mode Exit fullscreen mode

Left Shift and Right Shift

As the name suggests these operators shift all of the bits either to the left or to the right.

Again like the NOT operator this only works on a single number and the second number denotes the number of bits to shift by

The number 5 in a 8-bit binary is 0000 0101:

If we use the left shift operator with a 2, all of the bits are going to be shifted two bits to the left.

number = 5

print(number)
print(bin(number))

number = number << 2

print(number)
print(bin(number))
Enter fullscreen mode Exit fullscreen mode

Output:

5
0b101
20
0b10100
Enter fullscreen mode Exit fullscreen mode

The 2 leftmost bits are going to be discarded, although these are 0 in our case anyway. The other bits are going to shift left by 2. So this then becomes

0001 0100

If we convert it to decimal is going to give us 20.

If we then take this number and shift it right by 2 bits then it is going to become 5 again.

number = 5

print(number)
print(bin(number))

number = number << 2

print(number)
print(bin(number))

number = number >> 2

print(number)
print(bin(number))
Enter fullscreen mode Exit fullscreen mode

Output:

5
0b101
20
0b10100
5
0b101
Enter fullscreen mode Exit fullscreen mode

And that’s all there is to it. Bitwise operators can seem confusing at first but once you get used to thinking in binary they aren’t too scary.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .