Bitwise operators

What are Bitwise operators?

Bitwise operations operate on the bits (zeros and ones) of the binary representation of the operands, rather than on the decimal representation of the operands. In JavaScript, before executing the operation, the operands are converted to 32 bit binary numbers in two's complement representation, then the bitwise operation is performed on these bit sequences, and finally the result is converted back to standard JavaScript floating point values (standard decimal numbers). Bitwise operators operate on each individual bit in the bit sequence of the binary number.

Bitwise operations can be used in some situations to optimize algorithms, especially beneficial to limited memory devices. Because bitwise operations work on bit level, you may be able to avoid unnecessary complexity that may come with operators, numbers and methods on a "higher" level. This way some algorithms can be made faster or designed to consume less memory. However, in JavaScript all numbers are floating points which means that numbers need conversion back and forth which will probably (partially) undo possible efficiency gain obtained by using bitwise operators.


function isEven(intNum) {
  let x = Math.abs(Number.parseInt(intNum));
  return !(x&1); // bitwise solution.
//return (x%2 === 0); // regular operator/number solution
}

console.log(isEven(31576)); // logs: true
console.log(isEven(-423)); // logs: false

Explanation of the above example:
Any odd number (2n + 1) has a least significant bit 1 (110 = 00012). A bitwise AND between any odd number and 00012 results in 00012 (= 1). So x&1 is always true (1) when x is odd.

Each Bitwise operator explained

There are bitwise logical operators and bitwise shift operators. Next a table with all bitwise operators in JavaScript. The table also provides a truth table for the bitwise logical operators.

Table 1: Bitwise operators
Operator Name Description Truth table
a & b Bitwise AND Returns a 1 in each bit position for which the corresponding bits of both operands are 1.
  1100 
& 1010
  ====
  1000
a | b Bitwise OR Returns a 0 in each bit position for which the corresponding bits of both operands are 0.
  1100 
| 1010
  ====
  1110
a ^ b Bitwise XOR
(eXclusive OR)
Returns a 0 in each bit position for which the corresponding bits are the same.
Returns a 1 in each bit position for which the corresponding bits are different.
  1100 
^ 1010
  ====
  0110
~a Bitwise NOT Inverts the bits of its operand; 1 becomes a 0, and 0 becomes a 1.
 a 10 
~a 01
a << b Left shift Shifts a in binary representation b bits to the left, shifting in zeros from the right, discarding bits shifted off to the left.
a >> b Sign-propagating right shift Shifts a in binary representation b bits to the right, shifting in copies of the leftmost bit from the left, discarding bits shifted off to the right.
a >>> b Zero-fill right shift Shifts a in binary representation b bits to the right, shifting in zeros from the left, discarding bits shifted off to the right.

console.log(15&9); // logs: 9
  00001111 [15]
& 00001001 [9] 
  ========
  00001001 [9]   

console.log(15|9); // logs: 15
  00001111 [15]
| 00001001 [9] 
  ========
  00001111 [15] 

console.log(15^9); // logs: 6
  00001111 [15]
^ 00001001 [9] 
  ========
  00000110 [6]

console.log(~5); // logs: -6
~5 =
 00000101 = 5 →
 11111010 = ~5 (two's complement of 6)→
-00000110 = -6.

console.log(9<<2); // logs: 36
console.log(1<<31); // logs: -2147483648
console.log(1<<33); // logs: 2
       00001001 [9]
9<<2   00100100 [36]

       00000000000000000000000000000001 [1]
1<<31  10000000000000000000000000000000 [-2147483648]

       00000000000000000000000000000001 [1]
1<<33  00000000000000000000000000000010 [2] What happened here?  


console.log(9>>2); // logs: 2
console.log(-9>>2); // logs: -3
     00001001 [9]
9>>2 00000010 [2]

      11110111 [-9]
-9>>2 11111101 [-3] The sign is always preserved

console.log(9>>>2); // logs: 2
console.log(-9>>>2); // logs: 1073741821
      00001001 [9]
9>>>2 00000010 [2]

       11111111111111111111111111110111 [-9]
-9>>>2 00111111111111111111111111111101 [1073741821]

To prevent the engine from using the two's complement representation when converting back to a 64 bits floating point, you can use a zero fill right shift of zero (>>>0).


console.log(~5); // logs: -6
console.log((~5)>>>0); // logs: 4294967290
console.log(((~5)>>>0).toString(2)); // logs: "11111111111111111111111111111010"

Examples

Two more examples of simple algorithms using bitwise operators:

Swapping variables:


// One way to swap variables without bitwise operators:
let a = 3, b = 8;
let temp;

temp = a;
a = b;
b = temp;

console.log(`a = ${a}, b = ${b}`); // logs: "a = 8, b = 3"

// With bitwise operators:
let a = 3, b = 8;

a = a ^ b;
b = a ^ b; // b = (a ^ b) ^ b = a ^ (b ^ b) = a ^ 0 = a. → b = a
a = a ^ b; // a = (a ^ b) ^ a = b ^ (a ^ a) = b ^ 0 = b. → a = b

console.log(`a = ${a}, b = ${b}`); // logs: "a = 8, b = 3"

The above example uses two properties of the bitwise XOR: x ^ x = 0 and x ^ 0 = x. This procedure only swaps integers.

Testing if a number is a power of 2:


function isPowerOf2(intNum) {   
   let x = intNum;

   // Non-bitwise solution:
   // return (Math.log2(x) % 1 === 0);

   // Bitwise solution:
   return (x > 0 && (x & (x - 1)) === 0);
}

console.log(isPowerOf2(1024)); // logs: true
console.log(isPowerOf2(34)); // logs: false
console.log(isPowerOf2(-32)); // logs: false

Explanation of the above example:
Powers of two have only one set bit (only one bit 1) in binary.
Powers of two minus 1 have all bits set, except the bit that represents the next number (which is a power of 2). A bitwise AND of a power of two and that power of two minus 1 must equal 0.
This procedure only works correctly with integers.