O guia essencial para o mais novo tipo de dados do JavaScript: BigInt
Publicados: 2022-03-10Number
não pode representar com segurança valores inteiros maiores que 2 53 . Essa limitação forçou os desenvolvedores a usar soluções alternativas ineficientes e bibliotecas de terceiros. BigInt
é um novo tipo de dados destinado a corrigir isso. O tipo de dados BigInt
visa permitir que programadores JavaScript representem valores inteiros maiores que o intervalo suportado pelo tipo de dados Number
. A capacidade de representar números inteiros com precisão arbitrária é particularmente importante ao realizar operações matemáticas em números inteiros grandes. Com BigInt
, o estouro de inteiros não será mais um problema.
Além disso, você pode trabalhar com segurança com carimbos de data/hora de alta resolução, IDs de números inteiros grandes e muito mais sem precisar usar uma solução alternativa. BigInt
é atualmente uma proposta de estágio 3. Uma vez adicionado à especificação, ele se tornará o segundo tipo de dados numérico em JavaScript, o que trará o número total de tipos de dados suportados para oito:
- boleano
- Nulo
- Indefinido
- Número
- BigIntName
- Corda
- Símbolo
- Objeto
Neste artigo, vamos dar uma boa olhada no BigInt
e ver como ele pode ajudar a superar as limitações do tipo Number
em JavaScript.
O problema
A falta de um tipo inteiro explícito em JavaScript é muitas vezes desconcertante para programadores vindos de outras linguagens. Muitas linguagens de programação suportam vários tipos numéricos, como float, double, integer e bignum, mas esse não é o caso do JavaScript. Em JavaScript, todos os números são representados no formato de ponto flutuante de 64 bits de precisão dupla, conforme definido pelo padrão IEEE 754-2008.
Sob esse padrão, números inteiros muito grandes que não podem ser representados exatamente são arredondados automaticamente. Para ser preciso, o tipo Number
em JavaScript só pode representar números inteiros com segurança entre -9007199254740991 (-(2 53 -1)) e 9007199254740991 (2 53 -1). Qualquer valor inteiro que esteja fora desse intervalo pode perder a precisão.
Isso pode ser facilmente examinado executando o seguinte código:
console.log(9999999999999999); // → 10000000000000000
Esse número inteiro é maior do que o maior número que o JavaScript pode representar de forma confiável com a primitiva Number
. Portanto, é arredondado. Arredondamentos inesperados podem comprometer a confiabilidade e a segurança de um programa. Aqui está outro exemplo:
// notice the last digits 9007199254740992 === 9007199254740993; // → true
JavaScript fornece a constante Number.MAX_SAFE_INTEGER
que permite obter rapidamente o número inteiro seguro máximo em JavaScript. Da mesma forma, você pode obter o número inteiro mínimo seguro usando a constante 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
A solução
Como solução para essas limitações, alguns desenvolvedores de JavaScript representam grandes números inteiros usando o tipo String
. A API do Twitter, por exemplo, adiciona uma versão string de IDs a objetos ao responder com JSON. Além disso, várias bibliotecas, como bignumber.js, foram desenvolvidas para facilitar o trabalho com números inteiros grandes.
Com BigInt
, os aplicativos não precisam mais de uma solução alternativa ou biblioteca para representar números inteiros com segurança além Number.MAX_SAFE_INTEGER
e Number.Min_SAFE_INTEGER
. Operações aritméticas em grandes números inteiros agora podem ser executadas em JavaScript padrão sem risco de perda de precisão. O benefício adicional de usar um tipo de dados nativo em uma biblioteca de terceiros é um melhor desempenho em tempo de execução.
Para criar um BigInt
, basta anexar n
ao final de um inteiro. Comparar:
console.log(9007199254740995n); // → 9007199254740995n console.log(9007199254740995); // → 9007199254740996
Como alternativa, você pode chamar o BigInt()
:
BigInt("9007199254740995"); // → 9007199254740995n
Literais BigInt
também podem ser escritos em notação binária, octal ou hexadecimal:
// 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
Lembre-se de que você não pode usar o operador de igualdade estrita para comparar um BigInt
com um número normal porque eles não são do mesmo tipo:
console.log(10n === 10); // → false console.log(typeof 10n); // → bigint console.log(typeof 10); // → number
Em vez disso, você pode usar o operador de igualdade, que realiza a conversão de tipo implícita antes de comparar seus operandos:
console.log(10n == 10); // → true
Todos os operadores aritméticos podem ser usados em BigInt
s, exceto o operador unário de mais ( +
):
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 let x = 10n; ++x; // → 11n --x; // → 10n
A razão pela qual o operador unário de mais ( +
) não é suportado é que alguns programas podem contar com a invariante que +
sempre produz um Number
ou lança uma exceção. Alterar o comportamento de +
também quebraria o código asm.js.
Naturalmente, quando usados com operandos BigInt
, espera-se que os operadores aritméticos retornem um valor BigInt
. Portanto, o resultado do operador de divisão ( /
) é truncado automaticamente. Por exemplo:
25 / 10; // → 2.5 25n / 10n; // → 2n
Conversão de tipo implícita
Como a conversão de tipo implícito pode perder informações, não são permitidas operações mistas entre BigInt
s e Number
s. Ao misturar números inteiros grandes e números de ponto flutuante, o valor resultante pode não ser representado com precisão por BigInt
ou Number
. Considere o seguinte exemplo:
(9007199254740992n + 1n) + 0.5
O resultado dessa expressão está fora do domínio de BigInt
e Number
. Um Number
com uma parte fracionária não pode ser convertido com precisão em um BigInt
. E um BigInt
maior que 2 53 não pode ser convertido com precisão em um Number
.
Como resultado dessa restrição, não é possível realizar operações aritméticas com uma combinação de operandos Number
e BigInt
. Você também não pode passar um BigInt
para APIs da Web e funções JavaScript integradas que esperam um Number
. Tentar fazer isso causará um TypeError
:
10 + 10n; // → TypeError Math.max(2n, 4n, 6n); // → TypeError
Observe que os operadores relacionais não seguem essa regra, conforme mostrado neste exemplo:
10n > 5; // → true
Se você deseja realizar cálculos aritméticos com BigInt
e Number
, primeiro você precisa determinar o domínio no qual a operação deve ser feita. Para fazer isso, basta converter um dos operandos chamando Number()
ou BigInt()
:
BigInt(10) + 10n; // → 20n // or 10 + Number(10n); // → 20
Quando encontrado em um contexto Boolean
, BigInt
é tratado de forma semelhante a Number
. Em outras palavras, um BigInt
é considerado um valor verdadeiro desde que não seja 0n
:
if (5n) { // this code block will be executed } if (0n) { // but this code block won't }
Nenhuma conversão de tipo implícita entre os tipos BigInt
e Number
ocorre ao classificar uma matriz:
const arr = [3n, 4, 2, 1n, 0, -1n]; arr.sort(); // → [-1n, 0, 1n, 2, 3n, 4]
Operadores bit a bit como |
, &
, <<
, >>
e ^
operam em BigInt
s de maneira semelhante a Number
s. Números negativos são interpretados como complemento de dois de comprimento infinito. Operandos mistos não são permitidos. aqui estão alguns exemplos:
90 | 115; // → 123 90n | 115n; // → 123n 90n | 115; // → TypeError
O construtor BigInt
Assim como outros tipos primitivos, um BigInt
pode ser criado usando uma função construtora. O argumento passado para BigInt()
é convertido automaticamente em um BigInt
, se possível:
BigInt("10"); // → 10n BigInt(10); // → 10n BigInt(true); // → 1n
Tipos de dados e valores que não podem ser convertidos lançam uma exceção:
BigInt(10.2); // → RangeError BigInt(null); // → TypeError BigInt("abc"); // → SyntaxError
Você pode realizar operações aritméticas diretamente em um BigInt
criado usando um construtor:
BigInt(10) * 10n; // → 100n
Quando usados como operandos do operador de igualdade estrita, BigInt
criados usando um construtor são tratados de forma semelhante aos regulares:
BigInt(true) === 1n; // → true
Funções da biblioteca
JavaScript fornece duas funções de biblioteca para representar valores BigInt
como inteiros assinados ou não assinados:
-
BigInt.asUintN(width, BigInt)
: envolve umBigInt
entre 0 e 2 largura -1 -
BigInt.asIntN(width, BigInt)
: envolve umBigInt
entre -2 largura-1 e 2 largura-1 -1
Essas funções são particularmente úteis ao realizar operações aritméticas de 64 bits. Dessa forma, você pode ficar dentro do intervalo pretendido.
Suporte ao navegador e transpilação
No momento da redação deste artigo, o Chrome +67 e o Opera +54 são totalmente compatíveis com o tipo de dados BigInt
. Infelizmente, Edge e Safari ainda não o implementaram. O Firefox não suporta BigInt
por padrão, mas pode ser ativado configurando javascript.options.bigint
como true
em about:config
. Uma lista atualizada de navegadores compatíveis está disponível em Posso usar….
Infelizmente, transpilar BigInt
é um processo extremamente complicado, que incorre em pesadas penalidades de desempenho em tempo de execução. Também é impossível fazer polyfill diretamente no BigInt
porque a proposta altera o comportamento de vários operadores existentes. Por enquanto, uma alternativa melhor é usar a biblioteca JSBI, que é uma implementação puramente JavaScript da proposta do BigInt
.
Essa biblioteca fornece uma API que se comporta exatamente da mesma forma que o BigInt
nativo. Veja como você pode usar o JSBI:
import JSBI from './jsbi.mjs'; const b1 = JSBI.BigInt(Number.MAX_SAFE_INTEGER); const b2 = JSBI.BigInt('10'); const result = JSBI.add(b1, b2); console.log(String(result)); // → '9007199254741001'
Uma vantagem de usar JSBI é que, uma vez que o suporte do navegador melhore, você não precisará reescrever seu código. Em vez disso, você pode compilar automaticamente seu código JSBI em código BigInt
nativo usando um plug-in babel. Além disso, o desempenho do JSBI está no mesmo nível das implementações nativas BigInt
. Você pode esperar um suporte mais amplo do navegador para BigInt
em breve.
Conclusão
BigInt
é um novo tipo de dados destinado ao uso quando os valores inteiros são maiores que o intervalo suportado pelo tipo de dados Number
. Esse tipo de dados nos permite realizar operações aritméticas com segurança em números inteiros grandes, representar carimbos de data e hora de alta resolução, usar IDs de números inteiros grandes e muito mais sem a necessidade de usar uma biblioteca.
É importante ter em mente que você não pode realizar operações aritméticas com uma combinação de operandos Number
e BigInt
. Você precisará determinar o domínio no qual a operação deve ser feita convertendo explicitamente qualquer um dos operandos. Além disso, por motivos de compatibilidade, você não tem permissão para usar o operador unário mais ( +
) em um BigInt
.
O que você acha? Você acha o BigInt
útil? Deixe-nos saber nos comentários!