content/snippets/js/s/number-formatting.md
---
title: Formatting numeric values in JavaScript
shortTitle: Number formatting
type: story
language: javascript
tags: [math,string,date]
cover: white-chapel
excerpt: Learn common number formatting operations, such as rounding, padding, optional decimal marks, currency, seconds, bytes, and more.
listed: true
dateModified: 2024-02-14
---
Number formatting is one of the most common presentational tasks you'll encounter coding for the web. While there are some built-in methods, it's often necessary to roll up your own solution for a specific use case. Let's explore a few common scenarios and how to handle them.
> [!NOTE]
>
> You may not be familiar with JavaScript's [numeric separators](/js/s/numeric-separator), which are used in the examples below. They're **syntactic sugar** that make large numeric values more readable.
## Fixed-point notation without trailing zeros
In **fixed-point notation**, a number is represented with a fixed number of digits after the decimal point. However, we often want to **remove trailing zeros** from the result.
In order to do so, we can use `Number.prototype.toFixed()` to convert the number to a fixed-point notation string. Then, using `Number.parseFloat()`, we can convert the fixed-point notation string to a number, removing trailing zeros. Finally, we can use a template literal to convert the number to a string.
```js
const toOptionalFixed = (num, digits) =>
`${Number.parseFloat(num.toFixed(digits))}`;
toOptionalFixed(1, 2); // '1'
toOptionalFixed(1.001, 2); // '1'
toOptionalFixed(1.500, 2); // '1.5'
```
## Round number to given precision
Rounding a number to a **specific number of decimal places** is pretty common. We can use `Math.round()` and template literals to round the number to the specified number of digits. Omitting the second argument, `decimals`, will round to an integer.
```js
const round = (n, decimals = 0) =>
Number(`${Math.round(`${n}e${decimals}`)}e-${decimals}`);
round(1.005, 2); // 1.01
```
> [!NOTE]
>
> This function **returns a number**, instead of a string. This is intentional, as we might want to use the rounded number in further calculations.
## Format duration
Converting a number of milliseconds to a **human-readable format** is a matter of dividing the number by the appropriate values and creating a string for each value. In the following snippet, we'll use an object that contains the appropriate values for `day`, `hour`, `minute`, `second`, and `millisecond`, but you can easily adapt it to different needs.
We can use `Object.entries()` with `Array.prototype.filter()` to keep only non-zero values. Then, we can use `Array.prototype.map()` to create the string for each value, pluralizing appropriately. Finally, we can use `Array.prototype.join()` to combine the values into a string.
```js
const formatDuration = ms => {
if (ms < 0) ms = -ms;
const time = {
day: Math.floor(ms / 86_400_000),
hour: Math.floor(ms / 3_600_000) % 24,
minute: Math.floor(ms / 60_000) % 60,
second: Math.floor(ms / 1_000) % 60,
millisecond: Math.floor(ms) % 1_000
};
return Object.entries(time)
.filter(val => val[1] !== 0)
.map(([key, val]) => `${val} ${key}${val !== 1 ? 's' : ''}`)
.join(', ');
};
formatDuration(1_001);
// '1 second, 1 millisecond'
formatDuration(34_325_055_574);
// '397 days, 6 hours, 44 minutes, 15 seconds, 574 milliseconds'
```
### Number of seconds to ISO format
Similarly, formatting the number of seconds to **ISO format** is also a handful of divisions away. However, we need to handle the **sign** separately.
We can use `Array.prototype.map()` in combination with `Math.floor()` and `String.prototype.padStart()` to convert each segment to a string. Finally, we can use `Array.prototype.join()` to combine the values into a string.
```js
const formatSeconds = s => {
const [hour, minute, second, sign] =
s > 0
? [s / 3_600, (s / 60) % 60, s % 60, '']
: [-s / 3_600, (-s / 60) % 60, -s % 60, '-'];
return (
sign +
[hour, minute, second]
.map(v => `${Math.floor(v)}`.padStart(2, '0'))
.join(':')
);
};
formatSeconds(200); // '00:03:20'
formatSeconds(-200); // '-00:03:20'
formatSeconds(99_999); // '27:46:39'
```
## Locale-sensitive number formatting
Formatting a number to a **locale-sensitive string** is also easy using `Number.prototype.toLocaleString()`. This method allows us to format numbers using the local number format separators.
```js
const formatNumber = num => num.toLocaleString();
formatNumber(123_456); // '123,456' in `en-US`
formatNumber(15_675_436_903); // '15.675.436.903' in `de-DE`
```
### Number to decimal mark
If we want to format a number to a specific locale, we can pass the locale as the first argument to `Number.prototype.toLocaleString()`. For example, here's how to format a number to use the **decimal mark**, using the `en-US` locale.
```js
const toDecimalMark = num => num.toLocaleString('en-US');
toDecimalMark(12_305_030_388.9087); // '12,305,030,388.909'
```
### Number to currency string
When working with **currency**, it's important to use the appropriate formatting. Luckily, JavaScript's `Intl.NumberFormat` makes this easy, allowing us to format numbers as currency strings. All we have to do is specify the currency and language format, along with `style: 'currency'`.
```js
const toCurrency = (n, curr, LanguageFormat = undefined) =>
Intl.NumberFormat(LanguageFormat, {
style: 'currency',
currency: curr,
}).format(n);
toCurrency(123_456.789, 'EUR');
// €123,456.79 | currency: Euro | currencyLangFormat: Local
toCurrency(123_456.789, 'USD', 'en-us');
// $123,456.79 | currency: US Dollar | currencyLangFormat: English (USA)
toCurrency(123_456.789, 'USD', 'fa');
// ۱۲۳٬۴۵۶٫۷۹ $ | currency: US Dollar | currencyLangFormat: Farsi
toCurrency(322_342_436_423.2435, 'JPY');
// ¥322,342,436,423 | currency: Japanese Yen | currencyLangFormat: Local
toCurrency(322_342_436_423.2435, 'JPY', 'fi');
// 322 342 436 423 ¥ | currency: Japanese Yen | currencyLangFormat: Finnish
```
### Number to ordinal suffix
Converting a number to its **ordinal form** (e.g. `1` to `1st`, `2` to `2nd`, `3` to `3rd`, etc.) is also fairly simple, using `Intl.PluralRules`. We can use `Intl.PluralRules.prototype.select()` to get the correct **pluralization category** for the number, and then use a **lookup dictionary** to get the correct suffix. In order for this to produce the correct result, we need to specify the correct locale and the `type: 'ordinal'` option when constructing the object.
```js
const ordinalsEnUS = {
one: 'st',
two: 'nd',
few: 'rd',
many: 'th',
zero: 'th',
other: 'th',
};
const toOrdinalSuffix = (num, locale = 'en-US', ordinals = ordinalsEnUS) => {
const pluralRules = new Intl.PluralRules(locale, { type: 'ordinal' });
return `${num}${ordinals[pluralRules.select(num)]}`;
};
toOrdinalSuffix(1); // '1st'
toOrdinalSuffix(2); // '2nd'
toOrdinalSuffix(3); // '3rd'
toOrdinalSuffix(4); // '4th'
toOrdinalSuffix(123); // '123rd'
```
## Pad number with leading zeros
Last but not least, you might need to **pad a number with leading zeros** to a specific length. We can use `String.prototype.padStart()` to pad the number to the specified length, after converting it to a string.
```js
const padNumber = (n, l) => `${n}`.padStart(l, '0');
padNumber(1_234, 6); // '001234'
```