Czy JavaScript obsługuje 64-bitowe liczby całkowite?

81

Mam następujący kod:

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

Otrzymuję tę wartość: „ 4611686018427388000”, czyli0x4000000000000060

Zastanawiałem się, czy JavaScript źle obsługuje 64-bitowe liczby całkowite, czy też robię coś źle?

ahmd0
źródło

Odpowiedzi:

88

JavaScript reprezentuje liczby w formacie IEEE-754 podwójnej precyzji (64-bitowy). Jak rozumiem, daje to dokładność 53 bitów lub piętnaście do szesnastu cyfr dziesiętnych. Twój numer ma więcej cyfr, niż może obsłużyć JavaScript, więc otrzymujesz przybliżenie.

Nie jest to tak naprawdę „błędna obsługa” jako taka, ale oczywiście nie jest zbyt pomocna, jeśli potrzebujesz pełnej precyzji na dużych liczbach. Istnieje kilka bibliotek JS, które mogą obsługiwać większe liczby, np. BigNumber i Int64 .

nnnnnn
źródło
2
Pomocny może być również plik goog.math.Long Closure
Jeremy Condit
39
Należy dodać, że operacje na poziomie bitów są ograniczone do 32-bitowych IIUC.
sellibitze
2
Przeniesiono dokumentację goog.math.Długą dokumentację: google.github.io/closure-library/api/class_goog_math_Long.html
benizi
5
(Komentarz @Michaelangelo ) Niestety specyfikacje ECMAScript 2015 (wersja 6) nie mają oficjalnego wsparcia dla UInt64; podczas gdy Mozilla dodała obsługę UInt64 - jest to niestandardowe. WebGL ma podobne potrzeby, ale niestety ich nie ma Uint64Array, tylko Uint32Array .
falsarella
2
Dokumentacja goog.math.Long została ponownie przeniesiona: google.github.io/closure-library/api/goog.math.Long.html (Dzięki, @Pacerier)
benizi
11

Chromium w wersji 57 i nowszych natywnie obsługuje liczby całkowite o dowolnej precyzji. Nazywa się to BigInt i pracuje nad tym również dla innych przeglądarek. Jest znacznie szybszy niż implementacje JavaScript.

Shnatsel
źródło
Obsługiwane również przez Operę 54+ i Node.js. Firefox 65+ obsługuje to, jeśli javascript.options.bigintflaga jest włączona.
David Callanan
Nie zawsze jest szybszy. porównaj to z console.time("go");for (var i=0;i<10000000;++i) {} console.timeEnd("go");liczbami 64console.time("go");for (var i=0n;i<10000000n;++i) {} console.timeEnd("go");
bitowymi
@ CiboFATA8: Mówi o BigInt jako natywnym komponencie przeglądarki, a nie o BigInt zaimplementowanym w JavaScript. Porównujesz js Numbers, które są liczbami zmiennoprzecinkowymi z około 53 bitami precyzji (nie 64) z natywną przeglądarką BigInt (również nie 64-bitową).
hippietrail
0

Tj. V8 JavaScript jest silnikiem wywodzącym się z Smalltalk. (Lata 80-te - obecnie) Silniki Lisp i Smalltalk obsługują arytmetykę o dużej precyzji przy użyciu <LargeInteger>, czasami nazywanego <BigInt>. Spoiler, zespół Dart w Google to w dużej mierze grupa byłych Smalltalkerów, którzy wspólnie przenoszą swoje doświadczenie w przestrzeń JS.

Te typy liczb mają nieograniczoną dokładność i są zwykle używane jako bloki konstrukcyjne do dostarczania obiektów <Rational: Fraction>, których licznikiem i mianownikiem może być dowolny typ liczby, w tym <BigInt>. Dzięki temu można przedstawiać liczby rzeczywiste, urojone i robić to z doskonałą precyzją na liczbach niewymiernych, takich jak (1/3).

Uwaga: od dawna wdrażam i programuję Smalltalk, JS i inne języki oraz ich silniki i frameworki.

Jeśli zostanie to zrobione odpowiednio <BigInt> dla arytmetyki o dużej precyzji jako standardowa funkcja JavaScript, otworzy drzwi do ogromnego zestawu operacji, w tym natywnej wydajnej kryptografii (co jest łatwe do wykonania z liczbami o dużej precyzji).

Na przykład w jednym z moich silników smalltalk z 1998 roku na procesorze 2,3 GHz, który właśnie uruchomiłem:

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

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

Zdefiniowane jako: (ilustruje <BigInt>multi-precyzję w działaniu)

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

Silnik V8 z pracy Larsa Baka (mojego współczesnego) wywodzi się z Animorphic Smalltalk z pracy SELF Davida Ungara wywodzącej się z Smalltalk-80, a następnie ewoluował w JVM i przerobiony przez Lars for Mobile, który pojawił się później jako podstawa silnika V8.

Wspominam o tym, ponieważ zarówno Animorphic Smalltalk, jak i QKS Smalltalk obsługują adnotacje typu, które umożliwiają silnikowi i narzędziom rozumowanie w kodzie w podobny sposób, jak TypeScript próbował dla JavaScript.

Ta podpowiedź do adnotacji i jej użycie przez język, narzędzia i mechanizmy wykonawcze oferuje możliwość obsługi wielu metod (zamiast podwójnego wysyłania) potrzebnych do prawidłowego wspierania promocji typu arytmetycznego o dużej precyzji i reguł wymuszania.

Co z kolei jest kluczem do obsługi 8/16/32/64 int / uints i wielu innych typów liczbowych w spójnej strukturze.

<Magnitude|Number|UInt64>Przykłady wielu metod z 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.

Spodziewałbym się, że niektóre podobne wzorce zaczną się pojawiać dla obsługi JavaScript dla UIn64 / Int64 i innych typów strukturalnych lub numerycznych w miarę ewolucji <BigInt>.

smallscript
źródło