Working with Money in NodeJS

Working with money in JS can be a little tricky. We need to do some simple math operations that sometimes can lead to unpredicted results as we know 0.1 + 0.2 = 0.30000000000000004. This is because computers do calculations in Floating-Point Arithmetic. So by the end of this tutorial we should be able to do the below calculations on moneteral values correctly.

> 10.1 + 10.2
20.299999999999997
> 10.1 * 3
30.299999999999997
> 10 /3
3.3333333333333335

We'll look on different ways to tackle this problem.

  1. Using JS only
  2. Using decimal.js
  3. Using DINERO.JS
  4. New operator allocate
  5. Dealing with money in prisma

1. Using JS only.

This convert the problem from floating point arithmetic into integer arithmetic, Simply by doing the calculations on the smallest monetary unit. So if we're talking Egyptian Pounds we'll be doing calculation by converting the pound to piasters by multiplying it by 100 and then dividing the final result by 100 again.

const add = (a, b) => (a * 100 + b * 100) / 100;
const multiply = (amount, factor) => (amount * 100 * factor) / 100;
const divide = (amount, factor) => (amount * 100) / factor / 100;

add(10.1, 10.2); // 20.3
multiply(10.1, 3); // 30.3
divide(10, 3); // 3.333333333333333

Looks like we've made a good progress so far. Addition and multiplication works fine but division does not look right.

2. Using decimal.js.

decimal.js has a great set of functions that can be used in dealing with money. It can be downloaded via npm from here. Internally decimal.js represents in a form that's very similar to integers, using fixed number of bits.

new Decimal(10.1).plus(10.2).toString(); // '20.3'
new Decimal(10.1).mul(3).toString(); // '30.3'
new Decimal(10).div(3).toString(); // '3.3333333333333333333'

So again we've solved the addtion and multiplication but we've failed in division.

3. Using DINERO.JS.

DINERO as the name suggests is made specifically for handling monetary values. It can be downloaded via npm from here. It comes with methods for creating, parsing, manipulating, testing, transforming and formatting monetary values. It takes also currency in consideration when doing operations.

Dinero({ amount: 1010, currency: 'EGP' })
  .add(Dinero({ amount: 1020, currency: 'EGP' }))
  .toFormat(); // 'EGP 20.30'
Dinero({ amount: 1010, currency: 'EGP' }).multiply(3).toFormat(); // 'EGP 30.30'
Dinero({ amount: 1000, currency: 'EGP' }).divide(3).toFormat(); // 'EGP 3.33'

So as we see, we needed to multiply the amount by 100 to provide the smallest form of our currency. Now our results looks much better and we're having a good format.

4. New operator allocate.

In the above example we've seen that 10 / 3 can be equals to EGP 3.33 which is right but not very right because the inverse of the operation will not be accurate.

Dinero({ amount: 1000, currency: 'EGP' }).divide(3).multiply(3).toFormat(); // 'EGP 9.99'

As we can see 10 != 9.99, Although the difference is small but it's not acceptable in sectors such as banking. An EGP 100 loan over 3 months with no interest will make the bank lose as the total principal that will be paid is EGP 99.99 instead of EGP 100.

In that case you might need allocate.

Dinero({ amount: 1000, currency: 'EGP' })
  .allocate([1, 1, 1])
  .map((row) => row.toFormat());
// ['EGP 3.34', 'EGP 3.33', 'EGP 3.33']

Now that's makes much more sense. If we add those numbers we can get the original amount. Notice that allocate takes an array of integers as argument. This array represent the ratio and the array length represents the value that we want to distribute the amount on evenly.

5. Dealing with money in prisma.

Prisma is an ORM that's used in NodeJS. It simplifiy the interaction with a database and got ton of features. Majority of databases support Decimal as a data type. In postgresql there's a dedicated type for money.

model Item {
    id       Int     @id
    price    Decimal @db.money
    currency String
}

After migration Typescript will understand that creating an item will expect price of be a Decimal, DecimalJsLike, number or string. Also prisma uses decimal.js to represent the decimal value from the database.

export type ItemCreateInput = {
  id: nmber;
  price: Decimal | DecimalJsLike | number | string;
  currency: string;
};

Conclusion.

In this tutorial I've explained how I've dealt with moneteral values in NodeJS. I thought that Double or Float can be used but I was so wrong and I've did large refactors and replace them with the right types. I'm glad that I've found great resources that guided me to the right way.

References

  1. https://www.prisma.io/docs/orm/reference/prisma-schema-reference#decimal
  2. https://mikemcl.github.io/decimal.js/
  3. https://v2.dinerojs.com/docs
  4. https://www.youtube.com/watch?v=JrOPzkEGtdM

Thanks a lot for reading and Happy coding.