Money's all over the place. All general application areas contain them e.g:
- eCommerce
- Banking
- Investment
- Insurance and Pensions
However, Date and Time are first-class data types in all mainstream languages, while money is not. For this reason, in any application, developers need to reinvent the way of handling money, with all problems described below.
Problem #1: Money as a Number
The first assumption represents money as a Number. What problems do we have with this solution?
If you have 10 dollars and 10 euros, these are not equal values, as their Number parts are. For adding, you can't do something like this:
// e.g. val1 represents dollars and val2 represents euros
const val1 = 10;
const val2 = 20;
const val3 = val1 + val2;
// => 30 (???) but what currency is it
So you need some logic, functions, or services for converting them and of course, you must find a way to handle their currency. Hence the following conclusion, the Number individually is not good for representing monetary values.
Problem #2: Floating point math
Computers use a binary system, so they can’t natively represent decimal numbers. Some languages have come up with their own solutions like the BigDecimal type in Java or like Rational in Ruby. JavaScript now contains only number and bigint. Important remark, in the near future we can possibly get native decimal in the js world but it's only at stage 1 now. Because it is a binary representation of the base 10 system, you get inaccurate results when you try to perform mathematical operations.
0.1 + 0.2 // returns 0.30000000000000004
If you are interested, you can read more about floating-point math here.
So, floats aren't the best idea to handle monetary values. The more calculations you do, the more errors you make when performing rounding.
Problem #3: Allocation
When we talk about allocation here, we mean dividing money between people. For example, I have 5 dollars and want to divide it between 2 people. Every person gets 50 percent of the original value. In the simple model, it might look like this:
const amount = 5;
const percent = 50;
const personAmount = amount / 100 * percent;
// => 2.5
In the previous example, each person gets $2 and 50 cents. But what do you do in more complex cases? One of these cases is also known as Foemmel's Conundrum:
Allocate a whole amount of 5 cents into two accounts, where the first account contains 30% of the original amount and the second account contains 70% of the original amount.
It means multiplying 5 cents by 30%, which gives 1.5 cents for the first account; for the second account, it means multiplying 5 cents by 70%, which gives 3.5 cents. So, few accountants will have a balance with fractional cents. I guess that's not what we expect.
Another pitfall would be if you decide to round up result values. For the first account, the result value would be 2 cents, and for the second, the rounded value would be 4 cents. But if we add up these two values, we get 6 cents. It turns out that we lost 1 cent, but don't forget that it's just one operation. If you do many more such operations in a row, the inaccuracies can be much greater.
You can try to solve this conundrum on your own, but nevertheless any standard math operations don't give you the expected results. This problem can be solved for example by distributing the cents starting with the first account and continuing sequentially until there are no cents remaining but here we're not going to focus on these details, I try to describe different solutions in a separate post.
Solution
The solution to the aforementioned problems is Martin Fowler's Money Type from "Patterns of Enterprise Application Architecture".
It's an old and widely used pattern that is implemented in many other languages e.g.:
Fowler offers to use Money as a data structure. What does it mean? Let's briefly describe this pattern.
1. Don't represent money as Number, always represent Money as a pair of Amount and Currency.
This gives us the opportunity to make both math operations (addition, subtraction) and comparison between two Monies - comparison by amount or comparison by currency. In this implementation, we should treat attempts of performing math operations with different currencies like an error.
Also, we always have a lot of options on how to represent formatting in our code, because of Amount and Currency being held tightly together in our data structure.
2. Amounts must be represented in cents (minor units)
There are several ways you can solve the floating point issue in JavaScript. Originally, Fowler mentioned, that you have two options for implementing this pattern. Either use Decimal for implementing this pattern, but as I said earlier, we don't yet have the Decimal type in the JavaScript world, or use integral type.
I'll go into more detail on the second option. If you want to store 30 cents, you don't represent this as 0.3, you should store it as 30 (if you have $5, you need to store it as 500). The main advantage in this case is that you'll never have to work with non-integral numbers.
Inspired by alternatives from other languages, I created easymoney, the library for operating monetary values in JavaScript and Typescript.
easymoney
easymoney implements Martin Fowler's Money Type. It supplies all your needs: Math operations, formatting, allocation, and so on.
Its main features are as follows:
Modular. It's written with modular and composable architecture so that your final bundle will be as small as possible.
Statically typed. First-class support of Typescript.
Immutable: It has a chainable API, but without mutations.
Supports big numbers. Supports values greater than MAX_SAFE_INTEGER with Bignumber.js
Cryptocurrencies. It has support for custom currencies and formatting them.
Big int support. It has a package for supporting the new standard of big int out of the box in a separate package.
Custom calculators. You can override functionality with your own custom calculator.
Code examples
import { createMoney } from '@easymoney/money';
const money = createMoney({ amount: 100, currency: 'USD' });
const money2 = createMoney({ amount: 100, currency: 'USD' });
const result = money.add(money2);
result.getAmount();
// => 200
import {createMoneyIntlFormatter} from "@easymoney/formatter"
import { createMoney } from '@easymoney/money';
const money = createMoney({amount: 5, currency: "USD"});
const money1 = createMoney({amount: 50, currency: "USD"});
const formatted = createMoneyIntlFormatter().format(money);
// => "$0.05"
const formatted1 = createMoneyIntlFormatter()
.format(money,
"en-US",
{minimumFractionDigits: 1, maximumFractionDigits: 1});
// => "$0.5"
More examples you can find here.
Thank you
Thanks for reading the post and for your time. Big thanks to people who helped me to finish this project, especially Jan Janucewicz, who helped with integrating bignumber.js and made a great effort to tests and documentation.
If you find bugs, please report them on our Github issues. Alternatively, you can always ask me on Twitter.
Feel free to ask questions, to express any opinion, and discuss this from your point of view. Make code, not war. ❤️
Top comments (4)
€ool 😉 Good job!
Is there any updates on this library?
Nice post, learnt much , great work withe JavaScript library,
While googling around i also found dinerojs