Number and Currency Formatting in JavaScript using Intl.NumberFormat

While this post does not by any means aim to cover all the amazing things you can do with Intl.NumberFormat, I do cover two very common use cases you may have faced when formatting numbers in JavaScript.

Published on: 2024-04-04

Written by Schalk Neethling

While this post does not by any means aim to cover all the amazing things you can do with Intl.NumberFormat, I do cover two very common use cases you may have faced when formatting numbers in JavaScript. As with most things concerning programming, there are many ways to achieve the same end result, this is one of my favorite APIs when formatting numbers in JavaScript.

NOTE: Feel free to share how you handle number formatting with JavasScript in the comments.

First things first, support for Intl.NumberFormat and the related format function across runtime and browser environments is excellent so you can safely follow this post and use Intl.NumberFormat in production.

Basic number formatting

Let’s say you have a magical coin that superficially increases in value every 500 milliseconds until it reaches 2 million at which point it stops.

const increment = 1000;
const maxCoinValue = 2000000; // two million
let magicCoinCurrentValue = 0;

const magicalCoinMiner = setInterval(() => {
  if (magicCoinCurrentValue < maxCoinValue) {
    magicCoinCurrentValue = magicCoinCurrentValue + increment;
    console.log(magicCoinCurrentValue);
  } else {
    clearInterval(magicalCoinMiner);
  }
}, 500);

As the number grows ever bigger, the readability of the number will suffer as we are currently outputting the number without any formatting.

1000 # one thousand
...
30000 # thirty thousand
...
400000 # four hundred thousand
...
1800000 # one million eight hundred thousand

Let’s address the formatting using Intl.NumberFormat and the related format function.

const increment = 1000;
const maxCoinValue = 2000000; // two million
let magicCoinCurrentValue = 0;

const magicalCoinMiner = setInterval(() => {
  if (magicCoinCurrentValue < maxCoinValue) {
    magicCoinCurrentValue = magicCoinCurrentValue + increment;
    const formattedValue = Intl.NumberFormat().format(magicCoinCurrentValue);

    console.log(formattedValue);
  } else {
    clearInterval(magicalCoinMiner);
  }
}, 500);

With this change, the output will now look as follows:

1,000 # one thousand
...
30,000 # thirty thousand
...
400,000 # four hundred thousand
...
1,800,000 # one million eight hundred thousand

I am sure that you can agree that this new format is a lot easier to read. Right now, we are using the default NumberFormat but we can make this locale aware by specifying the locale as the first parameter to NumberFormat like so:

const formattedValue = Intl.NumberFormat("no").format(magicCoinCurrentValue);

console.log(formattedValue);

The no value sets the locale to Norwegian. With this, the numbers will now be formatted as follows:

1 000 # one thousand
...
30 000 # thirty thousand

If we change the locale to de for German, the numbers will be formatted as follows:

1.000 # one thousand
...
30.000 # thirty thousand

The numbers we are printing are monetary values so they are missing a decimal value and a currency symbol. One way we can do this is by using JavaScript template literals to append and prepend the pieces we are missing.

console.log(`$${formattedValue}.00`);

This is not ideal for several reasons. Our decimal value is hard coded as is our currency symbol. It is also hard to read and error-prone. In addition, the hard-coded decimal value is especially problematic.

Currencies

One of the many things NumberFormat can help us with is with currencies. To get both the currency symbol and our decimals without the need for the template literal or hard coding, we can specify the style and currency parameters for the NumberFormat constructor:

const formattedValue = Intl.NumberFormat("en", {
  currency: "USD",
  style: "currency",
}).format(magicCoinCurrentValue);

console.log(formattedValue);

NOTE: You need to specify the locale or undefined (which will use the default locale) or the options object will be ignored.

With this configuration, we will get the following output:

$1,000.00 # one thousand
...
$30,000.00 # thirty thousand

Pretty neat, right?! There are a couple of things to be aware of though. Making our output locale aware can result in surprising output if you are not used to how monetary values are displayed in different locales.

What do I mean by that?

Well, if we change the example above to use the de locale and the Euro currency symbol you might expect output like, €1.000.00.

const formattedValue = Intl.NumberFormat("de", {
  currency: "EUR",
  style: "currency",
}).format(magicCoinCurrentValue);

console.log(formattedValue);

However, this is the output you will get:

1.000,00 # one thousand
...
30.000,00 # thirty thousand

There is nothing wrong with this and it is the way currencies are commonly displayed in countries that are part of the European Union. However, if you want the currency symbol displayed on the left, keep the en locale.

const formattedValue = Intl.NumberFormat("en", {
  currency: "EUR",
  style: "currency",
}).format(magicCoinCurrentValue);

console.log(formattedValue);

Now the output will be as follows:

€1,000.00 # one thousand
...
€30,000.00 # thirty thousand

Do note that the number separators have also changed to match the locale. What happens if we do something like this?

const formattedValue = Intl.NumberFormat("en", {
  currency: "CAD",
  style: "currency",
}).format(magicCoinCurrentValue);

This will result in the following:

CA$1,000.00 # one thousand
...
CA$30,000.00 # thirty thousand

What happens if we change the locale to ca for Canada?

const formattedValue = Intl.NumberFormat("ca", {
  currency: "CAD",
  style: "currency",
}).format(magicCoinCurrentValue);

Now we get:

1.000,00 CAD # one thousand
...
30.000,00 CAD # thirty thousand

NOTE: The number separator has also changed above.

But what if you wanted it to display just the dollar symbol? That is where the currencyDisplay property comes into play:

const formattedValue = Intl.NumberFormat("en", {
  currency: "CAD",
  currencyDisplay: "narrowSymbol",
  style: "currency",
}).format(magicCoinCurrentValue);

NOTE: I have also changed the locale above to en.

Now we get:

$1,000.00 # one thousand
...
$30.000.00 # thirty thousand

Conclusion

As you can tell from the above, and this only touches the surface, Intl.NumberFormat is incredibly powerful especially its ability to ease the localization of number formatting and currencies.

I hope you found this quick look at the basics of this API helpful and that it sparked your curiosity to learn more. Think about some of the projects you are working on at the moment, are there areas where you can take advantage of this to simplify and make your code more robust?

Until the next one! Keep making the web awesome, open, and accessible. 🙏

Resources