Throughout my career, a majority of software I've developed had something to do with money. From complex web shops to payment gateways, money processing is everywhere. It seems to be a very responsible job, yet I can't remember any college courses, bootcamps, webinars, conference talks specifically discussing money-related issues. Is money handling so easy that it's not worth mentioning at all?
You can do a lot of things with monetary amounts:
- sum them up in a shopping cart,
- place an order,
- pass it to a payment gateway,
- issue an invoice,
- print a PDF file,
- send an e-mail receipt,
- prepare reports for the management,
- prepare tax documents,
- and many more.
There are so many use cases, however it looks like everyone assumes that it's all about just adding and printing some numbers. Easy peasy?
What can go wrong?
I've heard a story about a big e-commerce platform that integrates different parcel delivery APIs. While requesting a delivery, the shop had to declare the value of the goods for insurance. Some APIs expected an amount of dollars (like $12.34) while other APIs used cents (like 1234). The e-commerce developer made a mistake which costed the company hundreds of thousands of dollars because the values sent were too big.
Throughout my career, I've witnessed numerous issues related to processing money in all the projects I've been involved in.
For example, my fellow e-commerce admin complained that she couldn't set a product price to $4.10 - the system set it at $4.09. However, $4.20 worked fine.
Where do these errors come from?
How computers handle fractions
Most currencies in the world have subunits. For example, one dollar equals 100 cents. This is why we're used to represent monetary amounts as decimal numbers. So, $12.34 equals twelve dollars and thirty four cents.
This piece of JavaScript code seems reasonable:
const price = 0.1 + 0.2;
We expect the result to be 0.3, but in fact we see 0.30000000000000004. What happened?
A standard for storing and processing fractions that is supported natively by modern CPUs is IEEE 754. It was established in the early 80s and back then it defined only binary arithmetics, not decimal. Until now, when you see the word float
or double
in your favorite programming language, they most likely represent a binary floating-point number. Converting that number to a decimal counterpart will be subject to error.
float
and double
are good types for scientific calculations. They were designed to store a very wide range of real numbers by using a scientific notation. Obviously if you try to squeeze that range in only 32 bits, your calculations won't be precise, but how many developers are aware of it?
Bill Karwin@billkarwinIf I had a dime for every time I've seen someone use FLOAT to store currency, I'd have $999.997634. #ieee754jokes03:49 AM - 20 Jun 2013
There are many libraries designed specifically to process decimal numbers. Java has BigDecimal, PHP has bcmath, JavaScript has big.js. They have dedicated arithmetic engines inside to ensure precision.
This problem is also important when you try to store monetary amounts in a SQL database. You have to be careful when picking a type for a column. In most dialects, FLOAT
is a binary type and DECIMAL
or NUMBER
are decimal types. Remember to use a proper type!
Money around the world
There are almost 200 countries in the world and around 170 currencies - at least in the ISO 4217 standard. Currencies have different values, so for example 10 USD is not equal to 10 EUR. This is why we should care not only about processing amounts, but also joining them with respective currencies. It's just like in physics where you can't add 1 kilogram and 1 pound without a conversion.
Most currencies have a minor unit of 1/100, so 1 USD equals 100 cents, 1 GBP equals 100 pennies, and so on. But there are exceptions. Japanese yen do not have a subunit (it had in the past). Most varieties of dinar use 1/1000, while Mauritanian ouguiya has 1/5.
I've seen a code which integrated with an external payment API. The code was supposed to convert a decimal amount (let's say, 123.45
) and submit it to the API which expected an integer amount of a smallest unit (12345
). The if
below really bothers me:
if (!"JPY".equals(currency)) {
amount = amount * 100;
}
Now imagine having such conditions everywhere in your code. How error-prone can it be?
Money in different languages
If you want your application to work internationally, you might need to adjust it to different languages and regions. This process is called internationalization, or i18n. Besides message translations, it also involves number and currency formatting.
Language | Region | Example |
---|---|---|
English | United States | USD12,345.67 |
Polish | Poland | 12 345,67 USD |
Spanish | Spain | 12.345,67 USD |
Spanish | Mexico | 12,345.67 USD |
As you see in the table above, even a single language can differ across regions. This is why operating systems introduced locales. These are sets of predefined rules which release us from implementing all the formatting by hand.
Yet still I've seen many cases where people did not use localization properly. They took shortcuts by manually replacing dots with commas. Imagine copying the same replace function a hundred times across the whole application!
The Money Pattern
Martin Fowler suggested that amount and currency can be coupled in a data structure called Money
. Moreover, a class like this should provide basic arithmetics. In his book from 2002, Martin wrote:
A large proportion of the computers in this world manipulate money, so it's always puzzled me that money isn't actually a first class data type in any mainstream programming language.
There are implementations of the Money Pattern for most popular languages, but I have an impression that they are little known. Most developers assume that an integer or BigDecimal
type is enough to handle money properly.
Let's see how a dedicated library benefits us, as shown in this Java example:
Money net = Money.of(100, "EUR"); // 100 euro
Money gross = net.multiply(1.23); // 123 euro
Java has a JSR-354 standard for handling monetary amounts and a reference implementation called Moneta. For PHP, there is a similar library called MoneyPHP. For JavaScript, try dinero.js, currency.js or js-money.
Apart from simple arithmetics, most "money" libraries provide a way to easily convert between currencies. Sometimes the only thing we need to do is to feed an object called exchange rate repository with recent data pulled from an API. Sometimes a library does it for us:
ExchangeRateProvider rateProvider = MonetaryConversions
.getExchangeRateProvider();
CurrencyConversion conversion = rateProvider
.getCurrencyConversion("CHF");
Money amountUsd = Money.of(10, "USD");
Money amountChf = amountUsd.with(conversion);
Last but not least, money libraries help us adjust the printed output to a specific locale:
MonetaryAmountFormat formatter = MonetaryFormats.getAmountFormat(Locale.ENGLISH);
System.out.println(formatter.format(gross)); // EUR123.00
Conclusion
Handling financial calculations is easy if you follow a few simple rules:
-
Use proper data types for decimal arithmetics. Avoid
float
. - Pair an amount with a currency. Even if your software operates only on a single currency, you never know when your client would like to enter foreign markets.
- Be careful while exchanging data with external APIs. Pay attention to what is being sent - is it dollars or cents?
If you don't know what you're doing, you can make silly mistakes that might cost your clients a fortune. Why don't you become a professional that other business professionals can rely on? Surprise your coworkers and your clients with knowledge and experience! Good luck!
Top comments (1)
Very clear thank you.