Un único tipo para (casi) todos los números
La mayoría de los lenguajes te dan tipos distintos para enteros y decimales. JavaScript, históricamente, solo te ofrecía uno: Number. Daba igual que escribieras 42, 3.14 o -0.001: siempre obtenías la misma primitiva, un número de coma flotante de doble precisión de 64 bits según el estándar IEEE 754.
Qué cómodo: no hay que hacer casting entre int y float, ni tampoco se desborda nada al llegar a 2^31. Pero representar todo como flotante tiene sus consecuencias, y suelen pillar por sorpresa a quien está empezando. En 2020 se añadió un segundo tipo numérico, BigInt, justamente para cubrir los casos donde Number se queda corto.
El clásico tropiezo con la coma flotante
Ejecuta esto:
La primera línea imprime 0.30000000000000004. La segunda imprime false. Y no, no es una rareza de JavaScript: Python, Java, C y cualquier lenguaje que use flotantes IEEE 754 se comporta igual.
¿Por qué pasa esto? 0.1 y 0.2 no se pueden representar de forma exacta en binario, igual que 1/3 no se puede escribir con precisión en decimal. Lo que se guarda es la aproximación binaria más cercana, y esos pequeños errores se van acumulando. El modelo mental útil es este: piensa en los valores Number con decimales como aproximaciones que resultan estar muy cerca de lo que escribiste.
Si trabajas con dinero, no guardes $19.99 como 19.99. Guarda los centavos como enteros —1999— y formatéalos al mostrarlos. Es el mejor hábito que puedes adoptar para esquivar los errores de precisión de coma flotante en JavaScript.
Cómo comparar flotantes sin llevarte sustos
Como la igualdad directa no es fiable, cuando necesites comparar decimales hazlo con una tolerancia:
Number.EPSILON es la diferencia más pequeña entre 1 y el siguiente número representable; sirve como tolerancia por defecto razonable para valores cercanos a 1. Cuando trabajes con magnitudes muy grandes o muy pequeñas, vas a querer una tolerancia que se ajuste a la escala de los valores.
El rango de enteros seguros en JavaScript
Los enteros hasta cierto tamaño sí se representan de forma exacta en un float de 64 bits. A partir de ahí, empiezas a perder precisión bit a bit:
2^53 - 1 es el último entero a partir del cual todos los enteros menores se pueden representar sin problema. Pasado ese punto, algunos enteros simplemente no existen en el tipo Number: se redondean al vecino más cercano. Ahí tienes un bug silencioso de corrupción de datos listo para aparecer si estás parseando IDs de 64 bits desde una base de datos como números JSON normales.
Llega BigInt
BigInt es un primitivo aparte pensado para enteros de precisión arbitraria. Para crear uno, basta con añadir n al final de un literal entero, o llamar a BigInt(...):
Los BigInt no tienen más límite que la memoria disponible. Son la herramienta ideal para:
- IDs de bases de datos o los snowflake IDs de Twitter/X que superan
2^53. - Matemática criptográfica.
- Cualquier cálculo entero donde la exactitud importe más que la velocidad pura.
En cambio, no son la mejor opción para contadores del día a día, índices de arrays o dinero representado en céntimos: ahí el Number de toda la vida es más rápido y se lleva bien con todas las APIs del lenguaje.
Operaciones aritméticas con BigInt
Todos los operadores habituales funcionan, siempre y cuando ambos operandos sean BigInt:
La división trunca hacia cero: los BigInt no admiten parte decimal. Si necesitas fracciones, vuelves al terreno de Number (o tiras de una librería decimal).
No mezcles tipos
La regla con la que todo el mundo tropieza: no puedes mezclar Number y BigInt en la misma expresión.
La comparación es la única excepción: <, >, == sí hacen coerción entre ambos tipos:
Entonces, == los considera iguales, pero === no. Si ya usas === en todas partes (y deberías), ver comparaciones numéricas entre estos dos tipos es una señal de alerta en el diseño: decide con cuál te quedas y haz la conversión.
Cómo convertir entre number y bigint
Dos conversiones, dos trampas a tener en cuenta:
Pasar de Number a BigInt es estricto: los decimales y NaN lanzan error. El camino inverso, de BigInt a Number, es permisivo pero pierde precisión — cualquier valor por encima de MAX_SAFE_INTEGER se redondea. Si vas a convertir un BigInt que te llegó desde el servidor, pregúntate primero si realmente hace falta.
Valores especiales del tipo Number
Ya que estamos, conviene mencionar tres valores dentro del tipo Number que, en términos matemáticos, no son números:
Infinity y -Infinity aparecen cuando divides entre cero o te pasas del rango que puede representar un float. NaN ("not a number") aparece cuando una operación aritmética no da un resultado con sentido.
Lo curioso de NaN es que no es igual a sí mismo — y eso no es un bug de JS, viene del estándar IEEE 754. Para comprobarlo usa Number.isNaN(x). El isNaN global de toda la vida primero convierte el argumento, así que da resultados raros (isNaN("hello") devuelve true). Tira siempre de Number.isNaN.
Convertir strings a números en JavaScript
Lo que escribe el usuario y los números que vienen en JSON suelen llegar como strings. Tienes tres formas de convertirlos:
Number() es estricto: cualquier cosa no numérica devuelve NaN, salvo la cadena vacía y los espacios en blanco, que dan 0. En cambio, parseInt y parseFloat son permisivos: leen hasta donde pueden y se detienen. Elige el que encaje con tu intención y comprueba si el resultado es NaN antes de usarlo.
Para parsear BigInts desde cadenas, usa BigInt("123"): es estricto y lanza una excepción si la entrada es inválida.
Reglas rápidas
- Para contadores, operaciones matemáticas, coordenadas y la mayoría de números del día a día: usa
Number. - Para dinero: escala a céntimos enteros y usa
Number, o recurre a una librería decimal. - Para enteros más grandes que
2^53(IDs de bases de datos, criptografía, combinatoria): usaBigIntcon el sufijon. - Compara números de coma flotante con una tolerancia, nunca con
===. - Detecta resultados inválidos con
Number.isNaNyNumber.isFinite, no con las versiones globales. - No mezcles
NumberyBigInten la misma expresión: convierte de forma explícita.
A continuación: null vs undefined
JavaScript tiene dos formas de expresar "sin valor" —null y undefined— y no son intercambiables. En el siguiente apartado veremos qué significa cada uno, en qué se diferencian y cuándo conviene usar uno u otro.
Preguntas frecuentes
¿Por qué 0.1 + 0.2 no da 0.3 en JavaScript?
Porque el tipo Number de JavaScript es un flotante de 64 bits según el estándar IEEE 754, y ni 0.1 ni 0.2 se pueden representar de forma exacta en binario. El resultado termina siendo 0.30000000000000004. No es un bug de JavaScript: pasa exactamente igual en Python, Java y cualquier otro lenguaje que use ese mismo formato de coma flotante. Si manejas dinero, trabaja con enteros (céntimos) o tira de una librería decimal.
¿Qué es BigInt en JavaScript y cuándo debería usarlo?
BigInt es un primitivo numérico aparte, pensado para enteros que superan Number.MAX_SAFE_INTEGER (2^53 - 1). Se crea añadiendo una n al final —por ejemplo 9007199254740993n— o con BigInt(valor). Te interesa para IDs de base de datos de 64 bits, criptografía o cualquier cálculo entero donde la precisión importe más que la velocidad.
¿Puedo mezclar Number y BigInt en JavaScript?
No. Una operación como 1n + 1 lanza TypeError: Cannot mix BigInt and other types. Tienes que convertir explícitamente con BigInt(n) o Number(b). Los operadores de comparación como < o == sí funcionan entre los dos tipos, pero === devuelve false porque los tipos son distintos.