JavaScript Bitwise Operators with Non-Integers
In JavaScript, bitwise operators do not operate directly on floating-point numbers, strings, or complex objects. Instead, the JavaScript engine implicitly coerces any non-integer operand into a 32-bit integer before performing the operation. This article explains the internal conversion process, demonstrates how different non-integer data types behave under bitwise operations, and highlights common edge cases.
The Conversion Pipeline: ToInt32 and ToUint32
JavaScript numbers are stored as 64-bit floating-point values (IEEE
754). When a bitwise operator is applied, the engine performs the
internal ToInt32 abstract operation on the operands (or
ToUint32 in the case of the zero-fill right shift operator,
>>>).
This conversion involves two steps: 1. Type Coercion
(ToNumber): The operand is converted to a numeric
primitive. 2. Integer Truncation
(ToInt32): The numeric value is clamped modulo
\(2^{32}\) into a signed 32-bit integer
ranging from -2,147,483,648 to
2,147,483,647.
Behavior with Floating-Point Numbers
When applied to floating-point numbers, bitwise operators discard the fractional portion entirely. The operation truncates toward zero rather than rounding to the nearest whole integer.
// Positive floats truncate downward
console.log(5.9 | 0); // 5
// Negative floats truncate upward
console.log(-5.9 | 0); // -5
// Bitwise NOT with decimals
console.log(~3.7); // -4 (same as ~3)Because of this behavior, idioms like x | 0 or
~~x are frequently used in JavaScript as shorthand to
truncate floating-point numbers into 32-bit integers.
Behavior with Special Numeric Values
Special floating-point values that cannot be represented as standard
numbers are mapped directly to 0:
NaNconverts to0Infinityconverts to0-Infinityconverts to0-0converts to+0
console.log(NaN | 0); // 0
console.log(Infinity >> 2); // 0
console.log(~(-Infinity)); // -1 (since ~0 is -1)Behavior with Non-Numeric Data Types
Non-numeric types first undergo standard JavaScript type coercion into numbers before the bitwise operation takes place.
Booleans
Booleans are converted to 1 (true) or
0 (false):
console.log(true << 2); // 4 (1 << 2)
console.log(false | 8); // 8 (0 | 8)Strings
Numeric strings are parsed into numbers and then truncated.
Non-numeric strings evaluate to NaN, which becomes
0:
console.log("42.8" | 0); // 42
console.log("10" ^ 3); // 9
console.log("hello" & 255); // 0 ("hello" becomes NaN, then 0)
console.log("" | 0); // 0 (empty string becomes 0)Null and Undefined
nullconverts to0.undefinedconverts toNaN, which subsequently becomes0.
console.log(null | 0); // 0
console.log(undefined | 0); // 0Objects and Arrays
Objects and arrays are converted using their primitive conversion
methods (valueOf or toString): *
[] becomes "", which converts to
0. * [5.5] becomes "5.5", which
converts to 5. * Plain objects {} convert to
"[object Object]", which becomes NaN and then
0.
console.log([] | 0); // 0
console.log([9.9] | 0); // 9
console.log({} | 0); // 0Integer Overflow Beyond 32 Bits
Numbers exceeding the 32-bit signed integer range wrap around using two’s complement representation:
// 2^31 is 2147483648, which overflows a signed 32-bit integer
console.log(2147483648 | 0); // -2147483648
// Numbers larger than 2^32 wrap around completely
console.log(4294967296 | 0); // 0 (4294967296 is 2^32)BigInt Exception
The BigInt type does not follow the ToInt32
truncation rules. Bitwise operations on BigInt values
support arbitrary precision, but standard numbers and
BigInt values cannot be mixed. Attempting to apply a
bitwise operator between a BigInt and any other type
results in a TypeError.
console.log(5n & 3n); // 1n
// console.log(5n & 3); // TypeError: Cannot mix BigInt and other types