DEV Community

Andrew Lederman
Andrew Lederman

Posted on

Bitwise Operators in JavaScript

Binary numbers can be stored and represented with different amounts of bits. For instance, in JavaScript a single number in decimal is technically stored in memory as a 64-bit binary number. That's a lot of leading zero's...In many cases this is fine, but there are cases where we may want to conserve memory and use our alotted bits more intentionally.

Let's say we are storing the decimal number 10 as a byte of data.

00001010 = 10
Enter fullscreen mode Exit fullscreen mode

Technically we are using 8 bits to represent a single number value. If we think of each bit as a boolean value (on/off, true/false) instead of an actual number, a lot of real-world applications open up for us. Let's say we have a user who can have different levels of permission to access files. The user's permissions might be stored in an object like so:

{
   readPermission:true,
   writePermission:false,
   admin:false,
}
Enter fullscreen mode Exit fullscreen mode

This works fine, but another option is to assign the user a single decimal number, stored as binary, and use each individual bit to represent one of these flags instead! We would just need to be able to query and update each bit individually depending on our use case. Manipulating bits on this level requires bitwise operators.

Bitwise Operators

Bitwise operators take two values and compares them, outputting a new value from the computation. In JavaScript we generally run bitwise operations against two decimal numbers, which are then converted into binary values behind the scenes. It is also technically possible to work with binary numbers directly in JavaScript using Binary Literals which we will look at in a later post.

JavaScript stores decimal numbers in memory as 64-bit binary values. When a bitwise operator is used, each initial decimal value in the computation is converted into a 32-bit binary number in Two's Complement form, the computation is executed, then the result is converted back into 64-bits and finally returned as a decimal number.

Since it is possible to use individual bits to represent real world states (like user permissions), we can think of these operators as allowing us to query and determine state.

Let's take a look at the bitwise operators available to us and the actions they allow us to perform on binary numbers...

AND (&)

And Gate

The bitwise AND operator takes two binary numbers and combines them, comparing each set of digits in a column and outputs a 1 only if both inputs are 1. In all other cases the AND operator will return 0.

What can we do with this? You can think of the AND operator as allowing us to access or read specific bits within a binary word. Let's look at an example:

  10 & 7

  00001010 = 10
& 00000111 = 7
-----------
  00000010
Enter fullscreen mode Exit fullscreen mode

Any column with a 1 in the second number allows the original value to pass through or be read, while a 0 in the second number will turn off or mask that value. This is why the 1 in the 8's column is turned off in this example.

It's important to understand that with bitwise operators, we're not really concerned with the result in terms of base 10 values, we're not actually adding numbers. We're executing a computation to determine the state of a specific set of bits.

OR (|)

Or Gate

The OR operator returns 1 if either bit in the computation is 1. An easier way to think of this is that OR returns 0 only if both bits are 0. We can think about the purpose of the OR gate as allowing us to set/turn on specific bits in a binary word. Here's an example:

  10 | 7

  00001010 = 10
| 00000111 = 7
-----------
  00001111
Enter fullscreen mode Exit fullscreen mode

If you think about the second number (in this case 7) as being a set of instructions taken against the first number (10) we can see that by specifying 1's in specific columns, we can turn on or change the bits in the first number from 0 to 1, essentially turning on those bits. Again you could think of the user permissions example: by turning a single bit from off to on, a user could be upgraded to admin access for instance.

XOR (^)

Xor

The XOR or Exclusive-Or operator returns 1 only if both values are different, i.e. a 0 and a 1. Two ones or two zeros will return 0. If the AND operator can be used to read bits, and the OR operator can turn on bits, XOR allows us to toggle the state of specific bits.

   10 ^ 7

   00001010 
 ^ 00000111
------------
   00001101 
Enter fullscreen mode Exit fullscreen mode

Notice how anywhere there was a 1 in the second number, the original number's bit is toggled to be it's opposite.

NOT (~)

The NOT gate is the actual mechanism we can use in JavaScript to invert all of the values in order to compute the One's Complement of a binary word. NOT will invert every bit to be it's opposite.

00001010 = 10
11110101 = ~10
Enter fullscreen mode Exit fullscreen mode

Left-Shift (<<)

The Left-Shift operator will shift all the bits in the first number to the left by the right number amount of positions. Each value that gets pushed into new columns on the left will be dropped, and each new position that is created on the right will be filled in with a zero. Here's an example

8 << 2

00001000 = 8
00100000 = 8 << 2 = 32

Enter fullscreen mode Exit fullscreen mode

As you can see, the effect of a left-shift operation (x << y) is the same as multiplying x by 2 y times. One use of left-shift is that it allows us to perform carries in addition operations when the sum of a column is 2 or greater. Another application of this operator can be seen in working with RGB color values in Hexadecimal notation. We will see examples of these in later posts.

Right-Shift

There are two types of right-shift operations in JavaScript: Zero-Fill Right-Shift and Sign-Propagating Right-Shift. Zero-fill is used when dealing with unsigned numbers, and sign-propagating allows us to do the same right-shift operation with negative/signed numbers.

Zero-Fill Right-Shift (>>>)

Zero-fill right shift is used for positive or unsigned numbers. As each value is shifted right x number of positions, zeros are added to the left side of the number, and any values that get pushed further than the one column get dropped off.

10 >>> 2

00001010 = 10
00000010 = 10 >>> 2

Enter fullscreen mode Exit fullscreen mode

Sign-Propagating Right Shift (>>)

Sign-Propagating allows us to right-shift negative or signed numbers. As the number is shifted, the values on the left will be filled in with the value of the signed bit, thus preserving the sign of the original number.

   -10 >> 2

   10001010 = -10
>> 11100010 = -10 >> 2 = -3?

Enter fullscreen mode Exit fullscreen mode

Conclusion

Bitwise Operators allow us to manipulate bits on a granular level we cannot access with regular math operations. In the next post we will see some examples using Bitmasks and Hexadecimal notation...

Top comments (1)

Collapse
 
stuartyee profile image
Stuart Yee

Very nicely written!