JavaScript > JavaScript Fundamentals > Operators > Bitwise operators

Understanding JavaScript Bitwise Operators

This guide provides a comprehensive overview of JavaScript bitwise operators, explaining their functionality, use cases, and best practices. It covers operators like AND (&), OR (|), XOR (^), NOT (~), Left Shift (<<), Right Shift (>>), and Zero-fill Right Shift (>>>).

What are Bitwise Operators?

Bitwise operators perform operations on the individual bits of a number. They treat numbers as sequences of bits (0s and 1s) rather than decimal values. Understanding these operators is crucial for tasks such as working with low-level data structures, manipulating flags, and optimizing performance in certain scenarios. JavaScript converts numbers to 32-bit integers before performing bitwise operations, then converts the result back to a JavaScript number.

Bitwise AND (&)

The bitwise AND operator (&) returns 1 only if both corresponding bits are 1; otherwise, it returns 0. In the example, 0101 & 0011 results in 0001 because only the rightmost bit is 1 in both numbers.

let a = 5;  // Binary: 0101
let b = 3;  // Binary: 0011
let result = a & b; // Binary: 0001 (Decimal: 1)

console.log(result);

Bitwise OR (|)

The bitwise OR operator (|) returns 1 if at least one of the corresponding bits is 1; it returns 0 only if both bits are 0. In the example, 0101 | 0011 results in 0111 because any bit that is 1 in either number becomes 1 in the result.

let a = 5;  // Binary: 0101
let b = 3;  // Binary: 0011
let result = a | b; // Binary: 0111 (Decimal: 7)

console.log(result);

Bitwise XOR (^)

The bitwise XOR (exclusive OR) operator (^) returns 1 if the corresponding bits are different; it returns 0 if the bits are the same. In the example, 0101 ^ 0011 results in 0110 because the bits in the 2nd and 3rd positions (from the right) differ.

let a = 5;  // Binary: 0101
let b = 3;  // Binary: 0011
let result = a ^ b; // Binary: 0110 (Decimal: 6)

console.log(result);

Bitwise NOT (~)

The bitwise NOT operator (~) inverts all the bits of a number. It changes 0s to 1s and 1s to 0s. Because JavaScript uses 32-bit signed integers, the NOT operator also changes the sign of the number. The twos-complement representation is used for negative numbers. In the example, ~5 results in -6 because the twos complement conversion causes the value to change.

let a = 5;  // Binary: 0101
let result = ~a; // Binary: 11111111111111111111111111111010 (Decimal: -6)

console.log(result);

Left Shift (<<)

The left shift operator (<<) shifts all bits to the left by a specified number of positions. Zeros are added to the right. It effectively multiplies the number by 2 to the power of the shift amount. In the example, 5 << 2 shifts the bits of 5 two positions to the left, resulting in 20 (5 * 2^2).

let a = 5;  // Binary: 0101
let result = a << 2; // Binary: 010100 (Decimal: 20)

console.log(result);

Right Shift (>>)

The right shift operator (>>) shifts all bits to the right by a specified number of positions. The sign bit (the leftmost bit) is propagated to fill the vacated positions. It effectively divides the number by 2 to the power of the shift amount, discarding any remainder. In the example, 20 >> 2 shifts the bits of 20 two positions to the right, resulting in 5 (20 / 2^2).

let a = 20; // Binary: 10100
let result = a >> 2; // Binary: 00101 (Decimal: 5)

console.log(result);

Zero-fill Right Shift (>>>)

The zero-fill right shift operator (>>>) shifts all bits to the right by a specified number of positions. Unlike the right shift operator (>>), the vacated positions are filled with zeros, regardless of the sign bit. This operator always produces a non-negative result. In the example, -5 >>> 2 shifts the bits of -5 two positions to the right, filling the vacated positions with zeros, resulting in a large positive number.

let a = -5; // Binary: 11111111111111111111111111111011
let result = a >>> 2; // Binary: 001111111111111111111111111110 (Decimal: 1073741822)

console.log(result);

Real-Life Use Case: Working with Flags

Bitwise operators are commonly used to work with flags. A flag is a boolean value represented by a single bit. Multiple flags can be combined into a single number, with each bit representing a different flag. This allows for efficient storage and manipulation of boolean data. For example, representing read, write, and execute permissions using bits.

Real-Life Use Case: Graphics Programming

Bitwise operations were frequently used in graphics programming, particularly in older systems with limited memory and processing power. They could be used for tasks like masking colors, clipping regions, and performing certain image manipulations. While modern graphics APIs often abstract away these low-level details, understanding bitwise operations can still be helpful for optimizing certain graphics algorithms.

Best Practices

  • Clarity: Use bitwise operators sparingly and only when they provide a clear performance or memory benefit. Ensure the code is well-documented to explain the purpose of the bitwise operations.
  • Understand Limitations: Be aware of the limitations of 32-bit integers in JavaScript when using bitwise operators.
  • Use Parentheses: When combining multiple bitwise operators, use parentheses to ensure the desired order of operations.

Interview Tip

Be prepared to explain the functionality of each bitwise operator and provide examples of how they can be used in real-world scenarios. Demonstrate your understanding of bit manipulation and its potential benefits and drawbacks.

When to Use Them

Use bitwise operators when dealing with low-level data structures, manipulating flags, optimizing performance in specific scenarios (like older graphics systems or embedded systems), or when memory usage is a critical concern. Avoid them when the equivalent logical operators (&&, ||, !) provide a clearer and more readable solution.

Memory Footprint

Bitwise operators can be more memory-efficient when working with flags or sets of boolean values because multiple flags can be stored in a single number. This can be significant when dealing with a large number of flags or when memory is limited.

Alternatives

For simple boolean logic, use logical operators (&&, ||, !). For flag management, consider using an array of boolean values or an object with boolean properties if readability is a priority over memory efficiency. For more complex data structures, use appropriate data structures like sets or maps.

Pros

  • Performance: Bitwise operations can be faster than other methods for certain tasks, especially in low-level programming.
  • Memory Efficiency: They allow for compact representation of flags and sets of boolean values.

Cons

  • Readability: Bitwise operations can make code harder to understand if not used carefully and documented well.
  • Complexity: Incorrect usage can lead to unexpected results due to the way they manipulate bits.
  • Limited Applicability: They are not always the best choice for general-purpose programming tasks.

FAQ

  • Why do I get negative numbers when using the bitwise NOT operator?

    JavaScript uses 32-bit signed integers, and the bitwise NOT operator inverts all the bits, including the sign bit. This results in the twos-complement representation of a negative number.
  • Are bitwise operators used frequently in modern web development?

    Not as frequently as other JavaScript operators, but they can be useful in specific scenarios such as working with binary data, managing flags, or optimizing performance in certain algorithms. Their primary use case is more prevalent in lower-level languages or systems programming.
  • What is the difference between '>>' and '>>>' operators?

    The '>>' operator performs a signed right shift, preserving the sign of the number by filling vacated bits with the sign bit. The '>>>' operator performs an unsigned right shift, filling vacated bits with zeros regardless of the sign. Therefore, '>>>' always results in a non-negative number.