O guia essencial para o mais novo tipo de dados do JavaScript: BigInt

Publicados: 2022-03-10
Resumo rápido ↬ Em JavaScript, o tipo Number 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.

Mais depois do salto! Continue lendo abaixo ↓

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 um BigInt entre 0 e 2 largura -1
  • BigInt.asIntN(width, BigInt) : envolve um BigInt 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!