Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Node.js Maximum Safe Floating-point Number

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.

like image 839
WoLfPwNeR Avatar asked Aug 29 '17 01:08

WoLfPwNeR


People also ask

What is Max safe integer in JS?

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.

What is the largest number that can be stored in JavaScript where does the overflow occur What does JavaScript do when the overflow happens?

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.

How big of numbers can JavaScript handle?

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.


1 Answers

To a precision of 1 decimal digits, the maximum number you can work with is 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 |
like image 100
giladbarnea Avatar answered Oct 15 '22 03:10

giladbarnea