Published on August 30, 2022

Bitwise operators

 8 minutes

If you've been programming for quite a while, chances are you have come across low level operations like bitwise operations. And perhaps if you're like me you didn't get them easily, at least not at first glance. So this time, we're gonna be poking around with them and unveiling their secrets. For the sake of this article we'll be using javascript as programming language, but feel free to use the one of your choice.

So first off, what are bitwise operations ?

As you know computers native language is binary, i.e zeros (0) and ones (1), in other words bits. All the operations performed in the binary realm and namely in a set of bits are called bitwise operations.

These are the different bitwise operations we can perform:

  • And
  • Or
  • Not
  • Xor
  • Left shift
  • Right shift

Bitwise And (&)

The bitwise AND (&) returns 1 when both operands are 1. e.g:

const a = 5; // 00000000000000000000000000000101 const b = 3; // 00000000000000000000000000000011 console.log(a & b); // 00000000000000000000000000000001

Bitwise Or (|)

The bitwise OR (|) returns 1 when either operand is 1. e.g:

const a = 5; // 00000000000000000000000000000101 const b = 3; // 00000000000000000000000000000011 console.log(a | b); // 00000000000000000000000000000111

Bitwise Not (~)

The bitwise NOT (~) returns 0 when the operand is 1 and vice versa. e.g:

const a = 5; // 00000000000000000000000000000101 console.log(~a); // 11111111111111111111111111111010

Bitwise Xor (^)

The Bitwise XOR (^) returns 1 only if one of the bits is zero. e.g:

const a = 5; // 00000000000000000000000000000101 const b = 3; // 00000000000000000000000000000011 console.log(a ^ b); // 11111111111111111111111111111110

Bitwise Shifts

Bitwise shifts shift all the bits of a binary object in one direction, left or right. The left operand is the value to be shifted and the right one means the number of positions to be shifted.

Bitwise Left Shift (<<)

The bitwise left shift simply adds n times zeros to the end of the binary object. A simple way of thinking about this is with this formula:

a << b = a * (2 to the power of b) const a = 5; // 00000000000000000000000000000101 console.log(a << 2);// 00000000000000000000000000010100

Bitwise Right Shift (>>)

Coversely, the bitwise right shift adds n times zeros to the beginning of the binary object. The following formula will make your life easier:

a >> b = a / (2 to the power of b) const a = 5; // 00000000000000000000000000000101 console.log(a << 2);// 00000000000000000000000000000001

You might be wondering, why are those equivalent binary numbers shown as (32) bits rather than as (64) bits. Well, generally javascript does a conversion to (32) bits i.e (4) bytes signed integers before performing bitwise operations, and a convertion back to (64) bits afterwards, since javascript naturally stores numbers as (64) bits floating point numbers.

You don't necessarily have to represent numbers in decimal format, you can use the binary syntax in javascript as well. In order to do so simply start your binary number with a leading zero(0), followed by a (b) in lowercase or uppercase, e.g :

const a = 0b1111 // 15 const b = 0B100110010 // 306 const c = 0b10 // 2

So in which case escenario do we use bitwise operators ?

Even though they have several applications, me personally have used them for:

  • Bit flags

This is a technique for assigning meaning to individual bits within a binary number. They're the most efficient way of representing something whose state is defined by several "yes or no" properties. If you have let's say 4 discrete permissions (read, write, execute, change policy), it's better to store this in 1 byte rather than waste 4.

In the figure (0) you can see how a simple byte carries different meanings in the context of a pop up dialog.

We could use bitwise logical operators on numbers to query and change these bit values. This is a quick reference that will be useful when dealing with bit operations:

And (&) Query the value of a bit Or (|) Sets a bit to true Xor (^) Toggles the value of a bit

So in the context of a pop up dialog we could have the following inmutable data:

export const DIALOG_BUTTON_ACCEPT = (1 << 0) // 0b1 // 1 // 0000 0001 export const DIALOG_BUTTON_CANCEL = (1 << 1) // 0b10 // 2 // 0000 0010 export const DIALOG_BORDER_SHADOW = (1 << 2) // 0b100 // 4 // 0000 0100

Using bit flags you know that a dialog with accept and cancel buttons will hold the value of (3), so code-wise say we receive a options prop on our dialog:

Dialog.Show({ options: DIALOG_BUTTON_ACCEPT | DIALOG_BUTTON_CANCEL | DIALOG_BORDER_SHADOW })

Given those options if you have a keen eye you might have noticed that options is holding the value of 0b111 (7). Now how do we know if the DIALOG_BUTTON_ACCEPT flag was set ? Simple, by using the query operator (&).

if ( ( options & DIALOG_BUTTON_ACCEPT ) == DIALOG_BUTTON_ACCEPT ) { // flag DIALOG_BUTTON_ACCEPT is set }

The above logic comparison is analogous to this:

0000 0111 & 0000 0001 __________ 0000 0001

Since the and operation returns 1 only if both operands are 1 it will end up returning exactly the flag we're checking for, therefore we can conclude the flag is set.

  • Some tricks

If you wanna leave trail of wisdom at your workplace, you can take into account the following tricks:

To check the parity of a x number you can try out doing

x & 1

This is akin to x % 2 in some programming languages so keep in mind that one.

Based on the fact that a negative number is the bitwise not(~) of the number plus one (1), let us first showcase how to convert a number to a relative figure.

But beforehand, we must understand how the binary addition is carried out at a low level. So lets take a look at figure (1).

figure a

From figure (1) we could infer that:

  • 1 + 0 or 0 + 1 is 1, therefore, the sum expression is a ^ b ( bitwise xor ).
  • The carry expression is a & b ( bitwise and ).

With that being said, we can define steps for performing the bitwise sum and come up with some code:

  1. Given two positive numbers a and b.
  2. Check that b is not equal to zero (0).
  • Find the carry value ( a & b ).
  • Get the sum result ( a ^ b ) and stores it in variable a.
  • Shift the carry to the left by 1 bit and store it in variable b.
  1. Go back to step 2.
  2. When b is zero return the sum.
/** * bitwise addition * @param {number} a - a binary number e.g 0b10 * @param {number} b - a binary number e.g 0b11 * @returns {number} the result value */ const bitwise_add = (a, b) => { if (b == 0) return a return bitwise_add( a ^ b, (a & b) << 1) }

So now, how do you get the negative number of 0b11 (3) ?

bitwise_add(~0b11, 0b1) // - 3

Conclusion

Taking time to poke around and try to understand how these bitwise operations work is a good endeavor that eventually pays off, as they will give you a myriad number of possibilities and ideas on how to solve some problems leveraging binary features some programming languages have.

Comments