Safe integers in JavaScript

[2013-10-01] numbers, dev, javascript, jsint, jslang
(Ad, please don’t block)
Update 2014-02-08: Follow-up blog post “What are integers in JavaScript?

JavaScript can only safely represent integers i in the range −253 < i < 253. This blog post examines why that is and what “safely represent” means. It is based on an email by Mark S. Miller to the es-discuss mailing list.

Safe integers

The idea of a safe integer is about how mathematical integers are represented in JavaScript.

In the range (−253, 253) (excluding the lower and upper bounds), JavaScript integers are safe: there is a one-to-one mapping between mathematical integers and their representations in JavaScript.

Beyond this range, JavaScript integers are unsafe: two or more mathematical integers are represented as the same JavaScript integer. For example, starting at 253, JavaScript can only represent every second mathematical integer.

    > Math.pow(2, 53)
    9007199254740992
    > Math.pow(2, 53)+1
    9007199254740992
    > Math.pow(2, 53)+2
    9007199254740994
    > Math.pow(2, 53)+3
    9007199254740996
    > Math.pow(2, 53)+4
    9007199254740996
    > Math.pow(2, 53)+5
    9007199254740996
    > Math.pow(2, 53)+6
    9007199254740998
Why is that? Simplifyingly, a number in JavaScript is represented as
mantissa × 2exponent
Mantissa and exponent give you 53 bit integers (consult [1] for the details). But you can represent higher integers by using higher exponents. For example, the 53 bit range of integers with an exponent incremented by one (a multiplication by two) becomes a 54 bit range. However, now only every second integer can be represented, as we have seen above.

Similarly, starting at 254, JavaScript can only represent every fourth mathematical integer (and so on).

    > Math.pow(2, 54)
    18014398509481984
    > Math.pow(2, 54)+1
    18014398509481984
    > Math.pow(2, 54)+2
    18014398509481984
    > Math.pow(2, 54)+3
    18014398509481988
    > Math.pow(2, 54)+4
    18014398509481988
Therefore, a safe JavaScript integer is one that unambiguously represents a single mathematical integer.

Definitions in ECMAScript 6

ECMAScript 6 will probably provide the following constants:
    Number.MAX_SAFE_INTEGER = Math.pow(2, 53)-1;
    Number.MIN_SAFE_INTEGER = -Number.MAX_SAFE_INTEGER;
It will also provide a function for determining whether an integer is safe:
    Number.isSafeInteger = function (n) {
        return (typeof n === 'number' &&
            Math.round(n) === n &&
            Number.MIN_SAFE_INTEGER <= n &&
            n <= Number.MAX_SAFE_INTEGER);
    }
For a given value n, this function first checks whether n is a number and whether it is an integer. If both checks succeed, n is safe if it is greater or equal to MIN_SAFE_INTEGER and less or equal to MAX_SAFE_INTEGER.

Safe results of arithmetic computations

How can we make sure that results of arithmetic computations are correct? For example, the following result is clearly not correct.
    > 9007199254740990 + 3
    9007199254740992
We have two safe operands, but an unsafe result:
    > Number.isSafeInteger(9007199254740990)
    true
    > Number.isSafeInteger(3)
    true
    > Number.isSafeInteger(9007199254740992)
    false
The following result is also incorrect:
    > 9007199254740995 - 10
    9007199254740986
This time, the result is safe, but one of the operands isn’t:
    > Number.isSafeInteger(9007199254740995)
    false
    > Number.isSafeInteger(10)
    true
    > Number.isSafeInteger(9007199254740986)
    true
Therefore, the result of applying an integer operator op is only guaranteed to be correct if all operands and the result are safe. More formally:
    isSafeInteger(a) && isSafeInteger(b) && isSafeInteger(a op b)
implies that a op b is a correct result.

Recommended reading

  1. How numbers are encoded in JavaScript
This blog post is part of a series of posts on JavaScript’s numbers.