Currency Calculations in JavaScript
Tags: javascript
Published: 2021-10-28 17:14:37

*This article was originally written by [Julio Sampaio](https://www.honeybadger.io/blog/currency-money-calculations-in-javascript/#authorDetails) on the [Honeybadger Developer Blog](https://www.honeybadger.io/blog/currency-money-calculations-in-javascript/).*  

One of the most curious things about modern programming languages is that when someone decides to create a new one, lots of thinking about the accepted data types and internal auxiliary libraries takes place.

Think about all the programming languages you've worked with before. How many ways to deal with dates and times do they have? Most of them will probably release at least one way to deal with such data types because it is a very present type in a developer's programming life.

What happened to the money, then? Banks, brokers, online shopping, etc. need to handle money programmatically. And it's been like that for a long time.

Because of the lack of representativity, money types are handled in many different ways, depending on the language you're using. Consequently, some pitfalls show up.

In this article, we will explore these common pitfalls in greater detail and the best options to deal with money in JavaScript.

## Commons Pitfalls

Before we dive into the pitfalls, let's first understand what is required to perform monetary calculations.

Since 2002, when Martin Fowler released his acclaimed book titled [Patterns of Enterprise Application Architecture](https://martinfowler.com/books/eaa.html), we have a [great model](https://martinfowler.com/eaaCatalog/money.html) to deal with monetary values. It all comes down to two properties, _amount_ and _currency_, and several expected operations, including _+, -, *, /, >, >=, &lt;, &lt;=, and =.

Think about them for a bit. If we stop seeing money as a simple number and start viewing it as a data structure composed of two essential properties and some methods to deal with comparison, conversion, and calculations, then we're tackling most of the problems involving this data type.

In other words, to do monetary calculations, you will always need an amount and a currency, as well as a way to perform operations on them (i.e., via methods/functions).

From a JavaScript perspective, a `Money` object that can, for example, hold the two props and expose some functions for calculations would do the job.

### Don't Use a Floating Point

When dealing with money you'll need to store cents as well. For many devs, storing such values into decimal numbers is the right decision because there are decimal places.

Usually, they're represented as a unit of a power of 10:

```text
10² = 100 cents in a dollar
10³ = 1000 cents in 10 dollars
...
```

However, representing money as floating-point numbers in a computer presents some problems, as we've seen [here](https://www.honeybadger.io/blog/ruby-currency/).

Floating-point numbers exist through [different arithmetic](https://0.30000000000000004.com/) on your computer. Since your computer makes use of the binary system to store decimal numbers, you'll eventually produce inconsistent results with your calculations:

```javascript
0.2233 + 0.1 // results in 0.32330000000000003
```

This happens because the computer tries to round off as much as it can to obtain the best result. It also cuts off numbers that are too large, such as periodic tithes, for example.

You could decide to round the result of the previous operation by yourself via, for example, `Math.ceil`:

```javascript
Math.ceil(0.2233 + 0.1) // results in 1
```

However, this approach would still be problematic because you'd lose a couple of pennies during the process. Depending on the type of application you're developing, a loss like that could represent a lot of lost money for customers or your business.

Because of these problems, representing money as a _float_ object is not a recommended approach. If you're still interested in knowing more about the specifics of this issue, I strongly recommend reading Oracle's article: [What Every Computer Scientist Should Know About Floating-Point Arithmetic](https://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html).

### Don't Use `Number` Either

Like in many other languages, a `Number` is a primitive wrapper object that is used when developers need to represent or manipulate numbers, from integers to decimals.

Additionally, because it is a[double-precision 64-bit binary format IEEE 754](https://en.wikipedia.org/wiki/Floating-point_arithmetic) value, it also presents the same threat we just talked about in the previous section.

Furthermore, `Number` also lacks one of Fowler's conditions to create a perfect monetary structure: _currency_. It would be perfectly fine if your application currently only deals with one currency. However, it could be dangerous if things change in the future.

## The Intl API

The [ECMAScript Internationalization API](https://norbertlindenberg.com/2012/12/ecmascript-internationalization-api/index.html) is a collective effort to provide standardized formatting for international purposes. It allows applications to decide which functionalities they need and how they will be approached.

Among the many features provided, we have number formatting, which also embraces monetary value formatting based on the specified locale.

Take a look at the following example:

```javascript
var formatterUSD = new Intl.NumberFormat('en-US');
var formatterBRL = new Intl.NumberFormat('pt-BR');
var formatterJPY = new Intl.NumberFormat('ja-JP');

console.log(formatterUSD.format(0.2233 + 0.1)); // logs "0.323"
console.log(formatterBRL.format(0.2233 + 0.1)); // logs "0,323"
console.log(formatterJPY.format(0.2233 + 0.1)); // logs "0.323"
```

We're creating three different formatters passing different locales for US, Brazilian, and Japanese currencies, respectively. This is great to see how powerful this API is in terms of embracing both the amount and the currency at the same time and performing flexible calculations on them.

Note how the decimal system changes from one country to another and how the Intl API properly calculated the result of our monetary sum for all the different currencies.

If you'd like to set the maximum number of significant digits, simply change the code to:

```javascript
var formatterUSD = new Intl.NumberFormat('en-US', {
  maximumSignificantDigits: 2
});

console.log(formatterUSD.format(0.2233 + 0.1)); // logs "0.32"
```

This is what usually happens when you pay for gas at a gas station.

The API can even allow you to format a monetary value, including the currency sign of the specific country:

```javascript
var formatterJPY = new Intl.NumberFormat('ja-JP', {
  maximumSignificantDigits: 2,
  style: 'currency',
  currency: 'JPY'
});

console.log(formatterJPY.format(0.2233 + 0.1)); // logs "￥0.32"
```

Furthermore, it allows the conversion of various formats, such as speed (e.g., _kilometer-per-hour) and volume (e.g., _liters_). At this [link](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat), you may find all the available options for the Intl `NumberFormat`.

However, it's important to pay attention to the [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat#browser_compatibility) limitations of this feature. Since it is a standard, some browser versions won’t support part of its options, such as Internet Explorer and Safari.

For these cases, a fallback approach would be welcome if you're willing to support your app on these web browsers.

## Dinero.js, Currency.js, and Numeral.js

However, there are always great libraries that the community develops to support missing features, such as [dinero.js](https://dinerojs.com/), [currency.js](https://currency.js.org/), and [numeral.js](http://numeraljs.com/).

There are more available on the market, but we will focus on these three because they represent a significant percentage of developers using currency formatting features.

### Dinero.js

Dinero.js is a lightweight, immutable, and chainable JavaScript library developed to work with monetary values and enables global settings, extended formatting/rounding options, easy currency conversions, and native support to Intl.

Installing it is as easy as running a single command:

```bash
npm install dinero.js
```

One of the greatest benefits of using this library is that it completely embraces Fowler's definition of money, which means it supports both amount and currency values:

```javascript
const money = Dinero({ amount: 100, currency: 'USD' })
```

Furthermore, it also provides default methods to deal with monetary calculations:

```javascript
const tax = Dinero({ amount: 10, currency: 'USD' })
const result = money.subtract(tax) // returns new Dinero object

console.log(result.getAmount()) // logs 90
```

It's important to state that Dinero.js doesn't deal with cents separately. The amounts are specified in minor currency units, depending on the currency you're using. If you're using USD, then money is represented in cents.

To help with the formatting part, it provides us with the `toFormat()` method, which receives a string with the currency pattern with which you'd like to format:

```javascript
Dinero({ amount: 100 }).toFormat('$0,0') // logs "$1"
Dinero({ amount: 100000 }).toFormat('$0,0.00') // logs "$1,000.00"
```

You're in control over how the library handles the formats. For example, if you're dealing with currencies that have a different exponent (i.e., more than two decimal places), you can explicitly define the precision, as shown below:

```javascript
Dinero({ amount: 100000, precision: 3 }).toFormat('$0,0.000') // logs "$100.000"
Dinero({ amount: 100, currency: 'JPY', precision: 0 }).toFormat() // logs "¥100.00"
```

Perhaps one of its greatest features is the chainable support for its methods, which leads to better readability and code maintenance:

```javascript
Dinero({ amount: 10000, currency: 'USD' })
.add(Dinero({ amount: 20000, currency: 'USD' }))
    .divide(2)
    .percentage(50)
    .toFormat() // logs "$75.00"
```

Dinero.js also provides a way to set up a local or remote exchange conversion API via its [convert](https://dinerojs.com/module-dinero#~convert) method. You can either fetch the exchange data from an external REST API or configure a local database with a JSON file that Dinero.js can use to perform conversions.

### Currency.js

Currency.js is a very small (only 1.14 kB) JavaScript library for working with currency values.

To tackle the floating-point issue we talked about, currency.js works with integers behind the scenes and ensures that decimal precision is always correct.

To install it, you just need a single command:

```bash
npm install currency.js
```

The library can be even less verbose than Dinero.js, by encapsulating the monetary value (whether it is a string, a decimal, a number, or a currency) into its `currency()` object:

```javascript
currency(100).value // logs 100
```

The API is very clean and straightforward since it also adopts a chainable style:

```javascript
currency(100)
.add(currency("$200"))
.divide(2)
.multiply(0.5) // simulates percentage
.format() // logs "$75.00"
```

It also accepts string parameters, such as a monetary value, with the sign, as seen above. The `format()` method, in turn, returns a human-friendly currency format.

However, when it comes to internationalization, currency.js defaults to the US locale. If you're willing to work with other currencies, some extra work must be done:

```javascript
const USD = value => currency(value);
const BRL = value => currency(value, {
  symbol: 'R$',
  decimal: ',',
  separator: '.'
});
const JPY = value => currency(value, {
  precision: 0,
  symbol: '¥'
});

console.log(USD(110.223).format()); // logs "$110.22"
console.log(BRL(110.223).format()); // logs "R$110,22"
console.log(JPY(110.223).format()); // logs "¥110"
```

Currency.js is cleaner than Dinero.js in terms of verbosity, which is great. However, it doesn't have a built-in way to perform exchange conversions, so be aware of this limitation if your application needs to do so.

### Numeral.js

As the library name suggests, Numeral.js is more of a general-purpose library that deals with formatting and manipulating numbers in general in JavaScript.

Although it can also manipulate currency values, it offers a very flexible API to create [custom formats](http://numeraljs.com/#custom-formats).

To install it, only one command is required:

```bash
npm install numeral
```

Its syntax is very similar to currency.js when encapsulating a monetary value into its `numeral()` object:

```javascript
numeral(100).value() // logs 100
```

When it comes to formatting these values, it is closer to Dinero.js' syntax:

```javascript
numeral(100).format('$0,0.00') // logs "$100.00"
```

Since the library has limited [built-in](https://github.com/adamwdraper/Numeral-js/tree/master/locales) internationalization features, you'd have to set yours up in case a new currency system is needed:

```javascript
numeral.register('locale', 'es', {
  delimiters: {
    thousands: ' ',
    decimal: ','
  },
  currency: {
    symbol: '€'
  }
})

numeral.locale('es')

console.log(numeral(10000).format('$0,0.00')) // logs "€10 000,00"
```

When it comes to the chainable pattern, numeral.js also delivers the same readability we're looking for:

```javascript
const money = numeral(100)
  .add(200)
  .divide(2)
  .multiply(0.5) // simulates percentage
  .format('$0,0.00') // logs "$75.00"
```

Numeral.js is, by far, the most flexible library to deal with numbers on our list. Its flexibility includes the capacity to create locales and formats as you wish. However, be careful when using it since it doesn't provide a default way to calculate with zero precision for decimal numbers, for example.

## Wrapping Up

In this blog post, we've explored some of the best alternatives to deal with monetary values within JavaScript, whether it is for client or backend applications. Let's recap some of the important points discussed so far:

- If you're dealing with money, never use the floating-point primitive numbers of the language or the `Number` wrapper object.
- Instead, the Intl API provided by default by your web browser is preferred. It is more flexible, secure, and smart. However, be aware of its compatibility limitations.
- Regardless of each, if you can add a bit more weight to your app bundle, consider adopting one of the demonstrated libraries when dealing with currency calculations and/or conversions.

Finally, make sure to refer to their official docs and tests. Most of them provide great tests to help you understand the pros and cons of each library and choose the one that best suits your needs.