Number.isSafeInteger()
Tests whether a value is an integer that IEEE-754 doubles can represent exactly.
Number.isSafeInteger(testValue)- Added in JavaScript ES2015
Parameters
| Parameter | Type | Description |
|---|---|---|
$testValue | any | The value to be tested for being a safe integer. |
Return value
boolean`true` if the value is a number that is a safe integer, `false` otherwise.
Safe integers are the whole numbers from -(253 - 1) to 253 - 1, that is ±9,007,199,254,740,991. Inside that range every integer has its own distinct double. Outside it, two different integers can collapse onto the same value — 2**53 === 2**53 + 1 is true — and arithmetic silently stops being reliable.
When to reach for it
The classic case is identifiers from another system. A 64-bit database id, a Twitter-style snowflake, or a nanosecond timestamp can exceed 253, and JSON.parse will happily round it to a neighbouring value with no error. Checking the parsed number with Number.isSafeInteger() is how you find out before the wrong record is fetched. The fix is to keep such ids as strings, or to use BigInt, which has no upper bound.
How it differs from isInteger()
Number.isInteger() only asks whether the fractional part is zero, so it says true for 1e100. Number.isSafeInteger() adds the magnitude test. Note that both look only at the value in front of them: if precision was already lost during parsing, a rounded result may still land inside the safe range and pass.
Example
When to use it
- Detect a 64-bit database id that lost precision in JSON
- Decide whether a computed total needs BigInt
- Validate a timestamp in nanoseconds before arithmetic
Frequently asked questions
What is the largest safe integer in JavaScript?
Number.MAX_SAFE_INTEGER, which is 9007199254740991 (2^53 - 1). Above it, distinct integers can share the same double and arithmetic stops being exact.
How do I handle IDs larger than MAX_SAFE_INTEGER?
Keep them as strings through JSON and convert with BigInt only when you need arithmetic. Once JSON.parse has produced a Number, the precision is already gone.
Why does Number.isSafeInteger(2 ** 53) return false?
Because 2^53 and 2^53 + 1 round to the same double, so the representation is ambiguous even though the value itself is storable.
More examples
Catching an id mangled by JSON.parse
The value was already corrupted during parsing; isSafeInteger() flags it, and comparing the stringified number with the source text confirms the loss.
const payload = '{"id": 9007199254740993}';
const parsed = JSON.parse(payload);
console.log(parsed.id); // 9007199254740992 — off by one
console.log(Number.isSafeInteger(parsed.id)); // false
// Compare the round-trip against the original text
console.log(String(parsed.id) === '9007199254740993'); // falseChoosing BigInt when the range demands it
Ordinary addition at the boundary produces 9007199254740992 for both +1 and +2; BigInt keeps every digit.
function addIds(a, b) {
if (Number.isSafeInteger(a) && Number.isSafeInteger(b)) {
return a + b;
}
return BigInt(a) + BigInt(b);
}
console.log(addIds(2, 3)); // 5
console.log(addIds(9007199254740991, 1)); // 9007199254740992 — a Number: both inputs were safe
console.log(addIds(2 ** 53, 2)); // 9007199254740994n — an unsafe input routes to BigInt
console.log(9007199254740991 + 2); // 9007199254740992 — wrong
console.log(BigInt('9007199254740991') + 2n); // 9007199254740993nisInteger versus isSafeInteger
Every safe integer is an integer, but the reverse fails for any whole-valued double beyond 2^53 where exactness is gone.
const values = [42, -42, 2 ** 53, 1e21, 4.0, NaN];
for (const v of values) {
console.log(v, Number.isInteger(v), Number.isSafeInteger(v));
}
// 42 true true
// -42 true true
// 9007199254740992 true false
// 1e+21 true false
// 4 true true
// NaN false false
Discussion