In Node.js, is there a maximum safe floating-point number like Number.MAX_SAFE_INTEGER?
I had a little experiment to find out the (approximate) number I can use for subtracting 0.13 from it:
console.log(Math.floor(Number.MAX_SAFE_INTEGER));  // 9007199254740991
console.log(Math.floor(Number.MAX_SAFE_INTEGER)-0.13);  // 9007199254740991
console.log(Math.floor(Number.MAX_SAFE_INTEGER/2));  // 4503599627370495
console.log(Math.floor(Number.MAX_SAFE_INTEGER/2)-0.13);  // 4503599627370495
console.log(Math.floor(Number.MAX_SAFE_INTEGER/4));  // 2251799813685247
console.log(Math.floor(Number.MAX_SAFE_INTEGER/4)-0.13);  // 2251799813685246.8
console.log(Math.floor(Number.MAX_SAFE_INTEGER/64));  // 140737488355327
console.log(Math.floor(Number.MAX_SAFE_INTEGER/64)-0.13);  // 140737488355326.88
console.log(Math.floor(Number.MAX_SAFE_INTEGER/128));  // 70368744177663
console.log(Math.floor(Number.MAX_SAFE_INTEGER/128)-0.13);  // 70368744177662.87
My guess is that as the target precision increases, the maximum value decreases.
Description. The MAX_SAFE_INTEGER constant has a value of 9007199254740991 (9,007,199,254,740,991 or ~9 quadrillion). The reasoning behind that number is that JavaScript uses double-precision floating-point format numbers as specified in IEEE 754 and can only safely represent integers between -(253 – 1) and 253 – 1.
Note that the bitwise operators and shift operators operate on 32-bit integers, so in that case, the max safe integer is 231-1, or 2,147,483,647.
To represent integers larger than 2 to the 53rd power minus 1 in JavaScript, we can use the BigInt object to represent the values. It can be manipulated via normal operations like arithmetic operators — addition, subtraction, multiplication, division, remainder, and exponentiation.
562949953421311.To a precision of 2 decimal digits, it's 70368744177663.
Interestingly, the first number is equal to:
(Number.MAX_SAFE_INTEGER + 1) / 16 - 1
And the second number is equal to:
(Number.MAX_SAFE_INTEGER + 1) / 128 - 1
What we're looking for, is the maximum safe number to support a precision of d digits after the decimal point.
By "support" I mean "can reliably do basic arithmetic". 
For example, we know that Number.MAX_SAFE_INTEGER (aka 2**53-1) is not safe, because basic arithmetic is broken:
Number.MAX_SAFE_INTEGER - 0.1 === Number.MAX_SAFE_INTEGER
>>> true // unsafe
And we know that 0 is safe, since:
0 + 0.1 === 0
>>> false // safe
BTW, 0 is reliable as far as 1e-323 (including):
0 + 1e-323 === 0
>>> false // safe
0 + 1e-324 === 0
>>> true // unsafe
I binary-searched between 0 and Number.MAX_SAFE_INTEGER for the biggest number that answers that definition, and came up with these numbers.
Here's the code (pass any other number to findMaxSafeFloat() at the end of snippet)
/**Returns whether basic arithmetic breaks between n and n+1, to a precision of `digits` after the decimal point*/
function isUnsafe(n, digits) {
  // digits = 1 loops 10 times with 0.1 increases.
  // digits = 2 means 100 steps of 0.01, and so on.
  let prev = n;
  for (let i = 10 ** -digits; i < 1; i += 10 ** -digits) {
    if (n + i === prev) { // eg 10.2 === 10.1
      return true;
    }
    prev = n + i;
  }
  return false;
}
/**Binary search between 0 and Number.MAX_SAFE_INTEGER (2**53 - 1) for the biggest number that is safe to the `digits` level of precision.
 * digits=9 took ~30s, I wouldn't pass anything bigger.*/
function findMaxSafeFloat(digits, log = false) {
  let n = Number.MAX_SAFE_INTEGER;
  let lastSafe = 0;
  let lastUnsafe = undefined;
  while (true) {
    if (log) {
      console.table({
        '': {
          n,
          'Relative to Number.MAX_SAFE_INTEGER': `(MAX + 1) / ${(Number.MAX_SAFE_INTEGER + 1) / (n + 1)} - 1`,
          lastSafe,
          lastUnsafe,
          'lastUnsafe - lastSafe': lastUnsafe - lastSafe
        }
      });
    }
    if (isUnsafe(n, digits)) {
      lastUnsafe = n;
    } else { // safe
      if (lastSafe + 1 === n) { // Closed in as far as possible
        console.log(`\n\nMax safe number to a precision of ${digits} digits after the decimal point: ${n}\t((MAX + 1) / ${(Number.MAX_SAFE_INTEGER + 1) / (n + 1)} - 1)\n\n`);
        return n;
      } else {
        lastSafe = n;
      }
    }
    n = Math.round((lastSafe + lastUnsafe) / 2);
  }
}
console.log(findMaxSafeFloat(1));
An interesting thing I've found by lining up the safe numbers, is that the exponents don't step up in a consistent manner. Look at the table below; once in a while, the exponent increases (or decreases) by 4, and not 3. Not sure why.
| Precision | First UNsafe                | 2^53/x                   |
|-----------|-----------------------------|--------------------------|
| 1         | 5,629,499,534,21,312 = 2^49 | x = 16 = 2^4             |
| 2         | 703,687,441,77,664 = 2^46   | x = 128 = 2^7            |
| 3         | 87,960,930,22,208 = 2^43    | x = 1,024 = 2^10         |
| 4         | 5,497,558,13,888 = 2^39     | x = 16,384 = 2^14        |
| 5         | 68,719,476,736 = 2^36       | x = 131,072 = 2^17       |
| 6         | 8,589,934,592 = 2^33        | x = 1,048,576 = 2^20     |
| 7         | 536,870,912 = 2^29          | x = 16,777,216 = 2^24    |
| 8         | 67,108,864 = 2^26           | x = 134,217,728 = 2^27   |
| 9         | 8,388,608 = 2^23            | x = 1,073,741,824 = 2^30 |
                        If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With