La guía esencial para el tipo de datos más reciente de JavaScript: BigInt

Publicado: 2022-03-10
Resumen rápido ↬ En JavaScript, el tipo Number no puede representar con seguridad valores enteros mayores que 2 53 . Esta limitación ha obligado a los desarrolladores a utilizar soluciones alternativas ineficientes y bibliotecas de terceros. BigInt es un nuevo tipo de datos destinado a solucionar eso.

El tipo de datos BigInt tiene como objetivo permitir que los programadores de JavaScript representen valores enteros mayores que el rango admitido por el tipo de datos Number . La capacidad de representar números enteros con precisión arbitraria es especialmente importante cuando se realizan operaciones matemáticas con números enteros grandes. Con BigInt , el desbordamiento de enteros ya no será un problema.

Además, puede trabajar de manera segura con marcas de tiempo de alta resolución, ID de enteros grandes y más sin tener que usar una solución alternativa. BigInt es actualmente una propuesta de etapa 3. Una vez agregado a la especificación, se convertirá en el segundo tipo de datos numéricos en JavaScript, lo que elevará el número total de tipos de datos admitidos a ocho:

  • booleano
  • Nulo
  • Indefinido
  • Número
  • Empezando
  • Cuerda
  • Símbolo
  • Objeto

En este artículo, analizaremos detenidamente BigInt y veremos cómo puede ayudar a superar las limitaciones del tipo Number en JavaScript.

El problema

La falta de un tipo entero explícito en JavaScript a menudo desconcierta a los programadores que provienen de otros lenguajes. Muchos lenguajes de programación admiten múltiples tipos numéricos, como float, double, integer y bignum, pero ese no es el caso con JavaScript. En JavaScript, todos los números se representan en formato de punto flotante de 64 bits de doble precisión, según lo define el estándar IEEE 754-2008.

¡Más después del salto! Continúe leyendo a continuación ↓

Según este estándar, los números enteros muy grandes que no se pueden representar exactamente se redondean automáticamente. Para ser precisos, el tipo Number en JavaScript solo puede representar con seguridad números enteros entre -9007199254740991 (-(2 53 -1)) y 9007199254740991 (2 53 -1). Cualquier valor entero que quede fuera de este rango puede perder precisión.

Esto se puede examinar fácilmente ejecutando el siguiente código:

 console.log(9999999999999999); // → 10000000000000000

Este entero es mayor que el número más grande que JavaScript puede representar de manera confiable con la primitiva Number . Por lo tanto, es redondeado. El redondeo inesperado puede comprometer la confiabilidad y seguridad de un programa. Aquí hay otro ejemplo:

 // notice the last digits 9007199254740992 === 9007199254740993; // → true

JavaScript proporciona la constante Number.MAX_SAFE_INTEGER que le permite obtener rápidamente el entero seguro máximo en JavaScript. De manera similar, puede obtener el número entero seguro mínimo utilizando la 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

La solución

Como solución a estas limitaciones, algunos desarrolladores de JavaScript representan números enteros grandes utilizando el tipo String . La API de Twitter, por ejemplo, agrega una versión de cadena de ID a los objetos cuando responde con JSON. Además, se han desarrollado varias bibliotecas, como bignumber.js, para facilitar el trabajo con números enteros grandes.

Con BigInt , las aplicaciones ya no necesitan una solución alternativa o una biblioteca para representar de manera segura números enteros más allá Number.MAX_SAFE_INTEGER y Number.Min_SAFE_INTEGER . Las operaciones aritméticas con números enteros grandes ahora se pueden realizar en JavaScript estándar sin riesgo de pérdida de precisión. El beneficio adicional de usar un tipo de datos nativo en lugar de una biblioteca de terceros es un mejor rendimiento en tiempo de ejecución.

Para crear un BigInt , simplemente agregue n al final de un número entero. Comparar:

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

Alternativamente, puede llamar al constructor BigInt() :

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

Los literales BigInt también se pueden escribir en notación binaria, octal o 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

Tenga en cuenta que no puede usar el operador de igualdad estricta para comparar un BigInt con un número regular porque no son del mismo tipo:

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

En su lugar, puede utilizar el operador de igualdad, que realiza una conversión de tipo implícita antes de comparar sus operandos:

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

Todos los operadores aritméticos se pueden usar en BigInt excepto el operador unario más ( + ):

 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

La razón por la que el operador unario más ( + ) no es compatible es que algunos programas pueden confiar en el invariante que + siempre produce un Number o genera una excepción. Cambiar el comportamiento de + también rompería el código asm.js.

Naturalmente, cuando se usan con operandos BigInt , se espera que los operadores aritméticos devuelvan un valor BigInt . Por lo tanto, el resultado del operador de división ( / ) se trunca automáticamente. Por ejemplo:

 25 / 10; // → 2.5 25n / 10n; // → 2n

Conversión de tipo implícita

Debido a que la conversión implícita de tipos podría perder información, no se permiten operaciones mixtas entre BigInt s y Number s. Al mezclar números enteros grandes y números de punto flotante, es posible que BigInt o Number no puedan representar con precisión el valor resultante. Considere el siguiente ejemplo:

 (9007199254740992n + 1n) + 0.5

El resultado de esta expresión está fuera del dominio de BigInt y Number . Un Number con una parte fraccionaria no se puede convertir con precisión en BigInt . Y un BigInt mayor que 2 53 no se puede convertir con precisión en un Number .

Como resultado de esta restricción, no es posible realizar operaciones aritméticas con una combinación de operandos Number y BigInt . Tampoco puede pasar un BigInt a las API web y las funciones de JavaScript integradas que esperan un Number . Intentar hacerlo causará un TypeError :

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

Tenga en cuenta que los operadores relacionales no siguen esta regla, como se muestra en este ejemplo:

 10n > 5; // → true

Si desea realizar cálculos aritméticos con BigInt y Number , primero debe determinar el dominio en el que se debe realizar la operación. Para hacer eso, simplemente convierta cualquiera de los operandos llamando a Number() o BigInt() :

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

Cuando se encuentra en un contexto Boolean , BigInt se trata de forma similar a Number . En otras palabras, un BigInt se considera un valor real siempre que no sea 0n :

 if (5n) { // this code block will be executed } if (0n) { // but this code block won't }

No se produce ninguna conversión de tipo implícita entre los tipos BigInt y Number al ordenar una matriz:

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

Operadores bit a bit como | , & , << , >> y ^ operan en BigInt s de manera similar a Number s. Los números negativos se interpretan como complemento a dos de longitud infinita. No se permiten operandos mixtos. Aquí hay unos ejemplos:

 90 | 115; // → 123 90n | 115n; // → 123n 90n | 115; // → TypeError

El constructor BigInt

Al igual que con otros tipos primitivos, se puede crear un BigInt usando una función constructora. El argumento pasado a BigInt() se convierte automáticamente en BigInt , si es posible:

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

Los tipos de datos y valores que no se pueden convertir arrojan una excepción:

 BigInt(10.2); // → RangeError BigInt(null); // → TypeError BigInt("abc"); // → SyntaxError

Puede realizar operaciones aritméticas directamente en un BigInt creado con un constructor:

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

Cuando se usan como operandos del operador de igualdad estricta, los BigInt creados con un constructor se tratan de manera similar a los normales:

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

Funciones de biblioteca

JavaScript proporciona dos funciones de biblioteca para representar valores BigInt como enteros con o sin signo:

  • BigInt.asUintN(width, BigInt) : envuelve un BigInt entre 0 y 2 ancho -1
  • BigInt.asIntN(width, BigInt) : envuelve un BigInt entre -2 ancho-1 y 2 ancho-1 -1

Estas funciones son particularmente útiles cuando se realizan operaciones aritméticas de 64 bits. De esta manera, puede permanecer dentro del rango previsto.

Compatibilidad con navegadores y transpilación

En el momento de escribir este artículo, Chrome +67 y Opera +54 son totalmente compatibles con el tipo de datos BigInt . Desafortunadamente, Edge y Safari aún no lo han implementado. Firefox no es compatible con BigInt de forma predeterminada, pero se puede habilitar configurando javascript.options.bigint en true en about:config . Una lista actualizada de navegadores compatibles está disponible en ¿Puedo usar...?

Desafortunadamente, la transpilación de BigInt es un proceso extremadamente complicado, que incurre en una fuerte penalización de rendimiento en tiempo de ejecución. También es imposible BigInt directamente porque la propuesta cambia el comportamiento de varios operadores existentes. Por ahora, una mejor alternativa es usar la biblioteca JSBI, que es una implementación de JavaScript puro de la propuesta de BigInt .

Esta biblioteca proporciona una API que se comporta exactamente igual que el BigInt nativo. Así es como puede usar 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'

Una ventaja de usar JSBI es que una vez que mejore la compatibilidad con el navegador, no necesitará volver a escribir su código. En su lugar, puede compilar automáticamente su código JSBI en código BigInt nativo mediante un complemento de babel. Además, el rendimiento de JSBI está a la par con las implementaciones nativas de BigInt . Puede esperar una compatibilidad de navegador más amplia para BigInt pronto.

Conclusión

BigInt es un nuevo tipo de datos diseñado para usarse cuando los valores enteros son mayores que el rango admitido por el tipo de datos Number . Este tipo de datos nos permite realizar de manera segura operaciones aritméticas en números enteros grandes, representar marcas de tiempo de alta resolución, usar ID de números enteros grandes y más sin la necesidad de usar una biblioteca.

Es importante tener en cuenta que no puede realizar operaciones aritméticas con una combinación de operandos Number y BigInt . Deberá determinar el dominio en el que se debe realizar la operación mediante la conversión explícita de cualquiera de los operandos. Además, por razones de compatibilidad, no está permitido usar el operador unario más ( + ) en un BigInt .

¿Qué piensas? ¿Encuentras BigInt útil? ¡Cuéntanos en los comentarios!