Does JavaScript support 64-bit integers?

78,577

Solution 1

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.

Solution 2

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.

Solution 3

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.

Share:
78,577
ahmd0
Author by

ahmd0

Updated on July 08, 2022

Comments

  • ahmd0
    ahmd0 almost 2 years

    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?

  • nnnnnn
    nnnnnn about 12 years
    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.)
  • Jeremy Condit
    Jeremy Condit about 11 years
    Closure's goog.math.Long may also be helpful: docs.closure-library.googlecode.com/git/…
  • sellibitze
    sellibitze about 10 years
    One should maybe add that bit-level operations are limited to 32 bit IIUC.
  • benizi
    benizi almost 9 years
  • falsarella
    falsarella over 8 years
    (Comment by @Michaelangelo) Unfortunately, the ECMAScript 2015 Specifications (version 6) has no official support for UInt64; while Mozilla has added support for UInt64 -- this is non-standard. WebGL has similar needs but unfortunately there is no Uint64Array either, only Uint32Array.
  • benizi
    benizi almost 7 years
    goog.math.Long documentation has moved again: google.github.io/closure-library/api/goog.math.Long.html (Thanks, @Pacerier)
  • trusktr
    trusktr over 6 years
    "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?
  • Peter Zhao
    Peter Zhao about 6 years
    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. github.com/dcodeIO/long.js
  • bryc
    bryc about 6 years
    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).
  • oisyn
    oisyn over 5 years
    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.
  • christo8989
    christo8989 about 5 years
    Btw, I think ES10 or something will have Big ints.
  • David Callanan
    David Callanan about 5 years
    Also supported by Opera 54+ and Node.js. Firefox 65+ supports it if javascript.options.bigint flag is enabled.
  • Cibo FATA8
    Cibo FATA8 over 4 years
    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");
  • hippietrail
    hippietrail about 4 years
    @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).
  • Raj Pawan Gumdal
    Raj Pawan Gumdal about 3 years
    @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?
  • Admin
    Admin almost 3 years
    Upvote for the history bit, is there some paper about it?
  • Javier Rey
    Javier Rey over 2 years
    It's actually 54-bit precision, one bit for the sign, so the range is -2^53 to +2^53 (minus 1)
  • SilverbackNet
    SilverbackNet over 2 years
    FWIW, BigInt and its many methods is now standard in ES 11, and all major browsers.