Niezbędny przewodnik po najnowszym typie danych JavaScript: BigInt

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ W JavaScript typ Number nie może bezpiecznie reprezentować wartości całkowitych większych niż 2 53 . To ograniczenie zmusiło programistów do korzystania z nieefektywnych obejść i bibliotek innych firm. BigInt to nowy typ danych, który ma to naprawić.

Typ danych BigInt ma na celu umożliwienie programistom JavaScript reprezentowanie wartości całkowitych większych niż zakres obsługiwany przez typ danych Number . Możliwość reprezentowania liczb całkowitych z dowolną precyzją jest szczególnie ważna podczas wykonywania operacji matematycznych na dużych liczbach całkowitych. W przypadku BigInt przepełnienie liczby całkowitej nie będzie już problemem.

Ponadto możesz bezpiecznie pracować z sygnaturami czasowymi o wysokiej rozdzielczości, dużymi identyfikatorami liczb całkowitych i nie tylko bez konieczności stosowania obejścia. BigInt jest obecnie propozycją trzeciego etapu. Po dodaniu do specyfikacji stanie się drugim numerycznym typem danych w JavaScript, co zwiększy łączną liczbę obsługiwanych typów danych do ośmiu:

  • Boole'a
  • Zero
  • Nieokreślony
  • Numer
  • BigInt
  • Strunowy
  • Symbol
  • Obiekt

W tym artykule przyjrzymy się dobrze BigInt i zobaczymy, jak może pomóc przezwyciężyć ograniczenia typu Number w JavaScript.

Problem

Brak wyraźnego typu liczb całkowitych w JavaScript często wprawia programistów z innych języków w zakłopotanie. Wiele języków programowania obsługuje wiele typów numerycznych, takich jak float, double, integer i bignum, ale nie dotyczy to JavaScript. W JavaScript wszystkie liczby są reprezentowane w 64-bitowym formacie zmiennoprzecinkowym o podwójnej precyzji, zgodnie ze standardem IEEE 754-2008.

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Zgodnie z tym standardem bardzo duże liczby całkowite, których nie można dokładnie przedstawić, są automatycznie zaokrąglane. Aby być precyzyjnym, typ Number w JavaScript może bezpiecznie reprezentować tylko liczby całkowite między -9007199254740991 (-(2 53 -1)) a 9007199254740991 (2 53 -1). Każda wartość całkowita, która wykracza poza ten zakres, może stracić precyzję.

Można to łatwo sprawdzić, wykonując następujący kod:

 console.log(9999999999999999); // → 10000000000000000

Ta liczba całkowita jest większa niż największa liczba, jaką JavaScript może niezawodnie reprezentować za pomocą operacji podstawowej Number . Dlatego jest zaokrąglony. Nieoczekiwane zaokrąglanie może zagrozić niezawodności i bezpieczeństwu programu. Oto kolejny przykład:

 // notice the last digits 9007199254740992 === 9007199254740993; // → true

JavaScript dostarcza stałą Number.MAX_SAFE_INTEGER , która pozwala szybko uzyskać maksymalną bezpieczną liczbę całkowitą w JavaScript. Podobnie możesz uzyskać minimalną bezpieczną liczbę całkowitą, używając stałej Number.MIN_SAFE_INTEGER :

 const minInt = Number.MIN_SAFE_INTEGER; console.log(minInt); // → -9007199254740991 console.log(minInt - 5); // → -9007199254740996 // notice how this outputs the same value as above console.log(minInt - 4); // → -9007199254740996

Rozwiązanie

Jako obejście tych ograniczeń niektórzy programiści JavaScript przedstawiają duże liczby całkowite za pomocą typu String . Na przykład Twitter API dodaje ciągową wersję identyfikatorów do obiektów podczas odpowiadania za pomocą JSON. Ponadto opracowano szereg bibliotek, takich jak bignumber.js, aby ułatwić pracę z dużymi liczbami całkowitymi.

Dzięki BigInt aplikacje nie potrzebują już obejścia ani biblioteki do bezpiecznego reprezentowania liczb całkowitych poza Number.MAX_SAFE_INTEGER i Number.Min_SAFE_INTEGER . Operacje arytmetyczne na dużych liczbach całkowitych można teraz wykonywać w standardowym JavaScript bez ryzyka utraty precyzji. Dodatkową zaletą korzystania z natywnego typu danych w porównaniu z biblioteką innej firmy jest lepsza wydajność w czasie wykonywania.

Aby utworzyć BigInt , po prostu dołącz n na końcu liczby całkowitej. Porównywać:

 console.log(9007199254740995n); // → 9007199254740995n console.log(9007199254740995); // → 9007199254740996

Alternatywnie możesz wywołać konstruktor BigInt() :

 BigInt("9007199254740995"); // → 9007199254740995n

Literały BigInt można również zapisać w notacji binarnej, ósemkowej lub szesnastkowej:

 // binary console.log(0b100000000000000000000000000000000000000000000000000011n); // → 9007199254740995n // hex console.log(0x20000000000003n); // → 9007199254740995n // octal console.log(0o400000000000000003n); // → 9007199254740995n // note that legacy octal syntax is not supported console.log(0400000000000000003n); // → SyntaxError

Pamiętaj, że nie możesz użyć operatora ścisłej równości do porównania BigInt ze zwykłą liczbą, ponieważ nie są one tego samego typu:

 console.log(10n === 10); // → false console.log(typeof 10n); // → bigint console.log(typeof 10); // → number

Zamiast tego możesz użyć operatora równości, który wykonuje niejawną konwersję typu przed porównaniem jego operandów:

 console.log(10n == 10); // → true

Wszystkie operatory arytmetyczne mogą być używane w BigInt s z wyjątkiem jednoargumentowego operatora plus ( + ):

 10n + 20n; // → 30n 10n - 20n; // → -10n +10n; // → TypeError: Cannot convert a BigInt value to a number -10n; // → -10n 10n * 20n; // → 200n 20n / 10n; // → 2n 23n % 10n; // → 3n 10n ** 3n; // → 1000n let x = 10n; ++x; // → 11n --x; // → 10n

Powodem, dla którego jednoargumentowy operator plus ( + ) nie jest obsługiwany, jest to, że niektóre programy mogą polegać na niezmienniku, który + zawsze generuje Number lub zgłasza wyjątek. Zmiana zachowania + również złamałaby kod asm.js.

Oczywiście w przypadku użycia z operandami BigInt oczekuje się, że operatory arytmetyczne zwrócą wartość BigInt . Dlatego wynik operatora dzielenia ( / ) jest automatycznie obcinany. Na przykład:

 25 / 10; // → 2.5 25n / 10n; // → 2n

Niejawna konwersja typu

Ponieważ niejawna konwersja typu może spowodować utratę informacji, operacje mieszane między BigInt s i Number s są niedozwolone. Podczas mieszania dużych liczb całkowitych i liczb zmiennoprzecinkowych wynikowa wartość może nie być dokładnie reprezentowana przez BigInt lub Number . Rozważmy następujący przykład:

 (9007199254740992n + 1n) + 0.5

Wynik tego wyrażenia znajduje się poza domeną zarówno BigInt , jak i Number . Number z częścią ułamkową nie może być dokładnie przekonwertowany na BigInt . A BigInt większy niż 2 53 nie może być dokładnie przekonwertowany na Number .

W wyniku tego ograniczenia nie jest możliwe wykonywanie operacji arytmetycznych przy użyciu kombinacji operandów Number i BigInt . Nie możesz również przekazać BigInt do internetowych interfejsów API i wbudowanych funkcji JavaScript, które oczekują Number . Próba zrobienia tego spowoduje wystąpienie TypeError :

 10 + 10n; // → TypeError Math.max(2n, 4n, 6n); // → TypeError

Zauważ, że operatory relacyjne nie stosują się do tej reguły, jak pokazano w tym przykładzie:

 10n > 5; // → true

Jeśli chcesz wykonywać obliczenia arytmetyczne za pomocą BigInt i Number , musisz najpierw określić domenę, w której operacja powinna zostać wykonana. Aby to zrobić, po prostu skonwertuj jeden z operandów, wywołując Number() lub BigInt() :

 BigInt(10) + 10n; // → 20n // or 10 + Number(10n); // → 20

W przypadku napotkania w kontekście Boolean BigInt jest traktowany podobnie do Number . Innymi słowy, BigInt jest uważany za wartość prawdziwą, o ile nie jest równy 0n :

 if (5n) { // this code block will be executed } if (0n) { // but this code block won't }

Podczas sortowania tablicy nie występuje niejawna konwersja typu między BigInt i Number :

 const arr = [3n, 4, 2, 1n, 0, -1n]; arr.sort(); // → [-1n, 0, 1n, 2, 3n, 4]

Operatory bitowe, takie jak | , & , << , >> i ^ działają na BigInt s w podobny sposób do Number s. Liczby ujemne są interpretowane jako uzupełnienie dwójkowe o nieskończonej długości. Operandy mieszane są niedozwolone. Oto kilka przykładów:

 90 | 115; // → 123 90n | 115n; // → 123n 90n | 115; // → TypeError

Konstruktor BigInt

Podobnie jak w przypadku innych typów pierwotnych, BigInt można utworzyć za pomocą funkcji konstruktora. Argument przekazany do BigInt() jest automatycznie konwertowany na BigInt , jeśli to możliwe:

 BigInt("10"); // → 10n BigInt(10); // → 10n BigInt(true); // → 1n

Typy danych i wartości, których nie można przekonwertować, zgłaszają wyjątek:

 BigInt(10.2); // → RangeError BigInt(null); // → TypeError BigInt("abc"); // → SyntaxError

Możesz bezpośrednio wykonywać operacje arytmetyczne na BigInt utworzonym za pomocą konstruktora:

 BigInt(10) * 10n; // → 100n

W przypadku użycia jako operandy operatora ścisłej równości, BigInt utworzone przy użyciu konstruktora są traktowane podobnie do zwykłych:

 BigInt(true) === 1n; // → true

Funkcje biblioteczne

JavaScript udostępnia dwie funkcje biblioteczne do reprezentowania wartości BigInt jako liczb całkowitych ze znakiem lub bez znaku:

  • BigInt.asUintN(width, BigInt) : zawija BigInt od 0 do 2 szerokości -1
  • BigInt.asIntN(width, BigInt) : zawija BigInt między -2 szerokość-1 i 2 szerokość-1 -1

Funkcje te są szczególnie przydatne podczas wykonywania 64-bitowych operacji arytmetycznych. W ten sposób możesz pozostać w zamierzonym zakresie.

Obsługa przeglądarek i transpilowanie

W chwili pisania tego tekstu Chrome +67 i Opera +54 w pełni obsługują typ danych BigInt . Niestety Edge i Safari jeszcze go nie zaimplementowały. Firefox domyślnie nie obsługuje BigInt , ale można go włączyć, ustawiając javascript.options.bigint na true w about:config . Aktualna lista obsługiwanych przeglądarek jest dostępna na stronie Czy mogę użyć….

Niestety, transpilowanie BigInt to niezwykle skomplikowany proces, który pociąga za sobą znaczne obniżenie wydajności w czasie wykonywania. Niemożliwe jest również bezpośrednie wypełnianie BigInt ponieważ propozycja zmienia zachowanie kilku istniejących operatorów. Na razie lepszą alternatywą jest skorzystanie z biblioteki JSBI, która jest czysto JavaScriptową implementacją propozycji BigInt .

Ta biblioteka udostępnia interfejs API, który zachowuje się dokładnie tak samo jak natywny BigInt . Oto jak możesz korzystać z JSBI:

 import JSBI from './jsbi.mjs'; const b1 = JSBI.BigInt(Number.MAX_SAFE_INTEGER); const b2 = JSBI.BigInt('10'); const result = JSBI.add(b1, b2); console.log(String(result)); // → '9007199254741001'

Zaletą korzystania z JSBI jest to, że po ulepszeniu obsługi przeglądarek nie będziesz musiał przepisywać kodu. Zamiast tego możesz automatycznie skompilować swój kod JSBI do natywnego kodu BigInt za pomocą wtyczki babel. Ponadto wydajność JSBI jest porównywalna z natywnymi implementacjami BigInt . Wkrótce możesz spodziewać się szerszej obsługi BigInt przez przeglądarkę.

Wniosek

BigInt to nowy typ danych przeznaczony do użycia, gdy wartości całkowite są większe niż zakres obsługiwany przez typ danych Number . Ten typ danych pozwala nam bezpiecznie wykonywać operacje arytmetyczne na dużych liczbach całkowitych, reprezentować znaczniki czasu w wysokiej rozdzielczości, używać dużych identyfikatorów liczb całkowitych i nie tylko, bez konieczności korzystania z biblioteki.

Należy pamiętać, że nie można wykonywać operacji arytmetycznych za pomocą kombinacji operandów Number i BigInt . Musisz określić domenę, w której operacja powinna zostać wykonana, poprzez jawną konwersję jednego z operandów. Ponadto, ze względu na zgodność, nie można używać jednoargumentowego operatora plus ( + ) na BigInt .

Co myślisz? Czy BigInt jest dla Ciebie przydatny? Daj nam znać w komentarzach!