Does JavaScript support 64-bit integers?

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.


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.


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.