Bitwise operators in JavaScript are fundamental to the language. These operators apply logical
operations to each bit of the operands. They consist of bitwise NOT (~
), bitwise AND
(&
), bitwise OR (|
), and bitwise XOR (^
), as well as their assignment variations &=
,
|=
, and ^=
. This post discusses what they are and the best practices around them.
The shift operators, <<
, >>
, and >>>
as well as their assignment variations, are out of the
scope of this post.
What Are Bitwise Operators?
How Are Integers Represented in Memory?
Integers in memory are represented by a certain number of bits. In JavaScript, integers, including BigInt, are represented using the method of two’s complement. For example, using 32 bits,
20
is represented as0000 0000 0000 0000 0000 0000 0001 0100
, and-20
is represented as1111 1111 1111 1111 1111 1111 1110 1100
.
What Do Bitwise Operators Do?
Bitwise operators apply logical operations to each bit of the binary representation of integers. Assuming that the operands are 32-bit integers:
- Bitwise NOT
~
: Apply the logical operation NOT to each bit of an integer, i.e., flip every bit of an integer. For example,~20
is-21
, whose binary form is1111 1111 1111 1111 1111 1111 1110 1011
. - Bitwise AND
&
: Apply the logical operation AND to each pair of bits of two integers. For example,20 & (-20)
is4
, whose binary form is0000 0000 0000 0000 0000 0000 0000 0100
. - Bitwise OR
|
: Apply the logical operation OR to each pair of bits of two integers. For example,20 | (-20)
is-4
, whose binary form is1111 1111 1111 1111 1111 1111 1111 1100
. - Bitwise XOR
^
: Apply the logical operation XOR to each pair of bits of two integers. For example,20 ^ (-20)
is-8
, whose binary form is1111 1111 1111 1111 1111 1111 1111 1000
.
The table below summarizes the binary bitwise operators:
Operator | Operands/Result | |
---|---|---|
Decimal | Binary | |
Operand | 20 | 0000 0000 0000 0000 0000 0000 0001 0100 |
Operand | -20 | 1111 1111 1111 1111 1111 1111 1110 1100 |
& | 4 | 0000 0000 0000 0000 0000 0000 0000 0100 |
| | -4 | 1111 1111 1111 1111 1111 1111 1111 1100 |
^ | -8 | 1111 1111 1111 1111 1111 1111 1111 1000 |
What Data Types Do JavaScript Bitwise Operators Work With?
In JavaScript, bitwise operators only apply to 32-bit integers and BigInt. Any operands with another type would be first cast to either of these.
For example, consider the expression ~1.1
. Instead of applying the bitwise NOT operator on the
binary representation of 1.1
(which is possible in other contexts), a JavaScript engine first
casts 1.1
to 1
, and then applies the bitwise NOT operator to 1
as a 32-bit integer, which
results in -2
.
For another example, consider the expression ~0xaa11223344
. 0xaa11223344
has exceeded the
maximum of 32-bit representation. In this case, a JavaScript engine calculates 0xaa11223344
modulo 232, which results in 0x11223344
, and then applies the bitwise NOT operator
to it. The result is -287454021
. In fact, we can verify that ~0x11223344
, ~0xa11223344
,
~0xaa11223344
, and ~0xaaa11223344
all result in the same number.
How Are Bitwise Operators Used?
Bitwise operators are used for:
- enabling advanced or specialized algorithms, such as those in cryptography, and
- facilitating integers representing multiple Boolean variables, i.e., bitmasking.
Details of bitwise operators in advanced or specialized algorithms are out of the scope of this post. They are more suitable to be discussed in the context of those algorithms. This post focuses on bitmasking.
Traditional Uses in C
While bitwise operators are not prevalently directly used in most JavaScript programs, they are often heavily used in a traditional programming language, such as C. The basic idea is that, instead of using multiple Boolean variables to represent a group of on/off statuses/options, it is more efficient to use bits in an integer. In this way, a 32-bit integer can represent up to 32 on/off statuses/options. This method is also referred to as bitmasking.
Example
For example, the POSIX C function open
opens a file. It takes an integer parameter oflags
,
which indicates the options that the caller wants to use, e.g.,
open("/path/to/file", O_WRONLY | O_CREAT);
Here, O_WRONLY
and O_CREAT
are two options, meaning write-only and creating the file if it
doesn’t exist, respectively. A possible implementation of open
defines O_WRONLY
as 1
(binary
01
) and O_CREAT
as 2
(binary 10
). In this way, the caller can choose to turn on:
- both options (
O_WRONLY | O_CREAT
) or, - only one of them (
O_WRONLY
orO_CREAT
), or - neither (
0
).
Effectively, the lowest 2 bits of oflags
are used as two Boolean variables, representing the
O_WRONLY
and O_CREAT
options, respectively.
Bitwise operators also help the caller manipulate oflags
:
oflags |= O_WRONLY
turns on write-only,oflags &= ~O_WRONLY
turns off write-only, andoflags ^ OWRONLY
flips write-only.
Inside the function body of open
, the presences of these two options can be retrieved via
determining whether oflags & O_WRONLY
and oflags & O_CREAT
are not zero.
Uses in JavaScript
In JavaScript, bitwise operators are used much less commonly than they are in C. Still, there are some occasions that they are used in this way.
One notable example is Node.compareDocumentPosition
. Its return value is a bitmask that
requires bitwise operators to retrieve the results. For example, consider the following HTML
document:
<!doctype html>
<html lang="en">
<body>
<p>This is a <em>paragraph</em>.</p>
</body>
</html>
You can open this HTML document, then run the following in the JavaScript console:
p = document.getElementsByTagName("p")[0];
em = document.getElementsByTagName("em")[0];
const relPosition = p.compareDocumentPosition(em);
console.log(relPosition);
The console prints 20
. This magic number means nothing until we unmask them using bitwise
operators:
console.log(`Does p contains em? ${(relPosition & Node.DOCUMENT_POSITION_CONTAINED_BY) !== 0}`); // true
console.log(`Is em after p? ${(relPosition & Node.DOCUMENT_POSITION_FOLLOWING) !== 0}`); // true
console.log(`Does em contains p? ${(relPosition & Node.DOCUMENT_POSITION_CONTAINING) !== 0}`); // false
When Should I Use Bitwise Operators?
As discussed in the How Are Bitwise Operators Used?, there are two main use cases of bitwise operators: advanced/specialized algorithms and bitmasking.
If we are implementing an advanced or specialized algorithm that requires bitwise operators, then the answer is clearly yes. But what about bitmasking? We need to first understand the most common and idiomatic alternative to bitmasking in JavaScript.
Representing a Group of Boolean Variables in JavaScript
In JavaScript, the most idiomatic way to represent a group of Boolean variables is using an object
with string keys and Boolean values. The C example above, if reimplemented in
JavaScript, its option oflags
would be an object. Borrowing the TypeScript syntax, its type looks
like:
interface OflagsType {
append: boolean;
creat: boolean;
// ...
}
The JavaScript Node.compareDocumentPosition
example, if reimplemented, should
return an object of type:
interface DocumentPosition {
documentPositionContainedBy: boolean;
documentPositionFollowing: boolean;
documentPositionContaining: boolean;
// ...
}
Pros and Cons of Bitmasking
The table compares bitmasking to representing a group of Boolean variables using an object:
Bitmasking | Object | |
---|---|---|
Intuitive | ✓ | |
Less error-prone | ✓ | |
Easy to use | ✓ | |
Flexible with non-Boolean | ✓ | |
Efficient | ✓ | |
Compatible | - | - |
Based on this table, we should always prefer using an object unless:
- The situation demands the use of bitmasking for compatibility reasons, such as interacting with
Node.compareDocumentPosition
, or - performance gain of using bitmasking is important to the program. Keep in mind that this performance gain is tiny for each occurrence, but there still can be situations in which the performance gain is important, .e.g, when the bitmask is read or written many times in a short period of time.
Therefore, we should normally use an object unless a special circumstance commands bitmasking.
A Decision Flow Chart
Conclusion
- Bitwise operators apply to each bit of operands in their binary form.
- Bitwise operators are used for:
- enabling advanced or special algorithms, and
- facilitating integers representing multiple Boolean variables, i.e., bitmasking.
- In JavaScript, we usually prefer objects consisting of Boolean values over bitmasking unless a special circumstance justifies otherwise.