
Para mostrar un número, usa Intl.NumberFormat o su atajo toLocaleString(): (1234.5).toLocaleString('fr-FR', { minimumFractionDigits: 2 }) da «1 234,50», y la opción style: 'currency' añade la divisa. Para redondear sin mostrar, Math.round(n * 100) / 100; toFixed(2) también redondea, pero devuelve una cadena.
Un número se calcula en Number y se muestra como cadena: «1 234,50 €», «25,6 %», «1,4 k visitas». Entre lo uno y lo otro hay que redondear, colocar un separador de miles y elegir la coma o el punto. JavaScript hace todo eso sin ninguna biblioteca gracias a toFixed, toLocaleString y sobre todo Intl.NumberFormat. Todas las salidas de abajo se han ejecutado en Node.js 22.
Redondear un número
Math.round, Math.floor, Math.ceil, Math.trunc
Math.round(2.5); // 3 : al más cercano, .5 sube
Math.round(-2.5); // -2 : hacia +Infinito, no «lejos de cero»
Math.floor(4.7); // 4 : hacia abajo
Math.floor(-4.2); // -5
Math.ceil(4.2); // 5 : hacia arriba
Math.trunc(-4.7); // -4 : corta la parte decimal, sea cual sea el signoEstas funciones solo conocen el entero. Para redondear a dos decimales se multiplica, se redondea y se divide:
Math.round(1.345 * 100) / 100; // 1.35
Math.round(1.005 * 100) / 100; // 1 : se esperaba 1.01, ver más abajoLa trampa de los decimales binarios
1.005 no existe en binario: el número almacenado vale 1.00499999… y se redondea, por tanto, a 1.00. No es un fallo de JavaScript, sino del formato IEEE 754, común a todos los lenguajes. Dos remedios:
// 1. Corregir el error de representación antes de redondear
Math.round((1.005 + Number.EPSILON) * 100) / 100; // 1.01
// 2. No almacenar nunca decimales: los precios en céntimos enteros
const prixCentimes = 1005;
(prixCentimes / 100).toFixed(2); // '10.05'El segundo es la regla para todo lo que tiene que ver con el dinero: sumar céntimos enteros nunca produce 0.30000000000000004.
toFixed(): redondear para mostrar
(1234.5678).toFixed(2); // '1234.57'
(2.5).toFixed(0); // '3'
(1.005).toFixed(2); // '1.00' : la misma trampa binaria que Math.round
(0.1 + 0.2).toFixed(2); // '0.30' : justo para esto sirve
typeof (1.5).toFixed(2); // 'string'
(1.5).toFixed(2) + 1; // '1.501' : concatenación, no sumatoFixed devuelve una cadena. Es perfecto para mostrar y peligroso para calcular: conviértela con Number() si tienes que reutilizar el valor. Y su punto decimal siempre es un punto; para la coma francesa, pasa por toLocaleString.
Separador de miles y formato francés: toLocaleString()
const n = 1234567.891;
n.toLocaleString('fr-FR'); // '1 234 567,891'
n.toLocaleString('en-US'); // '1,234,567.891'
n.toLocaleString('de-DE'); // '1.234.567,891'
(1234.5).toLocaleString('fr-FR', { minimumFractionDigits: 2 }); // '1 234,50'
(3.14159).toLocaleString('fr-FR', { maximumFractionDigits: 2 }); // '3,14'Un método, tres convenciones nacionales, ni una línea de código de formateo. Dos precisiones:
- El separador de miles francés no es un espacio normal, sino un espacio fino inseparable (U+202F). Impide que «1 234» se parta al final de la línea. Si tienes que quitarlo, filtra con
/\s/g, no con' '. - Sin argumento de idioma,
toLocaleString()toma el del navegador o el del sistema. Para un resultado estable, indica siempre'fr-FR'.
Intl.NumberFormat: divisa, porcentaje, unidades
toLocaleString es un atajo hacia Intl.NumberFormat. El objeto completo se crea una vez y se reutiliza, algo que cuenta en cuanto formateas una lista o una tabla.
const euros = new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' });
euros.format(1234.5); // '1 234,50 €'
const dollars = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' });
dollars.format(1234.5); // '$1,234.50'
const pourcent = new Intl.NumberFormat('fr-FR', { style: 'percent', maximumFractionDigits: 1 });
pourcent.format(0.256); // '25,6 %' : el valor es un ratio, 0.256 y no 25.6
const km = new Intl.NumberFormat('fr-FR', { style: 'unit', unit: 'kilometer' });
km.format(12.5); // '12,5 km'
const go = new Intl.NumberFormat('fr-FR', { style: 'unit', unit: 'gigabyte', maximumFractionDigits: 2 });
go.format(2.35); // '2,35 Go'El formateador de divisa coloca el símbolo donde toca según el idioma (detrás en francés, delante en inglés), elige el número de decimales de la moneda e inserta el espacio inseparable. Otras tantas reglas que ya no hay que programar.
Notación compacta: 1,4 k y 1 M
Mostrar «1 400 visitas» como «1,4 k», o «1 000 000» como «1 M», es una necesidad habitual. La opción notation: 'compact' lo hace, en el idioma que quieras.
const compact = new Intl.NumberFormat('fr-FR', { notation: 'compact' });
compact.format(1400); // '1,4 k'
compact.format(1000000); // '1 M'
new Intl.NumberFormat('en-US', { notation: 'compact' }).format(23000); // '23K'
new Intl.NumberFormat('en-US', { notation: 'compact', maximumFractionDigits: 3 }).format(1230974); // '1.231M'
new Intl.NumberFormat('fr-FR', { notation: 'compact', compactDisplay: 'long' }).format(2500000); // '2,5 millions'Otras opciones útiles:
new Intl.NumberFormat('fr-FR', { minimumIntegerDigits: 2 }).format(7); // '07'
new Intl.NumberFormat('fr-FR', { useGrouping: false }).format(1234567); // '1234567'
new Intl.NumberFormat('fr-FR', { signDisplay: 'always' }).format(12); // '+12'
new Intl.NumberFormat('en-US', { notation: 'scientific' }).format(676565765722); // '6.766E11'
new Intl.NumberFormat('fr-FR', { maximumFractionDigits: 2, roundingMode: 'halfExpand' }).format(1.005); // '1,01'La última línea merece un comentario: Intl.NumberFormat redondea a partir de la representación decimal del número, no de su aproximación binaria. 1.005 sí se convierte en «1,01», donde toFixed daba «1.00». Una razón más para formatear con Intl en vez de a mano.
Ceros iniciales y otras bases
String(7).padStart(2, '0'); // '07' : horas, minutos, números de factura
String(7).padStart(3, '0'); // '007'
(255).toString(16); // 'ff' : hexadecimal, para un color CSS
(255).toString(2); // '11111111' : binario
(123.456).toPrecision(4); // '123.5' : número total de cifras significativas
(1234.5678).toExponential(2); // '1.23e+3'Dos comportamientos de toString() que conviene conocer: por encima de 1021 y por debajo de 10−6, JavaScript cambia a notación científica ((1e21).toString() da '1e+21' y (0.0000001).toString() da '1e-7'). toFixed o Intl.NumberFormat con notation: 'standard' conservan la escritura desarrollada.
La operación inversa
Una cadena formateada no se reconvierte tal cual: Number('1 234,50') vale NaN. Hay que quitar los espacios y sustituir la coma antes de convertir. El procedimiento completo está en Convertir una cadena en número en JavaScript.
¿Y Numeral.js?
La primera versión de este artículo presentaba Numeral.js, una biblioteca con un formato compacto: numeral(50000).format('0,0') para «50,000», '0a' para «23k», '0.00b' para «2.35GB», '0o' para «23rd». Sigue presente en muchos proyectos y su sintaxis es agradable.
import numeral from 'numeral';
numeral(50000).format('0,0'); // 50,000
numeral(23000).format('0a'); // 23k
numeral(2348895676).format('0.00b'); // 2.35GBPero su última versión, la 2.0.6, es de marzo de 2017 y el proyecto ya no se mantiene: las locales están congeladas y el separador francés no es el espacio fino que se espera. Para un proyecto nuevo, Intl.NumberFormat cubre las mismas necesidades (miles, compacto, divisa, porcentaje) sin ninguna dependencia, con las convenciones reales de cada idioma y sin peso en el bundle. Conserva Numeral donde ya está; no lo añadas más.
Resumen
| Necesidad | Herramienta | Ejemplo |
|---|---|---|
| Redondear para calcular | Math.round(n * 100) / 100 o céntimos enteros | 1.35 |
| Redondear para mostrar, punto decimal | toFixed(2) | '1234.57' |
| Formato francés, miles y coma | toLocaleString('fr-FR') | '1 234 567,891' |
| Divisa, porcentaje, unidad | Intl.NumberFormat + style | '1 234,50 €' |
| 1,4 k / 1 M | notation: 'compact' | '1,4 k' |
| Ceros iniciales | padStart(2, '0') | '07' |
Errores frecuentes
(1.5).toFixed(2) + 1 da «1.501»: toFixed devuelve una cadena y la suma concatena. Conviértelo con Number() si tienes que volver a calcular.(1.005).toFixed(2) vale «1.00» porque 1.005 se almacena como 1.00499999… en binario. Para un redondeo financiero, trabaja con céntimos enteros.toLocaleString('fr-FR') por un espacio normal El separador de miles francés es un espacio fino inseparable (U+202F). Un replace(' ', '') no lo quita; usa /\s/g.format() con cada valor.

