You're overflowing the capacity of JavaScript's number type, see §8.5 of the spec for details. Those IDs will need to be strings.

IEEE-754 double-precision floating point (the kind of number JavaScript uses) can't precisely represent **all** numbers (of course). Famously, `0.1 + 0.2 == 0.3`

is false. That can affect whole numbers just like it affects fractional numbers; it starts once you get above 9,007,199,254,740,991 (`Number.MAX_SAFE_INTEGER`

).

Beyond `Number.MAX_SAFE_INTEGER + 1`

(`9007199254740992`

), the IEEE-754 floating-point format can no longer represent every consecutive integer. `9007199254740991 + 1`

is `9007199254740992`

, but `9007199254740992 + 1`

is **also** `9007199254740992`

because `9007199254740993`

cannot be represented in the format. The next that can be is `9007199254740994`

. Then `9007199254740995`

can't be, but `9007199254740996`

can.

The reason is we've run out of bits, so we no longer have a 1s bit; the lowest-order bit now represents multiples of 2. Eventually, if we keep going, we lose that bit and only work in multiples of 4. And so on.

Your values are *well* above that threshold, and so they get rounded to the nearest representable value.

If you're curious about the bits, here's what happens: An IEEE-754 binary double-precision floating-point number has a sign bit, 11 bits of exponent (which defines the overall scale of the number, as a power of 2 [because this is a binary format]), and 52 bits of significand (but the format is so clever it gets 53 bits of precision out of those 52 bits). How the exponent is used is complicated (described here), but in **very** vague terms, if we add one to the exponent, the value of the significand is doubled, since the exponent is used for powers of 2 (again, caveat there, it's not direct, there's cleverness in there).

So let's look at the value `9007199254740991`

(aka, `Number.MAX_SAFE_INTEGER`

):

+??????????????????????????????????????????????????????????????? sign bit
/ +???????+?????????????????????????????????????????????????????? exponent
/ / | +?????????????????????????????????????????????????+? significand
/ / | / |
0 10000110011 1111111111111111111111111111111111111111111111111111
= 9007199254740991 (Number.MAX_SAFE_INTEGER)

That exponent value, `10000110011`

, means that every time we add one to the significand, the number represented goes up by 1 (the whole number 1, we lost the ability to represent fractional numbers much earlier).

But now that significand is full. To go past that number, we have to increase the exponent, which means that if we add one to the significand, the value of the number represented goes up by 2, not 1 (because the exponent is applied to 2, the base of this binary floating point number):

+??????????????????????????????????????????????????????????????? sign bit
/ +???????+?????????????????????????????????????????????????????? exponent
/ / | +?????????????????????????????????????????????????+? significand
/ / | / |
0 10000110100 0000000000000000000000000000000000000000000000000000
= 9007199254740992 (Number.MAX_SAFE_INTEGER + 1)

Well, that's okay, because `9007199254740991 + 1`

is `9007199254740992`

anyway. But! We can't represent `9007199254740993`

. We've run out of bits. If we add just 1 to the significand, it adds 2 to the value:

+??????????????????????????????????????????????????????????????? sign bit
/ +???????+?????????????????????????????????????????????????????? exponent
/ / | +?????????????????????????????????????????????????+? significand
/ / | / |
0 10000110100 0000000000000000000000000000000000000000000000000001
= 9007199254740994 (Number.MAX_SAFE_INTEGER + 3)

The format just cannot represent odd numbers anymore as we increase the value, the exponent is too big.

Eventually, we run out of significand bits again and have to increase the exponent, so we end up only being able to represent multiples of 4. Then multiples of 8. Then multiples of 16. And so on.

0
Created by T.J. Crowder on 2020-03-10 00:32:32 +0000 UTC

Share