
To display a number, use Intl.NumberFormat or its shortcut toLocaleString(): (1234.5).toLocaleString('fr-FR', { minimumFractionDigits: 2 }) gives “1 234,50”, and the style: 'currency' option adds the currency. To round without displaying, Math.round(n * 100) / 100; toFixed(2) rounds too but returns a string.
A number is calculated as a Number and displayed as a string: “1 234,50 €”, “25,6 %”, “1,4 k views” on a French site. In between, you have to round, insert a thousands separator, pick a comma or a point. JavaScript does all of that without a library, thanks to toFixed, toLocaleString and above all Intl.NumberFormat. Every output below was run under Node.js 22.
Rounding a number
Math.round, Math.floor, Math.ceil, Math.trunc
Math.round(2.5); // 3 : to the nearest, .5 rounds up
Math.round(-2.5); // -2 : towards +Infinity, not “away from zero”
Math.floor(4.7); // 4 : downwards
Math.floor(-4.2); // -5
Math.ceil(4.2); // 5 : upwards
Math.trunc(-4.7); // -4 : drops the decimal part, whatever the signThese functions only know about integers. To round to two decimal places, multiply, round, divide:
Math.round(1.345 * 100) / 100; // 1.35
Math.round(1.005 * 100) / 100; // 1 : expected 1.01, see belowThe binary decimals trap
1.005 does not exist in binary: the stored number is 1.00499999… and therefore rounds to 1.00. That is not a JavaScript bug but a property of the IEEE 754 format, shared by every language. Two workarounds:
// 1. Correct the representation error before rounding
Math.round((1.005 + Number.EPSILON) * 100) / 100; // 1.01
// 2. Never store decimals: prices in whole cents
const prixCentimes = 1005;
(prixCentimes / 100).toFixed(2); // '10.05'The second is the rule for anything involving money: adding whole cents never produces 0.30000000000000004.
toFixed(): rounding for display
(1234.5678).toFixed(2); // '1234.57'
(2.5).toFixed(0); // '3'
(1.005).toFixed(2); // '1.00' : same binary trap as Math.round
(0.1 + 0.2).toFixed(2); // '0.30' : exactly what it is for
typeof (1.5).toFixed(2); // 'string'
(1.5).toFixed(2) + 1; // '1.501' : concatenation, not additiontoFixed returns a string. That is perfect for display and dangerous for calculation: convert with Number() if you need to reuse the value. And its decimal separator is always a point; for the French comma, go through toLocaleString.
Thousands separator and French format: 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'One method, three national conventions, not a single line of formatting code. Two details:
- The French thousands separator is not an ordinary space but a narrow no-break space (U+202F). It stops “1 234” from being split at the end of a line. If you need to remove it, filter with
/\s/g, not with' '. - Without a locale argument,
toLocaleString()uses the browser’s or the system’s. For a stable result, always specify'fr-FR'.
Intl.NumberFormat: currency, percentage, units
toLocaleString is a shortcut to Intl.NumberFormat. The full object is created once and reused, which matters as soon as you format a list or a table.
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 %' : the value is a ratio, 0.256 rather than 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'The currency formatter puts the symbol in the right place for the locale (after the number in French, before it in English), picks the number of decimals for the currency and inserts the no-break space. That is a lot of rules you no longer have to code.
Compact notation: 1,4 k and 1 M
Showing “1 400 views” as “1,4 k” or “1 000 000” as “1 M” is a common need. The notation: 'compact' option does it, in the locale you want.
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'Other useful options:
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'The last line deserves a word: Intl.NumberFormat rounds from the decimal representation of the number, not from its binary approximation. 1.005 does become “1,01”, where toFixed gave “1.00”. One more reason to format with Intl rather than by hand.
Leading zeros and other bases
String(7).padStart(2, '0'); // '07' : hours, minutes, invoice numbers
String(7).padStart(3, '0'); // '007'
(255).toString(16); // 'ff' : hexadecimal, for a CSS colour
(255).toString(2); // '11111111' : binary
(123.456).toPrecision(4); // '123.5' : total number of significant digits
(1234.5678).toExponential(2); // '1.23e+3'Two toString() behaviours worth knowing: above 1021 and below 10−6, JavaScript switches to scientific notation ((1e21).toString() gives '1e+21', (0.0000001).toString() gives '1e-7'). toFixed or Intl.NumberFormat with notation: 'standard' keep the expanded form.
The reverse operation
A formatted string cannot be converted back as it is: Number('1 234,50') is NaN. You have to strip the spaces and replace the comma before converting. The full procedure is in Converting a string to a number in JavaScript.
What about Numeral.js?
The first version of this article presented Numeral.js, a library with a compact format syntax: numeral(50000).format('0,0') for “50,000”, '0a' for “23k”, '0.00b' for “2.35GB”, '0o' for “23rd”. It is still in plenty of projects and its syntax is pleasant.
import numeral from 'numeral';
numeral(50000).format('0,0'); // 50,000
numeral(23000).format('0a'); // 23k
numeral(2348895676).format('0.00b'); // 2.35GBBut its latest version, 2.0.6, dates from March 2017 and the project is no longer maintained: the locales are frozen and the French separator is not the expected narrow space. For a new project, Intl.NumberFormat covers the same needs (thousands, compact, currency, percentage) with no dependency, the real conventions of each locale and zero weight in the bundle. Keep Numeral where it already is; stop adding it.
Summary
| Need | Tool | Example |
|---|---|---|
| Round for calculation | Math.round(n * 100) / 100 or whole cents | 1.35 |
| Round for display, decimal point | toFixed(2) | '1234.57' |
| French format, thousands and comma | toLocaleString('fr-FR') | '1 234 567,891' |
| Currency, percentage, unit | Intl.NumberFormat + style | '1 234,50 €' |
| 1,4 k / 1 M | notation: 'compact' | '1,4 k' |
| Leading zeros | padStart(2, '0') | '07' |
Common errors
(1.5).toFixed(2) + 1 gives “1.501”: toFixed returns a string, and addition concatenates. Convert with Number() if you need to calculate again.(1.005).toFixed(2) is “1.00” because 1.005 is stored as 1.00499999… in binary. For financial rounding, work in whole cents.toLocaleString('fr-FR') with a normal space The French thousands separator is a narrow no-break space (U+202F). A replace(' ', '') does not remove it; use /\s/g.format() on each value.

