How JavaScript Bitwise Operations Work?
Backgrounds
It all starts with a question of the JavaScript bitwise operations shown below. What actually happens to the numbers when programs execute bitwise right shift operations? Why zero-fill right shift operations (triple greater than sign) applied to negative numbers would return a rather unintuitive result?
64 >> 2 // 16
-64 >> 2 // -16
64 >>> 2 // 16
-64 >>> 2 // 1073741807 Why?
In the subsequent sections, I will walk you through the whole process step by step to ensure you have a thorough understanding of all the details. 32-bit Signed Integer
Important points to note regarding operands of bitwise operations in JavaScript:Operands are treated as 32-bit signed integer (two's complement representation) while taking part in the operation.
// what you see 2 | -1 = -1 // what machine sees 00000000000000000000000000000010 | 11111111111111111111111111111111 = 11111111111111111111111111111111- The minimum and the maximum integers that are representable using a 32-bit signed number are -2147483648 to 2147483647.
- Operands that are out of the -2147483648 to 2147483647 range are actually converted (through truncating higher bits of the numbers) to ensure that they are in this range, before they take part in the operation.
Two's Complement
Two's Complement can be seen as a good way to represent negative numbers which has a lot of benefits, compared with representing them using the original binary format.
The general steps to get the two's complement representation of a negative number(in decimal) is as below:
- Get the positive part(in decimal, denoted by B) of the targeting negative number(in decimal, denoted by A)
- Calculate the binary format of B, and we get C
- Invert every bit of C, and we get D
- Add 1 to D, and we get E
And E is indeed the two's complement representation of the targeting negative number.
Below is a table of 32-bit signed integers in decimal and its corresponding two's complement representation.
| Decimal | Binary (two's complement) |
0 1101011 00100000 11110011 01011101 | |
---- | ---- |
2147483647 | 0 1111111 11111111 11111111 11111111 |
2147483646 | 0 1111111 11111111 11111111 11111110 |
2147483645 | 0 1111111 11111111 11111111 11111101 |
... | ... |
2 | 0 0000000 00000000 00000000 00000010 |
1 | 0 0000000 00000000 00000000 00000001 |
0 | 0 0000000 00000000 00000000 00000000 |
-1 | 1 1111111 11111111 11111111 11111111 |
-2 | 1 1111111 11111111 11111111 11111110 |
... | ... |
-2147483646 | 1 0000000 00000000 00000000 00000010 |
-2147483647 | 1 0000000 00000000 00000000 00000001 |
-2147483648 | 1 0000000 00000000 00000000 00000000 |
Bitwise Operators in JavaScript
There're 7 bitwise operators in JavaScript, as shown below.
| Operator | Name | Example |
~ | Bitwise NOT | ~a |
& | Bitwise AND | a & b |
| | Bitwise OR | a | b |
^ | Bitwise XOR | a ^ b |
<< | Left shift | a << b |
>> | Sign-propagating right shift | a >> b |
>>> | Zero-fill right shift | a >>> b |
And the keypoint of bitwise operation is that the decimal operand's two's complement representation is the "real" operand that will take part in the bitwise operation. You can input any qualified integers in the input area below to have a try and see what happens.
Bitwise NOT
Bitwise NOT operation is also known as binary One's complement.
Each bit of the binary value of the operand is inverted, that is to say, 1 to 0, and 0 to 1.
| a | 0 1101010 11101011 01111110 01101000 | |
| ~a | -1793818217 | 1 0010101 00010100 10000001 10010111 |
Bitwise AND
The bits of the same position of the binary values are compared using AND operation. If both bits are 1, then return 1, otherwise, return 0.
| a | 1 0010011 00100011 11000010 00100101 | |
| b | 1 0110111 01110110 11001001 10011101 | |
| a & b | -1826439163 | 1 0010011 00100010 11000000 00000101 |
Bitwise OR
The bits of the same position of the binary values are compared using OR operation. If both bits are 0, then return 0, otherwise, return 1.
| a | 0 1110010 01111000 10100001 10101100 | |
| b | 1 1001100 11011101 00001101 00010011 | |
| a | b | -16929345 | 1 1111110 11111101 10101101 10111111 |
Bitwise XOR
The bits of the same position of the binary values are compared using XOR operation. If both bits are different, then return 1, otherwise, return 0.
| a | 0 0000011 10100000 01010010 01111010 | |
| b | 1 1100110 10000100 00101001 01010011 | |
| a ^ b | -450594007 | 1 1100101 00100100 01111011 00101001 |
Bitwise Left Shift
The first operand's binary value is shifted left by X bits, where X is the second operand. The gaps are filled with 0, and the excess bits from the left are discarded.
| a | 0 1111010 10101011 11010100 00111111 | |
| b | 0 0000000 00000000 00000000 00011010 | |
| a << b | -67108864 | 1 1111100 00000000 00000000 00000000 |
Bitwise Sign-Propagating Right Shift
The first operand's binary value is shifted right by X bits, where X is the second operand. The excess bits from the right are discarded, and the leftmost bit keeps unchanged, and copies of the leftmost bit are shifted in from the left.
| a | 0 0101000 11110001 00001011 00000000 | |
| b | 0 0000000 00000000 00000000 00010100 | |
| a >> b | 655 | 0 0000000 00000000 00000010 10001111 |
Bitwise Zero-fill Right Shift
The first operand's binary value is shifted right by X bits, where X is the second operand. The excess bits from the right are discarded, and 0s are shifted in from the left.
| a | 1 0111111 00000000 01101010 01100110 | |
| b | 0 0000000 00000000 00000000 00010101 | |
| a >>> b | 1528 | 0 0000000 00000000 00000101 11111000 |
Back to the Questions At the Beginning
So now we know that why "-64 >>> 2" would result 1073741807. The reason is that -64 is a negative number, so in two's complement representation its sign bit is 1. ">>>" is zero-fill right shift operator, so after the shift, the sign bit is filled by 0, and then the new 32-bit number is interpreted as an unsigned 32-bit number, through which it was converted back to its decimal representation.
You can have a more realistic feel in the above "Bitwise Zero-fill Right Shift" section by filling the input field "a" with -64, and "b" with 0, and then add "b" one by one to see how the result would change.