How to format numbers to 2 decimal places in JavaScript

Display numbers with exactly 2 decimal places for prices, percentages, and measurements

Introduction

Many applications need to display numbers with a fixed number of decimal places. Prices typically show two decimal places like $19.99. Percentages often display as 45.50%. Measurements use consistent decimal places for readability like 3.14 meters.

Controlling decimal places ensures visual consistency across your application. Without explicit formatting, JavaScript displays numbers with varying decimal places based on their actual value. The number 5 displays as "5" while 5.5 displays as "5.5", creating inconsistent alignment and appearance.

This lesson shows how to format numbers to exactly 2 decimal places, or up to 2 decimal places, in a way that respects different locale conventions.

Using toFixed() for basic formatting

The toFixed() method converts a number to a string with a specified number of decimal places. Pass the number of decimal places as an argument.

const price = 19.9;
const formatted = price.toFixed(2);
console.log(formatted);
// Output: "19.90"

The method always displays exactly 2 decimal places. If the number has fewer decimal places, toFixed() pads with zeros. If it has more decimal places, the method rounds to 2 places.

const examples = [5, 5.5, 5.555, 5.999];

examples.forEach(num => {
  console.log(num.toFixed(2));
});
// Output:
// "5.00"
// "5.50"
// "5.56"
// "6.00"

The toFixed() method returns a string, not a number. This is intentional because the trailing zeros have meaning for display but would be lost if returned as a number.

The locale problem with toFixed()

The toFixed() method always uses a period as the decimal separator, regardless of the user's locale. Many countries use a comma as the decimal separator instead of a period.

const price = 19.99;
console.log(price.toFixed(2));
// Output: "19.99" (always uses period)

For users in Germany, France, Spain, and many other countries, this looks wrong. They expect to see "19,99" instead of "19.99". The toFixed() method cannot produce locale-appropriate output.

To format numbers correctly for different locales, use the Intl.NumberFormat API.

Using Intl.NumberFormat for locale-aware formatting

The Intl.NumberFormat API formats numbers according to locale conventions. Create a formatter with a locale and options, then call its format() method with a number.

const formatter = new Intl.NumberFormat("de-DE", {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

const price = 19.99;
console.log(formatter.format(price));
// Output: "19,99" (uses comma for German locale)

The same formatter produces different output for different locales.

const price = 19.99;

const usFormatter = new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

const deFormatter = new Intl.NumberFormat("de-DE", {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

console.log(usFormatter.format(price));
// Output: "19.99"

console.log(deFormatter.format(price));
// Output: "19,99"

The decimal separator changes automatically based on the locale.

Formatting with exactly 2 decimal places

To display exactly 2 decimal places, set both minimumFractionDigits and maximumFractionDigits to 2. This ensures the output always has 2 decimal places, padding with zeros when needed and rounding when the number has more precision.

const formatter = new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

console.log(formatter.format(5));
// Output: "5.00"

console.log(formatter.format(5.5));
// Output: "5.50"

console.log(formatter.format(5.555));
// Output: "5.56"

The minimumFractionDigits option controls trailing zeros. Without it, numbers with fewer decimal places would not show the zeros.

const withoutMinimum = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 2
});

console.log(withoutMinimum.format(5));
// Output: "5"

const withMinimum = new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

console.log(withMinimum.format(5));
// Output: "5.00"

Setting both options to the same value guarantees consistent decimal places across all numbers.

Formatting with up to 2 decimal places

Sometimes you want to show decimal places only when needed, up to a maximum of 2. Set maximumFractionDigits to 2 and minimumFractionDigits to 0 or omit it entirely.

const formatter = new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 0,
  maximumFractionDigits: 2
});

console.log(formatter.format(5));
// Output: "5"

console.log(formatter.format(5.5));
// Output: "5.5"

console.log(formatter.format(5.555));
// Output: "5.56"

This approach removes trailing zeros but still limits precision to 2 decimal places. It works well for displaying measurements or statistics where trailing zeros add no information.

The default value for minimumFractionDigits is 0 for plain number formatting, so you can omit it.

const formatter = new Intl.NumberFormat("en-US", {
  maximumFractionDigits: 2
});

console.log(formatter.format(5));
// Output: "5"

console.log(formatter.format(5.5));
// Output: "5.5"

Reusing formatters for better performance

Creating a new Intl.NumberFormat instance is relatively expensive. If you format many numbers with the same options, create the formatter once and reuse it.

const formatter = new Intl.NumberFormat("en-US", {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

const prices = [19.99, 29.5, 99, 149.999];

prices.forEach(price => {
  console.log(formatter.format(price));
});
// Output:
// "19.99"
// "29.50"
// "99.00"
// "150.00"

This pattern is more efficient than creating a new formatter for each number.

When to use each approach

Use exactly 2 decimal places when displaying prices, currency amounts, or any value where the decimal places have semantic meaning. Showing "$5.00" instead of "$5" indicates precision and matches user expectations for prices.

Use up to 2 decimal places when displaying statistics, measurements, or calculated values where trailing zeros add no information. Showing "5 meters" instead of "5.00 meters" is cleaner and more readable.

Use toFixed() only when you are certain your users all use the same decimal separator convention, or when the output is not user-facing. For internationalized applications, prefer Intl.NumberFormat.

Using the user's preferred locale

Instead of hardcoding a locale, use the user's browser language preferences. The navigator.language property provides the user's preferred locale.

const formatter = new Intl.NumberFormat(navigator.language, {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

const price = 19.99;
console.log(formatter.format(price));
// Output varies by user's locale
// For en-US: "19.99"
// For de-DE: "19,99"
// For fr-FR: "19,99"

You can also pass the entire navigator.languages array to let the Intl API select the first supported locale from the user's preferences.

const formatter = new Intl.NumberFormat(navigator.languages, {
  minimumFractionDigits: 2,
  maximumFractionDigits: 2
});

This approach provides automatic fallback if the user's first preference is not supported.