Kompleksowy przewodnik po typie BigInt w JavaScript. Poznaj jego cechy, zastosowania i sposób użycia do precyzyjnej arytmetyki na dużych liczbach całkowitych, pokonując ograniczenia języka.
JavaScript BigInt: Opanowanie arytmetyki na dużych liczbach całkowitych
JavaScript, choć jest wszechstronnym językiem, ma ograniczenia w obsłudze bardzo dużych liczb całkowitych. Standardowy typ `Number` może dokładnie reprezentować liczby całkowite tylko do pewnego limitu, znanego jako `Number.MAX_SAFE_INTEGER`. Powyżej tego limitu obliczenia stają się nieprecyzyjne, co prowadzi do nieoczekiwanych wyników. W tym miejscu z pomocą przychodzi BigInt
. Wprowadzony w ECMAScript 2020, BigInt
to wbudowany obiekt, który umożliwia reprezentowanie i manipulowanie liczbami całkowitymi o dowolnej wielkości, przekraczając ograniczenia standardowego typu `Number`.
Zrozumienie potrzeby BigInt
Przed wprowadzeniem BigInt
, deweloperzy JavaScript musieli polegać na bibliotekach lub własnych implementacjach do obsługi obliczeń na dużych liczbach całkowitych. Te rozwiązania często wiązały się z narzutem wydajnościowym i zwiększoną złożonością. Wprowadzenie BigInt
zapewniło natywny i wydajny sposób pracy z dużymi liczbami całkowitymi, otwierając możliwości zastosowań w różnych dziedzinach, w tym:
- Kryptografia: Bezpieczne operowanie na dużych liczbach pierwszych jest kluczowe dla algorytmów kryptograficznych.
- Obliczenia finansowe: Dokładne reprezentowanie dużych wartości pieniężnych bez utraty precyzji.
- Obliczenia naukowe: Wykonywanie złożonych obliczeń obejmujących ekstremalnie duże lub małe liczby.
- Znaczniki czasu o wysokiej precyzji: Reprezentowanie znaczników czasu z precyzją nanosekundową.
- Generowanie ID: Tworzenie unikalnych i bardzo dużych identyfikatorów.
Tworzenie wartości BigInt
Istnieją dwa główne sposoby tworzenia wartości BigInt
w JavaScript:
- Użycie konstruktora `BigInt()`: Ten konstruktor może przekonwertować liczbę, ciąg znaków lub wartość logiczną na
BigInt
. - Użycie przyrostka `n`: Dołączenie `n` do literału liczby całkowitej tworzy
BigInt
.
Przykłady:
Użycie konstruktora `BigInt()`:
const bigIntFromNumber = BigInt(12345678901234567890);
const bigIntFromString = BigInt("98765432109876543210");
const bigIntFromBoolean = BigInt(true); // Wynik to 1n
const bigIntFromFalseBoolean = BigInt(false); // Wynik to 0n
console.log(bigIntFromNumber); // Wyjście: 12345678901234567890n
console.log(bigIntFromString); // Wyjście: 98765432109876543210n
console.log(bigIntFromBoolean); // Wyjście: 1n
console.log(bigIntFromFalseBoolean); // Wyjście: 0n
Użycie przyrostka `n`:
const bigIntLiteral = 12345678901234567890n;
console.log(bigIntLiteral); // Wyjście: 12345678901234567890n
Ważna uwaga: Nie można bezpośrednio mieszać wartości BigInt
i Number
w operacjach arytmetycznych. Należy jawnie przekonwertować je na ten sam typ przed wykonaniem obliczeń. Próba ich bezpośredniego mieszania spowoduje błąd `TypeError`.
Operacje arytmetyczne na BigInt
BigInt
obsługuje większość standardowych operatorów arytmetycznych, w tym:
- Dodawanie (`+`)
- Odejmowanie (`-`)
- Mnożenie (`*`)
- Dzielenie (`/`)
- Reszta z dzielenia (`%`)
- Potęgowanie (`**`)
Przykłady:
const a = 12345678901234567890n;
const b = 98765432109876543210n;
const sum = a + b;
const difference = a - b;
const product = a * b;
const quotient = a / 2n; // Uwaga: Dzielenie obcina część ułamkową w kierunku zera
const remainder = a % 7n;
const power = a ** 3n; // Potęgowanie działa zgodnie z oczekiwaniami
console.log("Suma:", sum); // Wyjście: Suma: 111111111011111111100n
console.log("Różnica:", difference); // Wyjście: Różnica: -86419753208641975320n
console.log("Iloczyn:", product); // Wyjście: Iloczyn: 1219326311370217957951669538098765432100n
console.log("Iloraz:", quotient); // Wyjście: Iloraz: 6172839450617283945n
console.log("Reszta:", remainder); // Wyjście: Reszta: 5n
console.log("Potęga:", power); // Wyjście: Potęga: 187641281029182300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000n
Ważne uwagi:
- Dzielenie: Dzielenie z wartościami
BigInt
obcina wynik w kierunku zera. Oznacza to, że część dziesiętna wyniku jest odrzucana. Jeśli potrzebujesz bardziej precyzyjnego dzielenia, rozważ użycie bibliotek obsługujących arytmetykę o dowolnej precyzji. - Operator plusa unarnego (+): Operator plusa unarnego (+) nie może być używany z wartościami
BigInt
, ponieważ kolidowałoby to ze starszym kodem asm.js. Użyj funkcji konwersji `Number()`, aby przekonwertować BigInt na Number, jeśli potrzebujesz reprezentacji numerycznej (rozumiejąc, że możesz stracić precyzję). - Operatory bitowe:
BigInt
obsługuje również operatory bitowe, takie jak `&`, `|`, `^`, `~`, `<<` i `>>`. Operatory te działają zgodnie z oczekiwaniami na binarnej reprezentacji wartościBigInt
.
Operatory porównania
Możesz używać standardowych operatorów porównania (`==`, `!=`, `<`, `>`, `<=`, `>=`) do porównywania wartości BigInt
z innymi wartościami BigInt
, a nawet z wartościami Number
. Bądź jednak świadomy potencjalnej koercji typów.
Przykłady:
const a = 10n;
const b = 20n;
const c = 10;
console.log(a == b); // Wyjście: false
console.log(a != b); // Wyjście: true
console.log(a < b); // Wyjście: true
console.log(a > b); // Wyjście: false
console.log(a <= b); // Wyjście: true
console.log(a >= b); // Wyjście: false
console.log(a == c); // Wyjście: true (koercja typów)
console.log(a === c); // Wyjście: false (brak koercji typów)
Dobra praktyka: Używaj ścisłej równości (`===`) i ścisłej nierówności (`!==`), aby uniknąć nieoczekiwanej koercji typów podczas porównywania wartości BigInt
i Number
.
Konwersja między BigInt a Number
Chociaż bezpośrednie operacje arytmetyczne między BigInt
a Number
nie są dozwolone, można konwertować między tymi dwoma typami. Należy jednak pamiętać o potencjalnej utracie precyzji podczas konwersji BigInt
na Number
, jeśli wartość BigInt
przekracza `Number.MAX_SAFE_INTEGER`.
Przykłady:
const bigIntValue = 9007199254740991n; // Number.MAX_SAFE_INTEGER
const numberValue = Number(bigIntValue); // Konwersja BigInt na Number
console.log(numberValue); // Wyjście: 9007199254740991
const largerBigIntValue = 9007199254740992n; // Przekracza Number.MAX_SAFE_INTEGER
const largerNumberValue = Number(largerBigIntValue);
console.log(largerNumberValue); // Wyjście: 9007199254740992 (może być nieprecyzyjne)
const numberToBigInt = BigInt(12345); // Konwersja Number na BigInt
console.log(numberToBigInt); // Wyjście: 12345n
Przypadki użycia i przykłady
Kryptografia
Algorytmy kryptograficzne często opierają się na bardzo dużych liczbach pierwszych dla zapewnienia bezpieczeństwa. BigInt
umożliwia wydajne reprezentowanie i manipulowanie tymi liczbami.
// Przykład: Generowanie prostej (niezabezpieczonej) pary kluczy
function generateKeyPair() {
const p = 281n; // Liczba pierwsza
const q = 283n; // Inna liczba pierwsza
const n = p * q; // Moduł
const totient = (p - 1n) * (q - 1n); // Funkcja φ Eulera (tocjent)
// Wybierz e (wykładnik publiczny) taki, że 1 < e < totient i gcd(e, totient) = 1
const e = 17n;
// Oblicz d (wykładnik prywatny) taki, że (d * e) % totient = 1
let d = 0n;
for (let i = 1n; i < totient; i++) {
if ((i * e) % totient === 1n) {
d = i;
break;
}
}
return {
publicKey: { n, e },
privateKey: { n, d },
};
}
const keyPair = generateKeyPair();
console.log("Klucz publiczny:", keyPair.publicKey);
console.log("Klucz prywatny:", keyPair.privateKey);
Uwaga: To jest uproszczony przykład wyłącznie w celach demonstracyjnych. Rzeczywista kryptografia wykorzystuje znacznie większe liczby pierwsze i bardziej zaawansowane algorytmy.
Obliczenia finansowe
Podczas operowania na dużych sumach pieniędzy, szczególnie w transakcjach międzynarodowych, precyzja jest kluczowa. BigInt
może zapobiegać błędom zaokrągleń i zapewniać dokładne obliczenia.
// Przykład: Obliczanie odsetek składanych
function calculateCompoundInterest(principal, rate, time) {
const principalBigInt = BigInt(principal * 100); // Konwertuj na grosze
const rateBigInt = BigInt(rate * 10000); // Konwertuj na dziesięciotysięczne części procenta
const timeBigInt = BigInt(time);
let amount = principalBigInt;
for (let i = 0n; i < timeBigInt; i++) {
amount = amount * (10000n + rateBigInt) / 10000n;
}
const amountInDollars = Number(amount) / 100;
return amountInDollars;
}
const principal = 1000000; // 1 000 000 zł
const rate = 0.05; // 5% stopa procentowa
const time = 10; // 10 lat
const finalAmount = calculateCompoundInterest(principal, rate, time);
console.log("Kwota końcowa:", finalAmount); // Wyjście: Kwota końcowa: 1628894.6267774413 (w przybliżeniu)
W tym przykładzie konwertujemy kapitał i stopę procentową na wartości BigInt
, aby uniknąć błędów zaokrągleń podczas obliczeń. Wynik jest następnie konwertowany z powrotem na Number
do wyświetlenia.
Praca z dużymi identyfikatorami (ID)
W systemach rozproszonych generowanie unikalnych identyfikatorów na wielu serwerach może być wyzwaniem. Użycie BigInt
pozwala na tworzenie bardzo dużych identyfikatorów, których kolizja jest mało prawdopodobna.
// Przykład: Generowanie unikalnego ID na podstawie znacznika czasu i ID serwera
function generateUniqueId(serverId) {
const timestamp = BigInt(Date.now());
const serverIdBigInt = BigInt(serverId);
const random = BigInt(Math.floor(Math.random() * 1000)); // Dodaj odrobinę losowości
// Połącz wartości, aby utworzyć unikalny ID
const uniqueId = (timestamp << 20n) + (serverIdBigInt << 10n) + random;
return uniqueId.toString(); // Zwróć jako ciąg znaków dla łatwiejszej obsługi
}
const serverId = 123; // Przykładowe ID serwera
const id1 = generateUniqueId(serverId);
const id2 = generateUniqueId(serverId);
console.log("Unikalne ID 1:", id1);
console.log("Unikalne ID 2:", id2);
BigInt i JSON
Wartości BigInt
nie są natywnie obsługiwane przez JSON. Próba serializacji obiektu JavaScript zawierającego BigInt
za pomocą `JSON.stringify()` spowoduje błąd `TypeError`. Aby obsłużyć wartości BigInt
podczas pracy z JSON, masz kilka opcji:
- Konwersja na ciąg znaków: Przekonwertuj
BigInt
na ciąg znaków przed serializacją. Jest to najczęstsze i najprostsze podejście. - Niestandardowa serializacja/deserializacja: Użyj niestandardowej funkcji serializacji/deserializacji do obsługi wartości
BigInt
.
Przykłady:
Konwersja na ciąg znaków:
const data = {
id: 12345678901234567890n,
name: "Przykładowe dane",
};
// Konwertuj BigInt na ciąg znaków przed serializacją
data.id = data.id.toString();
const jsonData = JSON.stringify(data);
console.log(jsonData); // Wyjście: {"id":"12345678901234567890","name":"Przykładowe dane"}
// Podczas deserializacji musisz przekonwertować ciąg znaków z powrotem na BigInt
const parsedData = JSON.parse(jsonData, (key, value) => {
if (key === "id") {
return BigInt(value);
}
return value;
});
console.log(parsedData.id); // Wyjście: 12345678901234567890n
Niestandardowa serializacja/deserializacja (używając `replacer` i `reviver`):
const data = {
id: 12345678901234567890n,
name: "Przykładowe dane",
};
// Niestandardowa serializacja
const jsonData = JSON.stringify(data, (key, value) => {
if (typeof value === 'bigint') {
return value.toString();
} else {
return value;
}
});
console.log(jsonData);
// Niestandardowa deserializacja
const parsedData = JSON.parse(jsonData, (key, value) => {
if (typeof value === 'string' && /^[0-9]+$/.test(value)) { //sprawdź, czy to liczba i ciąg znaków
try {
return BigInt(value);
} catch(e) {
return value;
}
}
return value;
});
console.log(parsedData.id);
Kompatybilność z przeglądarkami
BigInt
jest szeroko obsługiwany w nowoczesnych przeglądarkach. Jednak kluczowe jest sprawdzenie kompatybilności ze starszymi przeglądarkami lub środowiskami. Możesz użyć narzędzia takiego jak Can I use, aby zweryfikować wsparcie przeglądarek. Jeśli musisz wspierać starsze przeglądarki, możesz rozważyć użycie polyfilla, ale pamiętaj, że polyfille mogą wpłynąć na wydajność.
Kwestie wydajnościowe
Chociaż BigInt
zapewnia potężny sposób pracy z dużymi liczbami całkowitymi, ważne jest, aby być świadomym potencjalnych implikacji wydajnościowych.
- Operacje na
BigInt
mogą być wolniejsze niż standardowe operacje naNumber
. - Konwersja między
BigInt
aNumber
również może wprowadzać narzut.
Dlatego używaj BigInt
tylko wtedy, gdy jest to konieczne, i optymalizuj swój kod pod kątem wydajności, jeśli wykonujesz dużą liczbę operacji na BigInt
.
Podsumowanie
BigInt
jest cennym dodatkiem do JavaScript, umożliwiającym programistom precyzyjną obsługę arytmetyki na dużych liczbach całkowitych. Rozumiejąc jego cechy, ograniczenia i przypadki użycia, możesz wykorzystać BigInt
do budowy solidnych i dokładnych aplikacji w różnych dziedzinach, w tym w kryptografii, obliczeniach finansowych i naukowych. Pamiętaj, aby uwzględnić kompatybilność z przeglądarkami i implikacje wydajnościowe podczas używania BigInt
w swoich projektach.
Dalsza lektura
- Mozilla Developer Network (MDN) - BigInt
- V8 Blog - BigInt: Arbitrary-Precision Integers in JavaScript
Ten przewodnik przedstawia kompleksowy przegląd BigInt
w JavaScript. Zapoznaj się z podlinkowanymi zasobami, aby uzyskać bardziej szczegółowe informacje i poznać zaawansowane techniki.