Natively Format JavaScript Numbers
August 14, 2020
NOTE: This post is a sibling post to Natively Format JavaScript Dates and Times
When I've needed to format numbers in JavaScript I usually used Number.prototype.toFixed()
, found a 3rd party library, or manually manipulated the number to suite my needs. However, with modern browsers, there's a lot of really interesting capabilities you could start using with Number.prototype.toLocaleString()
or Intl.NumberFormat
. The following is a screenshot of the cross browser support from caniuse.com.
NOTE: Although the support looks pretty good from the above image, some of the options (
compactDisplay
,currencySign
,notation
,signDisplay
,unit
, andunitDisplay
) are not supported in Safari (see compat table from MDN). However, thankfully there's a polyfill you can use from Format.JS.
Quick 6 minute Overview
Number.prototype.toLocaleString()
The Number.prototype.toLocaleString()
method takes two optional parameters of locales
and options
. If you don't pass either, you'll get the browser's default locale. We'll focus on the options
parameter in the following sections.
const number = 12345.6789;
console.log(number.toLocaleString());
// 12,345.679 (defaults to the "en-US" locale in my case)
console.log(number.toLocaleString('de-DE'));
// 12.345,679
Currency Format
Formatting numbers is great and all, but what about fancy stuff like money!?! Well, that's were the 2nd optional options
parameter comes into play. If you supply a property of { style: "currency" }
and a valid currency
(an ISO 4217 currency code) then you'll get a nicely formatted currency with locale support!
NOTE: There is no default
currency
code, so you'll get an error if you don't provide one.
const number = 12345.6789;
console.log(
number.toLocaleString('en-US', {
style: 'currency',
currency: 'USD',
}),
);
// $12,345.68
console.log(
number.toLocaleString('de-DE', {
style: 'currency',
currency: 'EUR',
}),
);
// 12.345,68 €
console.log(
number.toLocaleString('ja-JP', {
style: 'currency',
currency: 'JPY',
}),
);
// ¥12,346
Significant Digits
Sometimes you want to control how many digits are significant in a number. You might think of this as Frontend Estimation. You can provide a minimumSignificantDigits
(defaults to 1
) or a maximumSignificantDigits
(defaults to 21
).
const number = 12345.6789;
console.log(
number.toLocaleString('en-US', {
maximumSignificantDigits: 1,
}),
);
// 10,000
console.log(
number.toLocaleString('fr-FR', {
maximumSignificantDigits: 3,
}),
);
// 12 300
Unit Support
Unit support is one of those things that I wouldn't have expected to be a feature, but is pretty cool to have. You can mix and match locales along with units of measurement. You can find a full list of possible units from the ECMAScript specification. You can also provide a unitDisplay
of long
, short
(default), or narrow
to control how verbose the unit is displayed.
NOTE: This is one of those features that is not supported by Safari. Also there is no default value for
unit
, so ifstyle
is set tounit
then one must be provided.
const number = 12345.6789;
console.log(
number.toLocaleString('en-US', {
style: 'unit',
unit: 'mile-per-hour',
}),
);
// 12,345.679 mph
console.log(
number.toLocaleString('fr-FR', {
style: 'unit',
unit: 'liter',
unitDisplay: 'long',
}),
);
// 12 345,679 litres
Compact Notation
I found this amusing when I realized this feature existed. Not too long ago I needed something like this to abbreviate large numbers. I ended up finding a snippet of code online to get the job done, but now I know I could have used { notation: "compact" }!
It also takes an optional compactDisplay
that can be set to short
(default) or long
.
NOTE: This is one of those features that is not supported in Safari.
const number = 12345.6789;
console.log(
number.toLocaleString('en-US', {
notation: 'compact',
compactDisplay: 'short',
}),
);
// 12K
console.log(
number.toLocaleString('en-US', {
notation: 'compact',
compactDisplay: 'long',
}),
);
// 12 thousand
Percents
Having percent support is probably not a surprise to you, but it is handy to have especially since it is locale aware (as all the other options are). It gets a little bit nicer because you can also provide other options such as minimumFractionDigits
(defaults to 0 or 2 for currency) or maximumFractionDigits
to control how many fraction digits to use.
const number = 0.1234;
console.log(
number.toLocaleString('en-US', {
style: 'percent',
minimumFractionDigits: 2,
}),
);
// 12.34%
Accounting
I don't typically show negative currency with parenthesis, but apparently that is a common approach for those that do a lot of accounting. I probably won't use this, but it's good to know that it's an option in case I ever do.
NOTE: This is one of those features that is not supported in Safari.
const number = -123.456;
console.log(
number.toLocaleString('en-US', {
style: 'currency',
currency: 'USD',
currencySign: 'accounting',
signDisplay: 'always',
}),
);
// ($123.46)
Intl.NumberFormat
So, instead of using Number.prototype.toLocaleString() you could also use the Intl.NumberFormat
constructor and then call the format
method to format your numbers. However, that might confusing and may make you question which technique you should use. If you find yourself needing to format many numbers over and over again with the same locale and same options, then using Intl.NumberFormat
is preferable for performance reasons.
"When formatting large numbers of numbers, it is better to create a NumberFormat object and use the function provided by its NumberFormat.format property." --Number.prototype.toLocaleString()
const numberFormat = new Intl.NumberFormat('en-US', {
style: 'unit',
unit: 'mile-per-hour',
});
console.log(numberFormat.format(12345.6789));
// 12,345.679 mph
console.log(numberFormat.format(2345.67891));
// 2,345.679 mph
Playground
For more information about all of these options and browser support, check out the Intl.NumberFormat() constructor
page on MDN.
Tweet about this post and have it show up here!