[Перевод] BigInt — длинная арифметика в JavaScript
С BigInt
вы сможете безопасно хранить и обрабатывать большие целые числа даже за пределами максимального безопасного целочисленного значения Number
. BigInt
— новый числовой примитивный тип данных в JavaScript, позволяющий работать с числами произвольной точности. В этой статье мы рассмотрим некоторые примеры использования BigInt
и новые функции Chrome 67, сравнивая BigInt
и Number
в JavaScript.
Примеры использования
Целые числа произвольной точности открывают много новых вариантов использования JavaScript.
Этот факт сам по себе делает доступными бесчисленные возможности, например, математические операции над большими числами обычно используются в финансовых технологиях. BigInt позволяет предотвратить ошибки переполнения при математических операциях.
Зачастую это приводит к ошибкам и вынуждает разработчиков хранить их как строки. Большие числовые идентификаторы и высокоточные метки времени не могут быть безопасно представлены типом данных Number
в JavaScript. С BigInt
эти данные могут быть представлены как числовые значения.
Это позволит хранить денежные величины в виде десятичных дробей без потери точности при выполнении операций (например, без проблемы 0.
). == 0.BigInt
можно будет использовать в возможной реализации типа данных BigDecimal
. 20! 10 + 0. 30
Когда BigInt
станет широко доступным, можно будет отказаться от этих зависимостей в пользу нативно поддерживаемого BigInt
. Ранее в JavaScript в любом из этих случаев приходилось использовать библиотеки, эмулирующие функционал BigInt
. Это поможет сократить время загрузки, парсинга и компиляции, а также увеличит производительность во время выполнения.
Нативный BigInt
работает быстрее, чем популярные пользовательские библиотеки
В настоящее время Babel поддерживает парсинг литералов BigInt
, но не умеет преобразовывать их. Для полифила BigInt
требуется библиотека, которая реализует необходимые функции, а также шаг транспиляции, чтобы перевести новый синтаксис в вызов API библиотеки. Однако, сейчас, когда этот функционал начинает появляться в браузерах, вы можете начать экспериментировать с BigInt, ожидая со временем всё более широкой поддержки BigInt. Поэтому мы не надеемся, что BigInt
будет использоваться в продакшне на сайтах, требующих совместимость с широким кругом браузеров.
Статус-кво: Number
Константа Number. Примитивный тип данных
содержит максимально возможное целое число, которое можно безопасно увеличить на единицу.Number
в JavaScript представлен числами с плавающей запятой двойной точности. Его значение равно 2 ** 53-1
. MAX_SAFE_INTEGER
const max = Number.MAX_SAFE_INTEGER;
// → 9_007_199_254_740_991
Соответствующее предложение позволит использовать такую запись для обычных числовых литералов JavaScript. Примечание: для удобства чтения я группирую цифры, используя символы подчеркивания в качестве разделителей.
Его увеличение на единицу даёт ожидаемый результат:
max + 1;
// → 9_007_199_254_740_992
Но если мы увеличим его ещё на единицу, Number
не сможет точно сохранить результат:
max + 2;
// → 9_007_199_254_740_992
Поэтому всегда, когда мы получаем конкретно это значение в JavaScript, нельзя сказать, является ли оно точным или нет. Обратите внимание, что результат выражения max + 1
будет равен результату выражения max + 2
. е. Любые вычисления с целыми числами вне безопасного целочисленного диапазона (т. MIN_SAFE_INTEGER до Number. от
Number. По этой причине мы можем полагаться только на целочисленные значения в безопасном диапазоне. MAX_SAFE_INTEGER
) потенциально не точны.
Новинка: BigInt
С BigInt
вы сможете безопасно хранить и обрабатывать большие целые числа даже за пределами максимального безопасного целочисленного значения Number
. BigInt
— новый числовой примитивный тип данных в JavaScript, позволяющий работать с числами произвольной точности.
Например, 123
станет 123n
. Для создания BigInt
достаточно добавить суффикс n
к литеральной записи целого числа. Другими словами, BigInt(123) === 123n
. Глобальную функцию BigInt(number)
можно использовать для приведения числа к BigInt
. Давайте используем это для решения тех проблем, о которых мы говорили выше:
BigInt(Number.MAX_SAFE_INTEGER) + 2n;
// → 9_007_199_254_740_993n
Вот ещё один пример, с умножением двух чисел типа Number
:
1234567890123456789 * 123;
// → 151851850485185200000
Но результат заканчивается набором нулей. Если мы посмотрим на цифры младшего разряда, 9
и 3
, можно утверждать, что результат умножения должен заканчиваться на 7
(потому что 9 * 3 === 27
). Попробуем еще раз с BigInt
: Что-то пошло не так.
1234567890123456789n * 123n;
// → 151851850485185185047n
В этот раз результат верный.
Пределы для безопасной работы с целыми числами не применимы к BigInt
, поэтому с BigInt
мы можем применять длинную арифметику не беспокоясь о потере точности.
Новый примитивный тип данных
BigInt
— новый примитивный тип данных в языке JavaScript, поэтому он получает свой собственный тип, который может вернуть оператор typeof
:
typeof 123;
// → 'number'
typeof 123n;
// → 'bigint'
Чтобы сравнить число типа BigInt
и число типа Number
, преобразуйте один из них в тип другого, прежде чем выполнять сравнение, или используйте сравнение с преобразованием типов (==
): Так как BigInt
является самостоятельным типом данных, число типа BigInt
никогда не может быть строго равно числу типа Number
(например, 42n !== 42
).
42n === BigInt(42);
// → true
42n == 42;
// → true
При приведении к логическому значению (например, в if
, при использовании &&
или ||
, или как результат выражения Boolean(int)
, и так далее), числа типа BigInt
ведут себя точно так же, как числа типа Number
.
if (0n) { console.log('if');
} else { console.log('else');
}
// → logs 'else', because `0n` is falsy.
Операторы
Бинарные +
, -
, *
и **
работают как обычно. BigInt
поддерживает большинство операторов. Побитовые операторы |
, &
, <<
, >>
и ^
работают с числами типа BigInt
аналогично числам типа Number
, когда отрицательные числа представлены в двоичном виде как дополнительный код. /
и %
также работают, округляя результат до нуля при необходимости.
(7 + 6 - 5) * 4 ** 3 / 2 % 3;
// → 1
(7n + 6n - 5n) * 4n ** 3n / 2n % 3n;
// → 1n
Унарный +
не поддерживается, потому что он нарушит код asm.js, который ожидает, что +x
всегда будет возвращать либо Number
, либо исключение. Унарный -
можно использовать для обозначения отрицательного значения BigInt
, например, -42n
.
Это хорошо, потому что любое неявное преобразование может привести к потере информации. Важный момент — в операциях нельзя смешивать BigInt
и Number
. Рассмотрим пример:
BigInt(Number.MAX_SAFE_INTEGER) + 2.5;
// → ??
Здесь нет правильного ответа. Чему должен быть равен результат? Поэтому операции с BigInt
и Number
приводят к исключению TypeError
. BigInt
не может содержать дробные числа, а Number
не может точно содержать большие числа больше безопасного целочисленного предела.
Единственным исключением из этого правила являются операторы сравнения, такие как ===
(обсуждался ранее), <
и >=
, поскольку они возвращают логические значения, не несущие риска потери точности.
1 + 1n;
// → TypeError
123 < 124n;
// → true
Решите, какой из этих двух типов вам нужен, и используйте его. Примечание: поскольку BigInt
и Number
обычно не смешиваются, не стоит переписывать уже существующий код с Number
на BigInt
. Однако, Number
как и прежде может использоваться для значений, которые гарантировано будут находиться в безопасном диапазоне целых чисел. Для новых API, которые работают с потенциально большими целыми числами, BigInt
— лучший выбор.
Поэтому >>>
не работает для чисел BigInt
. Также стоит заметить, что оператор >>>
, который выполняет беззнаковый сдвиг вправо, не имеет смысла для чисел BigInt
, поскольку они всегда содержат знак.
API
Стали доступными несколько новых API-методов для BigInt
.
Если преобразование завершается неудачно, будет выброшено исключение SyntaxError
или RangeError
. Глобальный конструктор BigInt
похож на конструктор Number
: он преобразует свой аргумент в BigInt
(как уже упоминалось ранее).
BigInt(123);
// → 123n
BigInt(1.5);
// → RangeError
BigInt('1.5');
// → SyntaxError
BigInt.asIntN(width, value)
ограничит число value
типа BigInt
указанным в width
числом бит с учётом знака, а BigInt.asUintN(width, value)
сделает то же самое, рассматривая значение как беззнаковое. Существуют две функции, позволяющие ограничивать значения BigInt
указанным числом значащих бит, рассматривая при этом число либо как знаковое, либо как беззнаковое. Например, если вам необходимы операции на 64-битными числами, вы можете использовать эти API, чтобы оставаться в соответствующем диапазоне:
// максимально возможное значение типа `BigInt`,
// которое может быть представлено как знаковое 64-битное целое число.
const max = 2n ** (64n - 1n) - 1n;
BigInt.asIntN(64, max);
→ 9223372036854775807n
BigInt.asIntN(64, max + 1n);
// → -9223372036854775808n
// ^ значение отрицательное, так как произошло переполнение
е. Обратите внимание, что переполнение происходит, как только мы передаем значение типа BigInt
, превышающее 64-разрядный целочисленный диапазон (т. 63 бита для самого значения и 1 бит для знака).
Два новых типизированных массива, BigInt64Array
и BigUint64Array
, упрощают работу с такими значениями: BigInt
позволяет точно представлять 64-битные знаковые и беззнаковые целые числа, которые обычно используются в других языках программирования.
const view = new BigInt64Array(4);
// → [0n, 0n, 0n, 0n]
view.length;
// → 4
view[0];
// → 0n
view[0] = 42n;
view[0];
// → 42n
BigInt64Array
гарантирует, что его значения будут в пределах возможных 64-битных значений со знаком.
// максимально возможное значение типа `BigInt`,
// которое может быть представлено как знаковое 64-битное целое число.
const max = 2n ** (64n - 1n) - 1n;
view[0] = max;
view[0];
// → 9_223_372_036_854_775_807n
view[0] = max + 1n;
view[0];
// → -9_223_372_036_854_775_808n
// ^ значение отрицательное, так как произошло переполнение
BigUint64Array
работает аналогично для 64-битных значений без знака.
Получайте удовольствие с BigInt
!