10

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.

WoLfPwNeR
  • 1,148
  • 4
  • 11
  • 27
  • 1
    You can always consult the spec; it would probably have taken less time than typing in this question. – Pointy Aug 29 '17 at 13:12
  • It would also be helpful if you'd explain what you mean by "safe". For integer values, it's fairly obvious, but for real numbers it's less so. – Pointy Aug 29 '17 at 13:18

4 Answers4

45

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 |
giladbarnea
  • 551
  • 4
  • 4
  • Wow!! How does this only have 3 upvotes. But either I misunderstood or there's an issue with the code.. `num = findMaxSafeFloat(1, true); num -= 0.1; num -=0.1;` gives `562949953421310.75`. Is it possible that this is arch-dependent? – Codebling Mar 26 '20 at 00:09
  • 1
    Also it's not that weird that there's a jump in the exponents, since we're testing decimal (base 10) floating points against a binary system. You can see how big the jumps will be by taking the log in base 2 of the different precisions (`trunc(ln2(0.001)) - trunc(ln2(0.0001)) == 4`) – Codebling Mar 26 '20 at 00:31
  • What kind of number is `5,629,499,534,21,312` (specifically the ,21,312) – Roko C. Buljan Mar 10 '21 at 23:16
  • This may be obvious to some, but just to confirm my understanding of how to read the table the first line says: if i have floating number with one digit post the decimal point the safe mathematical range will -562949953421311.0 to 562949953421311.0 ? and for 2 digits post decimal point range will be -703,687,441,77,663.00 to 703,687,441,77,664.00 – Laukik Apr 22 '23 at 06:56
2

Update: My understanding about this question is: Is there a maximum floating number, between 0 and that, all floating number operation can be safely delivered.

If that is the question, short answer is: No

Actually, there is no MAX_SAFE_FLOAT in all programming language (will be very glad if there is one). Number in programming language is stored by 0 or 1 bits. As long as there is a limit for the storage (32bits, 64bits etc), numbers that can be represented is finite. However, the number of floating-number is infinite.

Consider floating-number between 0 and 0.000000001, how many numbers need to be represented? Infinite. It's impossible to let computer store infinite possibility accurately. That's why there would never be MAX_SAFE_FLOAT.

p.s. In JavaScript, all numbers are 64bit double-precision floating-number. There is no floating-number v.s. interger-number in JavaScript.

shaochuancs
  • 15,342
  • 3
  • 54
  • 62
  • 2
    This is just wrong. The number of representable floating point numbers is definitely **not** "infinite". There are only 64 bits involved; how can it possibly be infinite? – Pointy Aug 29 '17 at 13:07
  • 1
    @Pointy I didn't mean representable floating-numbers, I mean floating numbers that needs to be accurately represented. – shaochuancs Aug 29 '17 at 13:08
  • 1
    Well for one thing your answer ignores the fact that JavaScript has `Number.MAX_VALUE` and `Number.MIN_VALUE`. There is most definitely a largest value that can be stored; the value of the mantissa can only be so large, as can the value of the exponent. – Pointy Aug 29 '17 at 13:10
  • @Pointy The number of floating-number that needs to be represented is definitely infinite -- there are infinite numbers between 0 and 0.00001, for example. For Number.MAX_VALUE, it's not safe. What OP asks is the largest "safe" floating number – shaochuancs Aug 29 '17 at 13:14
  • 1
    ?? There are infinite **real** numbers between 0 and 0.00001, but there are not an infinite number of 64-bit IEEE-754 binary floating-point numbers in that range. That's what the question is about. `Number.MAX_VALUE` is "safe" in that it is in fact the largest value that can be represented in IEEE-754 double-precision format. – Pointy Aug 29 '17 at 13:17
  • 1
    @Pointy I think we have a different understanding about the question. My understanding is: OP asks for the "safe range", between which all floating number can be accurately represented. – shaochuancs Aug 29 '17 at 13:19
  • There is no "safe range" then; there are always conceptual real number values between any two IEEE-754 numbers that cannot be represented. – Pointy Aug 29 '17 at 13:20
  • @Pointy yes, "safe range" never exist for real number. But I guess OP may not know this fact. – shaochuancs Aug 29 '17 at 13:29
  • there is a way. The number base must be a multiple of 2 exponential –  May 11 '22 at 19:59
0

The exponent decreases in a consistent manner, because it depends on the decimal part size (binary).

  • For a precision 1, the max decimal part size is 9 -> 1001 -> size = 4
  • For a precision 2, the max decimal part size is 99 -> 1100011 -> size = 7
  • For a precision 3, the max decimal part size is 999 -> 1111100111 -> size = 10
  • ...

As you can see, we have the same numbers than @giladbarnea found.

Based on that observation, we can easily write a simpliest solution to find all precisions that can be related to any integer.

const precisions = [...new Array(16).keys()]
  .reverse()
  .map(value => {
    const int = 53 - (value && BigInt('9'.repeat(value)).toString(2).length)

    return int > 0
      ? 2 ** int - 1
      : 0
  })

function getSafePrecision (value) {
  const abs = Math.abs(value)

  return 15 - precisions.findIndex(precision => precision >= abs)
}

Example:

getSafePrecision(2 ** 43 - 1) // 3
getSafePrecision(2 ** 43) // 2
Lcf.vs
  • 1,792
  • 1
  • 11
  • 15
-2

You're looking for Number.MAX_VALUE and Number.MIN_VALUE.

Number.MAX_VALUE is 1.7976931348623157e+308 and Number.MIN_VALUE is 5e-324.

Pointy
  • 405,095
  • 59
  • 585
  • 614
  • please note that OP asks the largest "safe" floating number. I think what he/she want is the safe range, like the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER – shaochuancs Aug 29 '17 at 13:18
  • @shaochuancs that does not really make sense, however, as the representation of real numbers is *never* "safe" in the sense that integer representation can be called "safe". – Pointy Aug 29 '17 at 13:19
  • Yes, I know it does not make much sense to ask for "safe" floating number. But please look at the 5 "subtracting 0.13" operation, it seems OP tried to find the largest safe number, which when "subtracting 0.13" still give the right result. He also mentioned "as the target precision increases, the maximum value decreases", it seems he guess when "subtracting 0.1312134543124321512", that largest safe number would be smaller. – shaochuancs Aug 29 '17 at 13:28