Number en JavaScript: precisión, límites y cálculos confiables

Última actualización el
Number en JavaScript: precisión, límites y cálculos confiables

En JavaScript, number parece un tipo sencillo: sumas, restas, multiplicas y divides. Pero en cuanto trabajas con dinero, porcentajes, IDs grandes, datos de formularios o cálculos que deben ser exactos, aparecen detalles importantes. Entender cómo funciona Number evita bugs silenciosos y decisiones técnicas equivocadas.

Un solo tipo para enteros y decimales

JavaScript usa un único tipo number para representar enteros y decimales:

console.log(typeof 42); // "number"
console.log(typeof 3.14); // "number"
console.log(3 === 3.0); // true

Internamente, los números usan el formato IEEE 754 de 64 bits. Esto permite trabajar con un rango enorme de valores, pero también introduce límites de precisión.

El problema clásico: 0.1 + 0.2

Los decimales no siempre pueden representarse de forma exacta en binario:

console.log(0.1 + 0.2); // 0.30000000000000004
console.log(0.1 + 0.2 === 0.3); // false

Esto no es un bug de JavaScript; es una consecuencia de cómo funcionan los números de punto flotante. Para comparar decimales, usa una tolerancia:

function nearlyEqual(a, b) {
  return Math.abs(a - b) < Number.EPSILON;
}

console.log(nearlyEqual(0.1 + 0.2, 0.3)); // true

Para dinero, normalmente conviene trabajar en unidades enteras. Por ejemplo, guardar centavos en lugar de pesos o dólares:

const priceInCents = 1299;
const quantity = 3;
const totalInCents = priceInCents * quantity;

console.log(totalInCents); // 3897

NaN e Infinity

JavaScript incluye valores numéricos especiales:

console.log(10 / 0); // Infinity
console.log(-10 / 0); // -Infinity
console.log(Number('hello')); // NaN

NaN significa “Not a Number”, pero su comportamiento puede confundir:

console.log(NaN === NaN); // false
console.log(Number.isNaN(NaN)); // true

Usa Number.isNaN() para comprobar si un valor es realmente NaN. Evita el isNaN() global cuando puedas, porque primero convierte el valor y puede dar resultados inesperados.

Parsear números desde strings

Los datos de un input llegan como texto. Para convertirlos:

const age = Number('25');
const price = Number.parseFloat('19.99');
const page = Number.parseInt('12', 10);

Number() exige que todo el string sea numérico:

console.log(Number('12px')); // NaN
console.log(Number.parseInt('12px', 10)); // 12

Esa diferencia es útil, pero también peligrosa. Si estás validando formularios, normalmente quieres detectar entradas inválidas en lugar de aceptar un número parcial.

Redondeo y formato

toFixed() devuelve un string, no un number:

const value = 12.345;
const rounded = value.toFixed(2);

console.log(rounded); // "12.35"
console.log(typeof rounded); // "string"

Para mostrar moneda o números con formato local, usa Intl.NumberFormat:

const formatter = new Intl.NumberFormat('es-AR', {
  style: 'currency',
  currency: 'ARS',
});

console.log(formatter.format(1299.5));

Separar cálculo y presentación mantiene el código más sano: calcula con números, muestra con formateadores.

Límites seguros

JavaScript puede representar números muy grandes, pero no todos los enteros grandes son seguros:

console.log(Number.MAX_SAFE_INTEGER); // 9007199254740991
console.log(Number.isSafeInteger(9007199254740992)); // false

Si un ID viene de una base de datos y supera ese límite, no lo trates como number. Guárdalo como string o usa BigInt si necesitas operar matemáticamente con él.

BigInt

BigInt permite representar enteros más grandes que el límite seguro:

const huge = 123456789012345678901234567890n;
console.log(typeof huge); // "bigint"

Pero no puedes mezclar number y bigint directamente:

const count = 10n;
// count + 1; // TypeError
console.log(count + 1n); // 11n

Úsalo para enteros grandes, no para decimales ni dinero con centavos.

Math para operaciones comunes

El objeto Math incluye utilidades frecuentes:

console.log(Math.round(4.6)); // 5
console.log(Math.floor(4.6)); // 4
console.log(Math.ceil(4.1)); // 5
console.log(Math.max(3, 9, 1)); // 9

Math.random() sirve para valores aleatorios simples, pero no para seguridad, tokens o criptografía. Para eso existe crypto.getRandomValues().

Buenas prácticas

  • Convierte strings a números de forma explícita.
  • Valida con Number.isNaN() y Number.isFinite().
  • No compares decimales exactos si hubo operaciones de punto flotante.
  • Para dinero, usa enteros menores, como centavos.
  • No trates IDs gigantes como number; usa string o BigInt.
  • Usa Intl.NumberFormat para presentar números al usuario.

Conclusión

Number es suficiente para la mayoría de cálculos cotidianos, pero no es mágico. Saber dónde pierde precisión, cómo validar entradas y cuándo usar BigInt o enteros para dinero te ayuda a escribir software más confiable. Los bugs numéricos suelen ser silenciosos, así que conviene prevenirlos desde el diseño.