96

I have the following code:

var str = "0x4000000000000000";   //4611686018427387904 decimal
var val = parseInt(str);
alert(val);

I get this value: "4611686018427388000", which is 0x4000000000000060

I was wondering if JavaScript is mishandling 64-bit integers or am I doing something wrong?

iammilind
  • 68,093
  • 33
  • 169
  • 336
ahmd0
  • 16,633
  • 33
  • 137
  • 233
  • Related: [What is JavaScript's Max Int? What's the highest Integer value a Number can go to without losing precision?](http://stackoverflow.com/questions/307179) – hippietrail Dec 24 '12 at 12:28
  • Possible duplicate of [What is JavaScript's highest integer value that a number can go to without losing precision?](https://stackoverflow.com/questions/307179/what-is-javascripts-highest-integer-value-that-a-number-can-go-to-without-losin) – iammilind Aug 10 '18 at 05:38

3 Answers3

103

JavaScript represents numbers using IEEE-754 double-precision (64 bit) format. As I understand it this gives you 53 bits precision, or fifteen to sixteen decimal digits. Your number has more digits than JavaScript can cope with, so you end up with an approximation.

This isn't really "mishandling" as such, but obviously it isn't very helpful if you need full precision on large numbers. There are a few JS libraries around that can handle larger numbers, e.g., BigNumber and Int64.

nnnnnn
  • 147,572
  • 30
  • 200
  • 241
  • 1
    I think I was editing one solution into my answer as you were typing your comment: you can use a library that will handle it for you. (I've never needed to handle such big numbers in JS except for occasional use as record IDs in which case I didn't need to do maths operations on them so keeping them as strings worked fine.) – nnnnnn Mar 10 '12 at 03:24
  • 2
    Closure's goog.math.Long may also be helpful: http://docs.closure-library.googlecode.com/git/class_goog_math_Long.html – Jeremy Condit Mar 15 '13 at 06:26
  • 50
    One should maybe add that bit-level operations are limited to 32 bit IIUC. – sellibitze Mar 18 '14 at 23:23
  • 2
    goog.math.Long documentation has moved: http://google.github.io/closure-library/api/class_goog_math_Long.html – benizi May 24 '15 at 03:54
  • 5
    (Comment by [@Michaelangelo](http://stackoverflow.com/users/1339447/michaelangelo)) Unfortunately, the [ECMAScript 2015 Specifications](http://www.ecma-international.org/ecma-262/6.0/) (version 6) has no official support for `UInt64`; while Mozilla has added support for [UInt64](https://developer.mozilla.org/en-US/docs/Mozilla/js-ctypes/js-ctypes_reference/UInt64) -- this is non-standard. WebGL has similar needs but unfortunately there is no `Uint64Array` either, only [Uint32Array](https://www.khronos.org/registry/typedarray/specs/latest/#7). – falsarella Dec 12 '15 at 18:44
  • 3
    goog.math.Long documentation has moved again: https://google.github.io/closure-library/api/goog.math.Long.html (Thanks, @Pacerier) – benizi Aug 07 '17 at 03:41
  • "WebGL has similar needs but unfortunately there is no Uint64Array either, only Uint32Array." But if you want to ship a custom game for example, you could probably fork Chrome and add that. Does anyone know of anyone doing stuff like this? – trusktr Jan 19 '18 at 21:18
  • A Long class for representing a 64 bit two's-complement integer value derived from the Closure Library for stand-alone use and extended with unsigned support. https://github.com/dcodeIO/long.js – Peter Zhao Feb 28 '18 at 08:03
  • Also, there is massive performance (85~95%) loss when using a library for 64-bit arithmetic. This is a real drag, because 32-bit arithmetic is much more efficient (especially when `Math.imul` is relevant). – bryc Apr 07 '18 at 22:19
  • Re "This isn't really "mishandling" as such": I would call this particular issue mishandling though, as a double is perfectly capable of representing 0x4000000000000000 as that's a power of two and well within double's exponent range. – oisyn Feb 05 '19 at 12:14
  • Btw, I think ES10 or something will have Big ints. – christo8989 Feb 27 '19 at 14:15
  • @sellibitze - I was looking at this post coz of bit level operation. What should one do if we need around 50+ bits to store different bitwise information? – Raj Pawan Gumdal Apr 08 '21 at 17:22
  • It's actually 54-bit precision, one bit for the sign, so the range is -2^53 to +2^53 (minus 1) – Javier Rey Nov 13 '21 at 13:00
  • FWIW, BigInt and its many methods is now standard in ES 11, and all major browsers. – SilverbackNet Dec 04 '21 at 03:30
18

Chromium version 57 and later natively supports arbitrary-precision integers. This is called BigInt and is being worked on for other browsers as well. It is dramatically faster than JavaScript implementations.

Shnatsel
  • 4,008
  • 1
  • 24
  • 25
  • Also supported by Opera 54+ and Node.js. Firefox 65+ supports it if `javascript.options.bigint` flag is enabled. – David Callanan Mar 21 '19 at 09:02
  • It is not always faster. compare this `console.time("go");for (var i=0;i<10000000;++i) {} console.timeEnd("go");` vs 64bit numbers `console.time("go");for (var i=0n;i<10000000n;++i) {} console.timeEnd("go");` – Cibo FATA8 Dec 08 '19 at 17:09
  • 4
    @CiboFATA8: He is talking about BigInt as a native browser component vs BigInt implemented in JavaScript. You are comparing js Numbers, which are floats with about 53 bits of precision (not 64) with native browser BigInt (also not 64 bit). – hippietrail May 17 '20 at 02:24
10

I.e., V8 JavaScript is a Smalltalk derived engine. (1980s - present) Lisp and Smalltalk engines support multi-precision arithmetic using <LargeInteger> sometimes called <BigInt>. Spoiler, the Dart team at Google is largely a bunch of ex-Smalltalkers bringing their experience together into the JS space.

These types of numbers have unlimited precision and are typically used as building blocks to provide <Rational:Fraction> objects whose numerator and denominator can be any type of number including a <BigInt>. With that one can represent real-numbers, imaginary-numbers, and do so with perfect precision on irrational numbers like (1/3).

Note: I'm a long time implementer and developer of Smalltalk, JS and other languages and their engines and frameworks.

If done appropriately <BigInt> for multi-precision arithmetic as a standard feature of JavaScript will open the door to a tremendous suite of operations, including native efficient cryptography (which is easy to do with multi-precision numbers).

For example, in one of my 1998 smalltalk engines, on a 2.3GHz cpu I just ran:

[10000 factorial] millisecondsToRun => 59ms
10000 factorial asString size => 35660 digits

[20000 factorial] millisecondsToRun => 271ms
20000 factorial asString size => 77338 digits

Defined as: (illustrating <BigInt> multi-precision in action)

factorial

   "Return the factorial of <self>."

   | factorial n |

    (n := self truncate) < 0 ifTrue: [^'negative factorial' throw].
    factorial := 1.
    2 to: n do:
    [:i |
        factorial := factorial * i.
    ].
   ^factorial

The V8 engine from Lars Bak (a contemporary of mine) work is derived from Animorphic Smalltalk from David Ungar's SELF work derived from Smalltalk-80, and subsequently evolved into the JVM, and redone by Lars for Mobile emerging later as the V8 engine foundation.

I mention that because both Animorphic Smalltalk and QKS Smalltalk support type-annotations which enable the engine and tools to reason about code in a similar way to that which TypeScript has attempted for JavaScript.

That annotation hinting and its use by the language, tools, and runtime engines offers the capability to support multi-methods (rather than double dispatch) needed to support multi-precision arithmetic type-promotion and coercion rules properly.

Which, in turn, is key to supporting 8/16/32/64 int/uints and many other numeric types in a coherent framework.

Multi-method <Magnitude|Number|UInt64> examples from QKS Smalltalk 1998

Integer + <Integer> anObject

   "Handle any integer combined with any integer which should normalize
    away any combination of <Boolean|nil>."
   ^self asInteger + anObject asInteger

-- multi-method examples --

Integer + <Number> anObject

   "In our generic form, we normalize the receiver in case we are a
    <Boolean> or <nil>."
   ^self asInteger + anObject

-- FFI JIT and Marshaling to/from <UInt64>

UInt64 ffiMarshallFromFFV
   |flags| := __ffiFlags(). 
   |stackRetrieveLoc| := __ffiVoidRef().
    ""stdout.printf('`n%s [%x]@[%x] <%s>',thisMethod,flags,stackRetrieveLoc, __ffiIndirections()).
    if (flags & kFFI_isOutArg) [
        "" We should handle [Out],*,DIM[] cases here
        "" -----------------------------------------
        "" Is this a callout-ret-val or a callback-arg-val
        "" Is this a UInt64-by-ref or a UInt64-by-val
        "" Is this an [Out] or [InOut] callback-arg-val that needs 
        ""   to be updated when the callback returns, if so allocate callback
        ""   block to invoke for doing this on return, register it as a cleanup hook.
    ].
   ^(stackRetrieveLoc.uint32At(4) << 32) | stackRetrieveLoc.uint32At(0).

-- <Fraction> --

Fraction compareWith: <Real> aRealValue

   "Compare the receiver with the argument and return a result of 0
    if the received <self> is equal, -1 if less than, or 1 if
    greater than the argument <anObject>."
   ^(numerator * aRealValue denominator) compareWith:
            (denominator * aRealValue numerator)

Fraction compareWith: <Float> aRealValue

   "Compare the receiver with the argument and return a result of 0
    if the received <self> is equal, -1 if less than, or 1 if
    greater than the argument <anObject>."
   ^self asFloat compareWith: aRealValue

-- <Float> --

Float GetIntegralExpAndMantissaForBase(<[out]> mantissa, <const> radix, <const> mantissa_precision)
   |exp2| := GetRadix2ExpAndMantissa(&mantissa).
    if(radix = 2) ^exp2.

   |exp_scale| := 2.0.log(radix).
   |exp_radix| := exp2 * exp_scale.
   |exponent| := exp_radix".truncate".asInteger.
    if ((|exp_delta| := exp_radix - exponent) != 0) [
       |radix_exp_scale_factor| := (radix.asFloat ^^ exp_delta).asFraction.
        "" Limit it to the approximate precision of a floating point number
        if ((|scale_limit| := 53 - mantissa.highBit - radix.highBit) > 0) [
            "" Compute the scaling factor required to preserve a reasonable
            "" number of precision digits affected by the exponent scaling 
            "" roundoff losses. I.e., force mantissa to roughly 52 bits
            "" minus one radix decimal place.
           |mantissa_scale| := (scale_limit * exp_scale).ceiling.asInteger.     
            mantissa_scale timesRepeat: [mantissa :*= radix].
            exponent :-= mantissa_scale.
        ] else [
            "" If at the precision limit of a float, then check the
            "" last decimal place and follow a rounding up rule
            if(exp2 <= -52 and: [(mantissa % radix) >= (radix//2)]) [
                mantissa := (mantissa // radix)+1.
                exponent :+= 1.
            ].
        ].
        "" Scale the mantissa by the exp-delta factor using fractions
        mantissa := (mantissa * radix_exp_scale_factor).asInteger.
    ].

    "" Normalize to remove trailing zeroes as appropriate
    while(mantissa != 0 and: [(mantissa % radix) = 0]) [
        exponent :+= 1.
        mantissa ://= radix.
    ].
   ^exponent.

I would expect that some similar patterns will begin to emerge for JavaScript support of UIn64/Int64 and other structural or numeric types as <BigInt> evolves.

smallscript
  • 643
  • 7
  • 13