The Number isSafeInteger() method

By

Learn how the JavaScript Number.isSafeInteger() method checks if a value is an integer within the safe range, between -2^53 and 2^53, where precision holds.

~~~

Number.isSafeInteger() checks if a value is an integer that JavaScript can represent exactly, without losing precision.

JavaScript stores all numbers as 64-bit floating point values. That format can represent every integer between -(2^53 - 1) and 2^53 - 1 exactly. Outside that range, integers start losing precision. Those two boundaries are exposed as Number.MIN_SAFE_INTEGER and Number.MAX_SAFE_INTEGER:

Number.MAX_SAFE_INTEGER //9007199254740991
Number.MIN_SAFE_INTEGER //-9007199254740991

What does “not safe” look like?

Above the safe range, JavaScript can’t tell some integers apart:

2 ** 53 === 2 ** 53 + 1 //true
9007199254740993 //9007199254740992

The literal 9007199254740993 can’t be stored exactly, so it silently becomes the closest representable value. No error, no warning. Number.isSafeInteger() is how you detect that danger zone.

A number might satisfy Number.isInteger() but not Number.isSafeInteger(). 2^53 is a whole number, so isInteger() says true. But it sits right at the edge where precision breaks, so it’s not safe:

Number.isInteger(2 ** 53) //true
Number.isSafeInteger(2 ** 53) //false

Anything over 2^53 - 1 and below -(2^53 - 1) is not safe:

Number.isSafeInteger(Math.pow(2, 53)) // false
Number.isSafeInteger(Math.pow(2, 53) - 1) // true
Number.isSafeInteger(Math.pow(2, 53) + 1) // false
Number.isSafeInteger(-Math.pow(2, 53)) // false
Number.isSafeInteger(-Math.pow(2, 53) - 1) // false
Number.isSafeInteger(-Math.pow(2, 53) + 1) // true

Like isInteger(), this method does not coerce its argument. Strings, Infinity and non-integer numbers all return false:

Number.isSafeInteger('19') //false
Number.isSafeInteger(3.5) //false
Number.isSafeInteger(Infinity) //false

When you’d actually use this

The classic trap is IDs coming from an API. Databases like Postgres use 64-bit integers for IDs, and Twitter-style snowflake IDs go well past 2^53. Parse one of those as a JavaScript number and you may get a different ID than the one the server sent.

JSON.parse() is where this usually happens:

JSON.parse('{"id": 9007199254740993}').id //9007199254740992

The JSON was fine. The number changed the moment it became a JavaScript number, and nothing warned you.

If you’re handling values that might be that large, check them with Number.isSafeInteger() before trusting arithmetic or comparisons on them. And the fix, when they’re too big, is to keep them as strings or use BigInt, which handles integers of any size.

~~~

Related posts about js: