Polski

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:

Tworzenie wartości BigInt

Istnieją dwa główne sposoby tworzenia wartości BigInt w JavaScript:

  1. Użycie konstruktora `BigInt()`: Ten konstruktor może przekonwertować liczbę, ciąg znaków lub wartość logiczną na BigInt.
  2. 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:

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:

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:

  1. Konwersja na ciąg znaków: Przekonwertuj BigInt na ciąg znaków przed serializacją. Jest to najczęstsze i najprostsze podejście.
  2. 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.

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

Ten przewodnik przedstawia kompleksowy przegląd BigInt w JavaScript. Zapoznaj się z podlinkowanymi zasobami, aby uzyskać bardziej szczegółowe informacje i poznać zaawansowane techniki.