Computer Keyboard - Głównie JavaScript

Głównie JavaScript

rss feed icon

by

Gigantyczne liczby w JavaScript

Napis 'Big Int' na tle cyfr będących pierwszymi z silini z tysiąca

W języku JavaScript typ number, czyli dowolna zwykła liczba, jest w istocie typu float. Dlatego liczba wartości tego typu jest ograniczona, klasycznym problemem tego typy liczb jest np.: 0.2 + 0.1 != 0.3. Istnieje jednak nowe API oraz biblioteki, które umożliwiają operacje na liczbach całkowitych o dowolnej wielkości. Można ich także użyć do obliczeń zmiennoprzecinkowych.

BigInt

Big Int to nowe API, z chwilą pisania tego artykułu obsługuje je tylko Opera i Chrome. API jest na etapie 3 (ang. stage), procesu standaryzacji ECMAScript. Etap 3 jest przedostatni, oznaczający zbieranie informacji zwrotnych (ang. feedback) od firm, które implementują standard ECMAScript oraz użytkowników. Ostatnim jest już tylko etap 4, który oznacza, że propozycja jest gotowa, aby weszła do standardu.

API dodaje nowy typ prosty, czyli za pomocą operatora typeof, dostajemy ciąg znaków "bigint". Zapisujemy tego typu liczby dodając literę n na końcu:

var num = 10000n;

lub za pomocą funkcji BigInt:

var num = BigInt(10000);

W odróżnieniu od innych typów prostych nie można jednak używać słowa kluczowego new, dlatego zawsze będziemy mieli typ prosty. Ale nadal będzie następowało „paczkowanie” (ang. boxing) typu prostego do obiektu BigInt, gdy będziemy np. próbowali wywołać na tej liczbie jakąś metodę.

Jeśli mamy dwie liczby typu BigInt, możemy wykonywać na nich operacje, za pomocą zwykłych operatorów arytmetycznych.

var a = 100n;
var b = 200n;
console.log(a + b);
// 300n

Nie można łączyć liczb typu number oraz BigInt. Jeśli spróbujemy wykonać:

console.log(100 + 100n);

zostanie zwrócony wyjątek:

VM329:1 Uncaught TypeError: Cannot mix BigInt and other types, use explicit conversions

Najważniejsze jednak będą wartości, które są bardzo duże. Poniżej możecie zobaczyć obliczenie silni dla 1000, używając typu BigInt. Obliczenie wykonane jest za pomocą funkcji reduce (a nie rekurencji), dlatego nie grozi nam przepełnienie stosu.

Array(1000).fill(0).map((_, i) => i + 1).reduce((acc, i) => (acc * BigInt(i)), 1n);

Wynik ma 2568 cyfr, możecie zobaczyć go na CodePen (nie ma tam 1n tylko BigInt(1) dlatego, że edytor CodePen, przynajmniej w czasie pisanie tego artykułu, nie obsługuje składni BigInt, która nie jest jeszcze wbudowana w narzędzie Babel, którego używa CodePen). Nie jest też problemem silnia z 10 000.

Operacje na pieniądzach

Typ BigInt doskonale nadaje się także do operacji na pieniądzach, nawet do liczb z przecinkiem. Wystarczy tylko pomnożyć liczby określające pieniądze przez 100, a przy wyświetlaniu zamienić na ciąg znaków i dodać przecinek np. za pomocą funkcji replace i wyrażenia regularnego /([0-9]{2})$/.

Babel i Polyfill

Niestety ze względu na to, że BigInt to nowa składnia, nowy typ oraz to, że przeciążone zostały operatory dla tego typu, nie da się stworzyć dla niego polyfill (czyli pliku, który by dodał jego obsługę w przeglądarkach, które go nie obsługują). Jednak z chwilą pisania tego artykułu jest otwarty Pull Request z implementacją BigInt w Babel, ale nie jest on jeszcze skończony. Chociaż kto wie kiedy i czy w ogóle będzie zmergowany i dokończony.

Jest także projekt babel-plugin-transform-bigint, ale nie wygląda zachęcająco.

Google, który zaimplementował w swojej przeglądarce typ BigInt, udostępnił ciekawy projekt babel-plugin-transform-jsbi-to-bigint. Działa on odwrotnie (dzięki temu był łatwiejszy w implementacji), konwertuje kod JSBI, jedną z bibliotek do dużych liczb, do postaci natywnego BigInt. Dzięki temu można utworzyć dwa pliki jeden dla przeglądarek, które obsługują BigInt i drugi dla reszty przeglądarek. A w niedalekiej przyszłości będzie można zrezygnować z tego drugiego pliku całkowicie.

Będzie można (chociaż nie jestem pewien czy to zadziała), użyć narzędzia babel z samym pluginem aby pozbyć się JSBI, z kodu źródłowego, kiedy wszystkie nowoczesne przeglądarki będą miały już dodaną obsługę BigInt.

Sama składnia BigInt dla przeglądarek, które je wspierają, jest dostępna w Babel, poprzez oficjalny plugin.

Jeśli jesteś zainteresowany typem BigInt możesz przeczytać ten wpis na blogu V8 (czyli silniku JavaScript, który zasila node.js oraz Chrome/Chromium) „BigInt: arbitrary-precision integers in JavaScript”.

Biblioteki do dużych liczb

Istnieją też gotowe biblioteki JavaScript, które powstały jeszcze przed specyfikacją ECMAScript, Najpopularniejsza chyba to bn.js, której użyłem w projekcie LIPS (jest to prosty LISP w JavaScript), w którym opakowałem liczby w klasę (a dokładnie funkcje/konstruktor) i sprawdzam, czy przeglądarka obsługuje BigInt lub jest załadowana bn.js, gdy żaden z tych warunków nie jest spełniony używa zwykłych liczb.

Biblioteka użyta do pluginu napisanego przez Google czyli jsbi jest świeża, ma zaledwie 4 miesiące (z chwilą pisania tego artykułu) ale napisana została przez Mathiasa Bynensa, który jest częścią zespołu V8 oraz znany z implementacji związanych ze specyfikacjami standardu ECMAScript (głównie związane z kodowaniem znaków), np. jest autorem wyrażenia regularnego, które dopasowuje się do znaków Emoji z Unicode (użyłem go w swoim projekcie jQuery Terminal). Jest także autorem wpisu na blogu Google’a o BigInt, którego link znajduje się wyżej (Polecam jego prezentacje na YouTube).

Podsumowanie

Duże liczby są bardzo ciekawym dodatkiem do języka JavaScript. Nie trzeba się przejmować, że stracimy dokładność, jeśli przekroczymy próg Number.MAX_SAFE_INTEGER czyli 9_007_199_254_740_991. Można także użyć ich do implementacji biblioteki liczb dziesiętnych, które mogą pomóc przy operacjach na pieniądzach, (czyli mogą być rozwiązaniem problemu 0.10 + 0.20 !== 0.30). Znasz jeszcze jakieś ciekawe zastosowania tych liczb? A może sam użyłeś ich w jakimś projekcie? Napisz w komentarzu.

źródło strony (aby zobaczyć kod na githubie musisz kliknąć przycisk raw)
Komentarze

Hasło, które podasz umożliwi ponowne zalogowanie się i np. usunięcie komentarza, jest dobrowolne. Email jest szyfrowany i używany do wysyłania powiadomień o odpowiedziach do komentarzy oraz do pobierania awatara dzięki usłudze Gravatar.com.