Niezbędny przewodnik po najnowszym typie danych JavaScript: BigInt
Opublikowany: 2022-03-10Number
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.
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)
: zawijaBigInt
od 0 do 2 szerokości -1 -
BigInt.asIntN(width, BigInt)
: zawijaBigInt
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!