For the following code snippet:
for (let i = 0; i < 60; i++) {
let count = 0
const num = Math.pow(2, i) - 1
let n = num
while (n > 0) {
n = n & (n - 1)
count++
}
console.log(`The binary representation of the number 2^${i}-1 contains ${count} '1', binary: ${num.toString(2)}`)
}
The output is:
if 0 <= i <= 31 then count equal to i which is correct
if 32 <= i then count equal to 1. This is clearly wrong
Why is count equal to 1 when i is [32, +∞]?
Why is the num.toString(2) always wrong when i is greater than 32 ?
example:
iis54The binary representation of the number 2^54-1 contains 1 '1', binary: 1000000000000000000000000000000000000000000000000000000
When using bitwise operators in JavaScript, the
... operands are converted to thirty-two-bit integers and expressed by a series of bits (zeros and ones). Numbers with more than 32 bits get their most significant bits discarded.
So when using the & operator, the values up to 2**32 - 1 will produce the correct result and values after it will produce unexpected results since the result of Math.pow(2, i) - 1 is chopped off during & operation.
While the specification defines the Number type as:
double-precision 64-bit format IEEE 754-2019 values
https://262.ecma-international.org/12.0/#sec-ecmascript-language-types-number-type
In reality, different js engines will generally treat integers differently.
For example V8, Chrome's JS engine will switch the underlying datatype as needed from more simple to more complex:
ECMAScript standardizes numbers as 64-bit floating-point values, also known as double precision floating-point or Float64. However, that doesn’t mean that JavaScript engines store numbers in Float64 representation all the time — doing so would be terribly inefficient! Engines can choose other internal representations, as long as the observable behavior matches Float64 exactly.
Most numbers in real-world JavaScript applications happen to be valid ECMAScript array indices, i.e. integer values in the range from 0 to 2³²−2.
JavaScript engines can choose an optimal in-memory representation for such numbers to optimize code that accesses array elements by index. For the processor to do the memory access operation, the array index must be available in two’s complement. Representing array indices as Float64 instead would be wasteful, as the engine would then have to convert back and forth between Float64 and two’s complement every time someone accesses an array element.
The 32-bit two’s complement representation is not just useful for array operations. In general, processors execute integer operations much faster than floating-point operations
That means that while you are using simple integers, V8 will store them as a two’s complement 32-bit number
That said, for the behaviour you are seeing in your code example, the answer by Salman A addresses that better than I can.