Natively Format JavaScript Numbers

Elijah Manor about 1 month
917 words 5 min read

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, and unitDisplay) 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

The above video is hosted on egghead.io.

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 if style is set to unit 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.