6

Number() function returns incorrect values on some arguments, like this:

Number('10000000712224641') returns 10000000712224640
Number('10000000544563531') returns 10000000544563532

I tested this on Firefox, Chome, IE and Node.js. Why is this happening?

Kyll
  • 7,036
  • 7
  • 41
  • 64
user3383116
  • 392
  • 1
  • 7
  • 2
    Betting on floating point precision. – Kyll Mar 01 '16 at 16:02
  • 1
    JavaScript (as well as every other programming language) have [well documented examples](http://stackoverflow.com/questions/1458633/how-to-deal-with-floating-point-number-precision-in-javascript) of problems like this. – arthurakay Mar 01 '16 at 16:04
  • 3
    @JuanMendes Aren't all numbers in JS stored the same way? – Kyll Mar 01 '16 at 16:04
  • 2
    @JuanMendes: It certainly looks like floating point precision to me. Overflow would lead to values becoming negative or otherwise *wildly* wrong. – Jon Skeet Mar 01 '16 at 16:06
  • @JonSkeet The first time John Skeet has said something to me... Reviewed my answer to explain that some integers above `Number.MAX_SAFE_INTEGER` can be expressed and yes, it is because of precision – Ruan Mendes Mar 01 '16 at 16:11

3 Answers3

10

JavaScript safely supports approximately up to 17 digits and all numbers, whether floats or integers, are expressed in 64-bit IEEE-754 binary floating.

Number.MAX_SAFE_INTEGER // 9007199254740991

When you get above that number, the trailing digits get rounded unless you have a power of 2 (or the addition of powers of two)

 Math.pow(2, 54)     // 18014398509481984 (not rounded)
 Math.pow(2, 54) + 1 // 18014398509481984 (rounded)
 Math.pow(2, 54) - 1 // 18014398509481984 (rounded)
 Math.pow(2,57) + Math.pow(2,52) // 148618787703226370 (not rounded)
 Math.pow(2, 57) + Math.pow(2, 52) + 1 // 148618787703226370 (rounded)
Ruan Mendes
  • 90,375
  • 31
  • 153
  • 217
  • 1
    It's not a matter of exact powers of 2... it's just that there are 53 bits of precision (assuming a normalized number). So any integers which only *need* 53 bits in the significand (and are within the right range of exponents) can be expressed exactly. – Jon Skeet Mar 01 '16 at 16:11
  • @JonSkeet I'm trying to understand how `Math.pow(2,54)` seems to work, and all the `Math.pow(2,54) -1 === Math.pow(2,54)` and `Math.pow(2,54) + 1 === Math.pow(2,54)` – Ruan Mendes Mar 01 '16 at 16:15
  • Think about what the binary representation of 2^54+1 is, when you've only got 53 significant bits to play with. The closest exactly-representable value to that is... 2^54. – Jon Skeet Mar 01 '16 at 16:16
  • @JonSkeet I'm still trying to follow what you mean, I said that powers of 2 can be expressed precisely even if larger than `Number.MAX_SAFE_INTEGER` and other numbers can't. How is that different from what you said? – Ruan Mendes Mar 01 '16 at 16:48
  • 1
    Consider 2^54 + 2^52. That's not a power of 2, which means according to your rule that it can't be expressed precisely - but actually it can, because it doesn't need more than 53 significant digits to represent it exactly. – Jon Skeet Mar 01 '16 at 16:48
  • @JonSkeet Does the answer look accurate now? – Ruan Mendes Mar 01 '16 at 16:49
7

Javascript uses 64-bit IEEE-754 binary floating point to store all numbers - like double in C# and Java, for example. There isn't a different type to store integers. (The actual implementation may use optimizations to avoid always performing arithmetic in this way, but from an end-user perspective the results will always be as if every number were treated as a 64-bit binary floating point value.)

That means only 52 bits are available to store the significand, with the other bits being used for the exponent and sign. With normalization, that means you can effectively store values with 53 significant bits of precision. That means beyond 253-1 (which is the value 9007199254740991 as quoted in other answers), the distance between "adjacent" numbers is more than 1, so you can't store all integers exactly.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • "There isn't a different type to store integers." - not at the engine level. V8, at least, has a dedicated `Smi` type for small integers (31 bits). So something like `x = 3 + 2` will not involve doubles internally. – georg Mar 01 '16 at 16:32
  • @georg: I was going by the only Javascript specification I could find, which was the old ECMA-262 one (ECMAscript rather than JS...) Additional pointers welcome :) Is that optimization ever visible to users, or can they always treat numbers as if they *were* always stored as 64-bit IEEE-754? – Jon Skeet Mar 01 '16 at 16:33
  • It's totally transparent, it's not possible for a JS program to distinguish between a SMI (immediate) and a double, heap-allocated number. And ECMAscript is (the official dialect of) JS, the 2015 spec is [here](http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-language-types-number-type). – georg Mar 01 '16 at 16:47
  • @georg: Good. Glad it's just an implementation detail :) – Jon Skeet Mar 01 '16 at 16:47
2

This is due to the fact that javascript supports a number of digits. The maximum safe integer possible is stored in a constant called MAX_SAFE_INTEGER which contains value 9007199254740991.

codetalker
  • 576
  • 1
  • 6
  • 21