Руководство по новому типу данных в JavaScript: BigInt

Дата публикации:Июль 28, 2019

В JavaScript тип Number не может безопасно представлять целочисленные значения, превышающие 2 в 53 степени. Это ограничение вынуждает разработчиков использовать неэффективные обходные пути и сторонние библиотеки. BigInt  – новый тип данных, призванный решить имеющуюся проблему.

Тип данных BigInt позволяет программистам на JavaScript представлять целочисленные значения, выходящие за рамки диапазона, поддерживаемого типом данных Number. Возможность представлять целые числа с произвольной точностью особенно важна при выполнении математических операций над большими целыми числами. С BigInt программисты больше не столкнутся с целочисленным переполнением.

Также вы можете безопасно работать с временными метками высокого разрешения, большими целочисленными идентификаторами и т.д., не используя обходных путей. BigInt в настоящее время является предложением на стадии 3. После добавления в спецификацию он станет вторым числовым типом данных в JavaScript, что приведет к расширению общего количества поддерживаемых типов данных до восьми.

  • Boolean
  • Null
  • Undefined
  • Number
  • BigInt
  • String
  • Symbol
  • Object

В данной статье мы подробно рассмотрим BigInt, расскажем, как он поможет обойти ограничения типа Number в JavaScript.

Проблема

Отсутствие явного целочисленного типа в JavaScript часто сбивает с толку программистов, пришедших из других языков. Многие языки программирования поддерживают несколько числовых типов, таких как float, double, integer, bignum, но в случае с JavaScript все обстоит иначе. В JavaScript все числа представлены в 64-битном формате с плавающей запятой двойной точности, как определено в стандарте IEEE 754-2008.

Согласно этому стандарту очень большие целые числа, которые не могут быть точно представлены, автоматически округляются. Иными словами, тип Number в JavaScript может безопасно представлять только целые числа между -9007199254740991 (-(2^53-1)) и 9007199254740991 (2^53-1). Любое целочисленное значение, выходящее за пределы этого диапазона, может потерять точность.

Это можно легко проверить, выполнив следующий код:

console.log(9999999999999999);    // → 10000000000000000

Это целое число больше наибольшего числа, которое JavaScript может надежно представить с помощью примитива Number. Поэтому оно округляется. Неожиданное округление может поставить под угрозу безопасность программ. Вот еще один пример:

// notice the last digits

9007199254740992 === 9007199254740993;    // → true

JavaScript предлагает константу Number.MAX_SAFE_INTEGER, которая позволяет быстро получить максимальное целое число без округления в JavaScript. Точно так же вы можете получить и минимальное целое число, используя константу Number.MIN_SAFE_INTEGER:

const minInt = Number.MIN_SAFE_INTEGER;

 

console.log(minInt);         // → -9007199254740991

 

console.log(minInt - 5);     // → -9007199254740996

 

// notice how this outputs the same value as above

console.log(minInt - 4);     // → -9007199254740996

 

Решение

В качестве обхода этих ограничений некоторые JavaScript-разработчики представляют большие целые числа с помощью типа String. К примеру, Twitter API добавляет строковую версию идентфикаторов к объектам при ответе с помощью JSON. Кроме того, был разработан ряд библиотек, таких как bignumber.js, которые позволяют упростить работу с большими целыми числами.

Благодаря BigInt приложениям больше не требуется обходной путь или библиотеки для безопасного представления целых чисел за пределами Number.MAX_SAFE_INTEGER и Number.Min_SAFE_INTEGER. Арифметические операции над большими целыми числами теперь могут выполняться в стандартном JavaScript без риска потери точности. Дополнительным преимуществом использования собственного типа данных по сравнению со сторонней библиотекой является лучшая runtime-производительность.

Чтобы создать BigInt, просто добавьте n в конец целого числа. Сравните:

console.log(9007199254740995n);    // → 9007199254740995n

console.log(9007199254740995);     // → 9007199254740996

Альтернативно вы можете вызвать конструктор BigInt():

BigInt("9007199254740995");    // → 9007199254740995n

Литералы BigInt также могут быть записаны в двоичной, восьмеричной или шестнадцатеричной записях:

// binary

console.log(0b100000000000000000000000000000000000000000000000000011n);

// → 9007199254740995n

 

// hex

console.log(0x20000000000003n);

// → 9007199254740995n

 

// octal

console.log(0o400000000000000003n);

// → 9007199254740995n

 

// note that legacy octal syntax is not supported

console.log(0400000000000000003n);

// → SyntaxError

 

Имейте в виду, что вы не можете использовать оператор строгого равенства для сравнения BigInt с обычным числом, поскольку они разных типов:

console.log(10n === 10);    // → false

 

console.log(typeof 10n);    // → bigint

console.log(typeof 10);     // → number

Вместо этого вы можете использовать оператор равенства, который выполняет неявное преобразование типов перед сравнением операндов:

console.log(10n == 10);    // → true

В BigInts могут использоваться все арифметические операции, кроме унарного оператора плюс (+):

10n + 20n;    // → 30n

10n - 20n;    // → -10n

+10n;         // → TypeError: Cannot convert a BigInt value to a number

-10n;         // → -10n

10n * 20n;    // → 200n

20n / 10n;    // → 2n

23n % 10n;    // → 3n

10n ** 3n;    // → 1000n

 

const x = 10n;

++x;          // → 11n

--x;          // → 9n

 

Причина, по которой унарный оператор плюс не поддерживается, заключается в том, что некоторые программы могут полагаться на инвариантное поведение, когда + всегда создает Number, а в противном случае выдается ошибка Exception. Изменение поведения + приведет к нарушению кода asm.js.

Естественно, при использовании операндов с BigInt, арифметические операторы тоже будут возвращать значение BigInt. Поэтому результат оператора деления (/) будет автоматически округляться до ближайшего целого числа.

К примеру:

25 / 10;      // → 2.5

25n / 10n;    // → 2n

Неявное преобразование типов

Поскольку неявное преобразование типов может привести к потере информации, смешанные операции между BigInts и Numbers не допускаются. При смешивании больших целых чисел и чисел с плавающей запятой результирующее значение может не иметь точного представления в BigInt или Number. Рассмотрим следующий пример:

(9007199254740992n + 1n) + 0.5

Результат этого выражения находится вне области BigInt и Number. Число Number с дробной частью не может быть точно преобразовано в BigInt. И число BigInt больше 2^53 не может быть точно преобразовано Number.

В результате этого ограничения невозможно выполнять арифметические операции со смесью операндов Number и BigInt. Вы также не можете передавать BigInt в Web API и встроенные JavaScript-функции, ожидающие число Number. Попытка сделать это приведет к ошибке TypeError:

10 + 10n;    // → TypeError

Math.max(2n, 4n, 6n);    // → TypeError

Обратите внимание, что реляционные операторы не следуют этому правилу, что показано в примере:

10n > 5;    // → true

Если вы хотите выполнить арифметические вычисления с BigInt и Number, вам сначала нужно задать область (домен), в которой должна быть выполнена операция. Чтобы сделать это, просто конвертируйте любой из операндов, вызвав Number() или BigInt():

BigInt(10) + 10n;    // → 20n

// or

10 + Number(10n);    // → 20

При обнаружении в контексте Boolean значение BigInt будет обрабатываться аналогично Number. Иными словами, BigInt считается истинным значением, если оно не равно 0n:

if (5n) {

// this code block will be executed

}

 

if (0n) {

// but this code block won't

}

При сортировке массива BigInts и Numbers не происходит явного преобразования типов:

const arr = [3n, 4, 2, 1n, 0, -1n];

 

arr.sort();    // → [-1n, 0, 1n, 2, 3n, 4]

Битовые операторы, такие как |, &, <<, >>, ^, работают с BigInts аналогично Numbers. Отрицательные числа интерпретируются как второе дополнение бесконечной длины. Смешанные операнды не допускаются. Вот некоторые примеры:

90 | 115;      // → 123

90n | 115n;    // → 123n

90n | 115;     // → TypeError

&nbsp;

Конструктор BigInt

Как и в случае с другими примитивными типами, BigInt может быть создан с помощью функции-конструктора. Аргумент, переданный в BigInt(), автоматически преобразуется в BigInt, если это возможно:

BigInt("10");    // → 10n

BigInt(10);      // → 10n

BigInt(true);    // → 1n

Типы данных и значения, которые не могут быть преобразованы, выдают ошибку:

BigInt(10.2);     // → RangeError

BigInt(null);     // → TypeError

BigInt("abc");    // → SyntaxError

Вы можете напрямую выполнять арифметические операции над BigInt, созданным с помощью конструктора:

BigInt(10) * 10n;    // → 100n 

При использовании в качестве операндов для оператора строгого равенства, BigInts, созданные с помощью конструктора, обрабатываются аналогично обычным BigInt’ам:

BigInt(true) === 1n;    // → true 

Функции библиотеки

JavaScript предлагает две библиотечных функции для представления значений BigInt в виде целых чисел со знаком или без знака:

  • asUintN(width, BigInt). Преобразует BigInt в целое число между 0 и 2^width-1
  • BigInt.asIntN(width, BigInt). Преобразует BigInt в целое число между -2^(width-1) и 2^(width-1)-1

Эти функции особенно полезны при выполнении 64-битных арифметических операций. В результате этого вы сможете оставаться в пределах предполагаемого диапазона.

Поддержка браузерами и транспайлинг

На момент написания статьи Chrome +67 и Opera +54 полностью поддерживали тип данных BigInt. К сожалению, Edge и Safari пока еще не внедрили его. Firefox по умолчанию не поддерживает BigInt, но его можно включить, установив для javascript.options.bigint значение true в about:config.

Увы, но транспайлинг BigInt является чрезвычайно сложным процессом, который влечет за собой серьезные потери производительности во время выполнения. Также невозможно напрямую выполнить полифилл BigInt, поскольку предложение меняет поведение нескольких существующих операторов. На данный момент лучшей альтернативой является использование библиотеки JSBI, которая представляет собой реализацию BigInt-предложения на чистом JavaScript.

Библиотека предлагает API, который ведет себя точно так же, как и нативный BigInt. Вы можете использовать JSBI следующим образом:

&nbsp;

import JSBI from './jsbi.mjs';

&nbsp;

const b1 = JSBI.BigInt(Number.MAX_SAFE_INTEGER);

const b2 = JSBI.BigInt('10');

&nbsp;

const result = JSBI.add(b1, b2);

&nbsp;

console.log(String(result));    // → '9007199254741001'

&nbsp;

Преимущество использования JSBI заключается в том, что, как только браузерная поддержка увеличится, вам не нужно будет переписывать свой код. Вместо этого вы сможете просто автоматически скомпилировать свой JSBI-код в нативный BigInt-код с помощью плагина babel. Кроме того, производительность JSBI находится на одном уровне с нативной BigInt реализацией. В ближайшее время поддержка со стороны браузеров должна увеличиться.

Источник: https://www.smashingmagazine.com

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Получать новые комментарии по электронной почте. Вы можете подписаться без комментирования.