BigInt

Значення BigInt (велике ціле) представляють числові значення, котрі завеликі для представлення примітивом number.

Опис

Значення BigInt, також іноді зване просто BigInt, є примітивом bigint, створеним шляхом додавання в кінець літерала цілого числа n, або ж викликом функції BigInt() (без оператора new) із передачею їй цілого числа або рядка.

const previouslyMaxSafeInteger = 9007199254740991n;

const alsoHuge = BigInt(9007199254740991);
// 9007199254740991n

const hugeString = BigInt("9007199254740991");
// 9007199254740991n

const hugeHex = BigInt("0x1fffffffffffff");
// 9007199254740991n

const hugeOctal = BigInt("0o377777777777777777");
// 9007199254740991n

const hugeBin = BigInt(
  "0b11111111111111111111111111111111111111111111111111111"
);
// 9007199254740991n

Значення BigInt у певних аспектах подібні до значень Number, але також відрізняються в кількох ключових нюансах: значення BigInt не можуть використовуватися з методами вбудованого об'єкта Math і не можуть змішуватися в операціях зі значенням Number; усі значення повинні бути зведені до одного типу. Проте слід обережно зводити значення туди-назад, адже точність значення BigInt може бути втрачена при зведенні до значення Number.

Інформація про тип

При перевірці typeof значення BigInt (примітив bigint) дасть "bigint":

typeof 1n === "bigint"; // true
typeof BigInt("1") === "bigint"; // true

Значення BigInt також може бути загорнуто в Object:

typeof Object(1n) === "object"; // true

Оператори

Наступні оператори можуть використовуватися зі значеннями BigInt (як примітивними, так і загорнутими в об'єкти):

+ * - % **

Побітові оператори також підтримуються, окрім >>> (зсуву вправо з заповненням нулями), адже всі значення BigInt мають знак.

Також підтримується унарний оператор (+), щоб не ламати asm.js.

const previousMaxSafe = BigInt(Number.MAX_SAFE_INTEGER);
// 9007199254740991n

const maxPlusOne = previousMaxSafe + 1n;
// 9007199254740992n

const theFuture = previousMaxSafe + 2n;
// 9007199254740993n, це тепер працює!

const multi = previousMaxSafe * 2n;
// 18014398509481982n

const subtr = multi - 10n;
// 18014398509481972n

const mod = multi % 10n;
// 2n

const bigN = 2n ** 54n;
// 18014398509481984n

bigN * -1n;
// -18014398509481984n

Оператор / також працює з цілими числами як очікується – проте результати операцій з дробовими результатами обрізаються при використанні зі значеннями BigInt: не буде жодної дробової частини.

const expected = 4n / 2n;
// 2n

const truncated = 5n / 2n;
// 2n, а не 2.5n

Порівняння

Значення BigInt строго не дорівнює значенню Number, проте дорівнює нестрого:

0n === 0;
// false

0n == 0;
// true

Значення Number і значення BigInt можуть порівнюватися як звично:

1n < 2;
// true

2n > 1;
// true

2 > 2;
// false

2n > 2;
// false

2n >= 2;
// true

Значення BigInt та Number можуть зустрічатися в одному масиві й сортуватися:

const mixed = [4n, 6, -12n, 10, 4, 0, 0n];
// [4n, 6, -12n, 10, 4, 0, 0n]

mixed.sort(); // усталена логіка сортування
// [ -12n, 0, 0n, 10, 4n, 4, 6 ]

mixed.sort((a, b) => a - b);
// не спрацює, адже віднімання не працює з різними типами
// TypeError: can't convert BigInt value to Number value

// сортування з адекватним числовим порівнювачем
mixed.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
// [ -12n, 0, 0n, 4n, 4, 6, 10 ]

Зверніть увагу, що порівняння загорнутих в Object значень BigInt працює як для інших об'єктів – показує рівність лише тоді, коли порівнюється з собою один і той же примірник:

Object(0n) === 0n; // false
Object(0n) === Object(0n); // false

const o = Object(0n);
o === o; // true

Перевірки умов

Значення BigInt відповідають тим самим правилам перетворення, що й Number, коли:

  • перетворюються на Boolean – за допомогою функції Boolean;
  • використовуються з логічними операторами ||, && і !; або
  • зустрічаються в перевірках умов, як то інструкціях if.

А саме – лише 0n є хибністю; все решта – істинність.

if (0n) {
  console.log("Привіт з if!");
} else {
  console.log("Привіт з else!");
}

// "Привіт з else!"

0n || 12n;
// 12n

0n && 12n;
// 0n

Boolean(0n);
// false

Boolean(12n);
// true

!12n;
// false

!0n;
// true

Зведення до BigInt

Чимало вбудованих операцій, котрі очікують на BigInt, спершу зводять свої аргументи до BigInt. Ця операція може бути підсумована отак:

  • BigInt повертаються як є.
  • undefined і null викидають TypeError.
  • true стає 1n; false стає 0n.
  • Рядки перетворюються шляхом розбору їх так, ніби вони містять цілочисловий літерал. Будь-яка невдача розбору призводить до SyntaxError. Синтаксис є підмножиною рядкових літералів чисел, у якій десятковий розділювач і експоненційний запис – заборонені.
  • Number викидають TypeError для запобігання небажаному неявному зведенню, що призвело б до втрати точності.
  • Symbol викидають TypeError.
  • Об'єкти перетворюються на примітиви шляхом виклику їх методів [@@toPrimitive]() (з підказкою "number"), valueOf() і toString() – у такому порядку. Потім результівний примітив перетворюється на BigInt.

Найкращий спосіб досягнути в JavaScript майже такого ж ефекту – функція BigInt(): BigInt(x) використовує такий же алгоритм для перетворення x, окрім того, що Number не викидають TypeError, а перетворюються на BigInt, якщо є цілими числами.

Зверніть увагу, що вбудовані операції, котрі очікують на BigInt, нерідко після зведення обрізають BigInt до фіксованої ширини. Серед таких операцій – BigInt.asIntN(), BigInt.asUintN(), а також методи об'єктів BigInt64Array і BigUint64Array.

Конструктор

BigInt()

Створює нове значення BigInt

Статичні методи

BigInt.asIntN()

Обрізає значення BigInt до знакового цілого числа, і повертає його.

BigInt.asUintN()

Обрізає значення BigInt до беззнакового цілого числа, і повертає його.

Властивості примірника

BigInt.prototype[@@toStringTag]

Початкове значення @@toStringTag – рядок "BigInt". Ця властивість використовується в Object.prototype.toString(). Проте у зв'язку з тим, що BigInt також має власну реалізацію метода toString(), ця властивість не використовується, якщо не викликати Object.prototype.toString.call() зі значенням BigInt як thisArg.

Методи примірника

BigInt.prototype.toLocaleString()

Повертає рядок з чутливим до мови представленням цього значення BigInt. Заміщає метод Object.prototype.toLocaleString().

BigInt.prototype.toString()

Повертає рядкове представлення цього значення BigInt за заданою основою числення. Заміщає метод Object.prototype.toString().

BigInt.prototype.valueOf()

Повертає це значення BigInt. Заміщає метод Object.prototype.valueOf().

Рекомендації щодо застосування

Зведення

Через те, що перетворення між значеннями Number та значеннями BigInt можуть призводити до втрати точності, рекомендовано наступне:

  • Значення BigInt слід використовувати лише тоді, коли доцільно очікувати значень, більших за 2^53
  • Не слід перетворювати між собою значення BigInt і Number.

Криптографія

Операції, котрі підтримують значення BigInt, мають не сталий час виконання, а тому вразливі до атак по часу. Таким чином, значення BigInt у JavaScript можуть бути небезпечними для використання в криптографії, якщо не вживати застережних заходів. Як дуже узагальнений приклад – нападник може виміряти часову різницю між 101n ** 65537n і 17n ** 9999n, завдяки чому – оцінити потужність таємних значень, як то приватних ключів, на основі витрат часу. Якщо все ж необхідно використовувати BigInt, слід звернутися до ЧаПів атак по часу щодо загальних порад на тему цієї проблеми.

Використання всередині JSON

Застосування JSON.stringify() до будь-якого значення BigInt призведе до виринання TypeError, адже значення BigInt усталено не серіалізуються в JSON. Проте JSON.stringify() залишає саме для значень BigInt запасний хід: ця функція намагається викликати метод BigInt toJSON(). (Вона не робить цього для будь-яких інших примітивних значень.) Таким чином, можна реалізувати власний метод toJSON() (це один з тих небагатьох випадків, коли внесення змін до вбудованих об'єктів явно не знеохочується):

BigInt.prototype.toJSON = function () {
  return this.toString();
};

Замість викидання помилки, тепер JSON.stringify() виробляє рядок:

console.log(JSON.stringify({ a: 1n }));
// {"a":"1"}

Коли не хочеться вносити зміни до BigInt.prototype, можна застосувати для серіалізації значень BigInt параметр JSON.stringify replacer:

const replacer = (key, value) =>
  typeof value === "bigint" ? value.toString() : value;

const data = {
  number: 1,
  big: 18014398509481982n,
};
const stringified = JSON.stringify(data, replacer);

console.log(stringified);
// {"number":1,"big":"18014398509481982"}

Коли є дані JSON, котрі містять значення, про котрі відомо, що там будуть великі цілі числа, то для їх обробки можна використати параметр reviver методу JSON.parse:

const reviver = (key, value) => (key === "big" ? BigInt(value) : value);

const payload = '{"number":1,"big":"18014398509481982"}';
const parsed = JSON.parse(payload, reviver);

console.log(parsed);
// { number: 1, big: 18014398509481982n }

Примітка: Хоч замінювач для JSON.stringify() можна зробити узагальненим, і коректно серіалізувати значення BigInt для всіх можливих об'єктів, та відновлювач для JSON.parse() мусить бути конкретним для структури очікуваного корисного навантаження, тому що серіалізація відбувається зі втратами: неможливо відрізнити рядок, котрий представляє BigInt, від звичайного рядка.

Приклади

Обчислення простих чисел

// Повертає true, якщо передане значення BigInt є простим числом
function isPrime(p) {
  for (let i = 2n; i * i <= p; i++) {
    if (p % i === 0n) return false;
  }
  return true;
}

// Приймає за аргумент значення BigInt, повертає n-не просте число у вигляді значення BigInt
function nthPrime(nth) {
  let maybePrime = 2n;
  let prime = 0n;

  while (nth >= 0n) {
    if (isPrime(maybePrime)) {
      nth--;
      prime = maybePrime;
    }
    maybePrime++;
  }

  return prime;
}

nthPrime(20n);
// 73n

Специфікації

Сумісність із браузерами

desktop mobile server
Chrome Edge Firefox Internet Explorer Opera Safari WebView Android Chrome Android Firefox for Android Opera Android Safari on iOS Samsung Internet Deno Node.js
BigInt
Chrome Full support 67
Edge Full support 79
Firefox Full support 68
Internet Explorer No support No
Opera Full support 54
Safari Full support 14
WebView Android Full support 67
Chrome Android Full support 67
Firefox for Android Full support 68
Opera Android Full support 48
Safari on iOS Full support 14
Samsung Internet Full support 9.0
Deno Full support 1.0
Node.js Full support 10.4.0
BigInt() constructor Chrome Full support 67
Edge Full support 79
Firefox Full support 68
Internet Explorer No support No
Opera Full support 54
Safari Full support 14
WebView Android Full support 67
Chrome Android Full support 67
Firefox for Android Full support 68
Opera Android Full support 48
Safari on iOS Full support 14
Samsung Internet Full support 9.0
Deno Full support 1.0
Node.js Full support 10.4.0
asIntN Chrome Full support 67
Edge Full support 79
Firefox Full support 68
Internet Explorer No support No
Opera Full support 54
Safari Full support 14
WebView Android Full support 67
Chrome Android Full support 67
Firefox for Android Full support 68
Opera Android Full support 48
Safari on iOS Full support 14
Samsung Internet Full support 9.0
Deno Full support 1.0
Node.js Full support 10.4.0
asUintN Chrome Full support 67
Edge Full support 79
Firefox Full support 68
Internet Explorer No support No
Opera Full support 54
Safari Full support 14
WebView Android Full support 67
Chrome Android Full support 67
Firefox for Android Full support 68
Opera Android Full support 48
Safari on iOS Full support 14
Samsung Internet Full support 9.0
Deno Full support 1.0
Node.js Full support 10.4.0
toLocaleString Chrome Full support 67
Edge Full support 79
Firefox Full support 68
Internet Explorer No support No
Opera Full support 54
Safari Full support 14
WebView Android Full support 67
Chrome Android Full support 67
Firefox for Android Full support 68
Opera Android Full support 48
Safari on iOS Full support 14
Samsung Internet Full support 9.0
Deno Full support 1.0
Node.js Full support 10.4.0
toLocaleString.locales
Chrome Full support 76
Edge Full support 79
Firefox Full support 70
Internet Explorer No support No
Opera No support No
Safari Full support 14
WebView Android Full support 76
Chrome Android Full support 76
Firefox for Android Full support 79
Opera Android Full support 54
Safari on iOS Full support 14
Samsung Internet Full support 12.0
Deno Full support 1.8
Node.js Full support 12.9.0
toLocaleString.options
Chrome Full support 76
Edge Full support 79
Firefox Full support 70
Internet Explorer No support No
Opera No support No
Safari Full support 14
WebView Android Full support 76
Chrome Android Full support 76
Firefox for Android Full support 79
Opera Android Full support 54
Safari on iOS Full support 14
Samsung Internet Full support 12.0
Deno Full support ?null Node.js Full support 12.9.0
toString Chrome Full support 67
Edge Full support 79
Firefox Full support 68
Internet Explorer No support No
Opera Full support 54
Safari Full support 14
WebView Android Full support 67
Chrome Android Full support 67
Firefox for Android Full support 68
Opera Android Full support 48
Safari on iOS Full support 14
Samsung Internet Full support 9.0
Deno Full support 1.0
Node.js Full support 10.4.0
valueOf Chrome Full support 67
Edge Full support 79
Firefox Full support 68
Internet Explorer No support No
Opera Full support 54
Safari Full support 14
WebView Android Full support 67
Chrome Android Full support 67
Firefox for Android Full support 68
Opera Android Full support 48
Safari on iOS Full support 14
Samsung Internet Full support 9.0
Deno Full support 1.0
Node.js Full support 10.4.0

Дивіться також