How To Build A Money Data Type In JavaScript 🔊

An alternative to the floating-point trap

Fagner Brack
ITNEXT

--

The picture of a massive flame from a Fire Breathing act. The background is dark. The fire comes from the right, a hidden person outside the screen executes the technique (source)
Listen to the audio version!

Last time I wrote a step-by-step example of how to apply Inside Out Test-Driven Development to a problem using JavaScript. That post used the Number type to represent dollars.

According to the JavaScript specification, the Number type is a “double-precision 64-bit binary format IEEE 754” value. You shouldn’t use implementations of IEEE 754 to represent any data where you need to have precision. Money is one of them.

There is too much content out there about this subject. Therefore, this post shows, through coding examples, how you can create a model that can handle money types correctly

You shouldn't use the "Number" type in JavaScript to represent Money

Jack, The Moneylender, started to calculate some loans using the function from the previous post. At some point, he tried to calculate the loan amount of "$2585." The result was "$52.73" followed by "999999999999."

Jack is not happy about this. Five years ago, his friend worked as an accountant for a Mafia queen. One day, she spotted a weird fractional number from an account and got suspicious.

She fired him. Like… with actual fire!

All right, Jack is not working for the Mafia right now. However, he doesn’t like the fact a calculation might yield the incorrect result in certain circumstances.

You need to fix it.

Jack doesn't like fire

In the book "Patterns of Enterprise Application Architecture," Martin Fowler provides a solution to the representation of monetary values. He suggests you can create a model that affords some arithmetic operations. However, the code runs the calculations on top of an "amount," which is an internal non-fractional property that is not affected by floating-point issues.

You can decouple the visual representation from the logic and run all the math in cents.

If you represent the cents as non-fractional Numbers, you don't need to worry about floating-point issues. Later, you can convert the Number into a String and decide where to put the decimal separator according to the currency you want to output.

A text file containing examples of the positioning and styles of decimal points in different currencies.

Given this is a general programming concern and not a very specific Problem Domain, the programming language should at least provide a solution out of the box.

Unfortunately, JavaScript doesn’t have a decimal or a money type, and I don’t recommend you to spend the time to reinvent it. There are some libraries out there such as dinero.js that are worth investigating.

For this post, though, let’s create a Money type from scratch.

The only operations that are necessary, according to the "interest to pay for dollars" function, is the subtraction, multiplication and the "greater than." There’s no need to spend the time to develop affordances you don’t need right now.

The JavaScript code for the function "interest to pay for dollars." If you want to know how this came to be, look at the previous post. The code uses the native operators for subtraction, multiplication and "greater than" comparison.

The constructor of the new model accepts a String with a dollar sign ("$1.00"). There’s no need to support multiple currencies at this point, so it's safe to use that notation.

Also, the new model requires that consumers always specify two decimal digits ".00," even when the value is round like "$5.00," instead of omitting the digits like "$1." The logic is more straightforward that way. It's easier to reason about the problem.

Money('$0.01');
Money('$0.10');
Money('$1.00');
Money('$10.00');
Money('$12.45');

The minus operation subtracts the value in cents. Later, it generates the visual representation and prefix with the negative sign, if necessary.

To produce the visual representation, you put the decimal point on the second to last digit. If the decimal point location ends up before the first digit, fill the missing characters with zeroes.

The code for a function with the name "to decimal point." The algorithm shows how to put the decimal point in the right location.

The multiplication is trickier than the subtraction. Would it make sense to multiply dollars for cents? Maybe. Let's try.

Note: See this comment for additional information on why it wouldn't make sense to multiply dollars for cents.

To solve the interest problem for Jack, you need to run calculations like $1.00 multiplied by $0.09. Therefore, it seems reasonable to use other dollar values instead of a Number in the multiplication factors.

Since you're multiplying two dollar values as cents, if you multiply $2.00 by $2.00, you get 40000 cents. The representation of that is $400.00, but the result should be $4.00.

If you divide the result of the calculation by 100, you get the correct output of 400 cents. However, you can avoid the mathematical division by slicing the last 2 digits out of the multiplication result.

A text file that shows multiplication in cents and how the rightmost digits can be sliced out.

Of course, both solutions come at the expense of losing the precision beyond 2 decimal digits.

The code that shows the implementation of the multiplication logic.

You can see the final result, without further optimization, in a new commit to the "Jack, The Moneylender" repository, including the tests.

I used TDD to produce this code, just like I did with the first deliverable for Jack. However, this post is not to show you how to do TDD; it’s to understand how you can work around the lack of precision for floating-point numbers.

There are many ways to represent money. One of them is to represent as cents.

Nothing prevents you from increasing the precision and scaling above cents, such as to support the Mill currency or fractions of Bitcoin (satoshis). To do that and keep backward compatibility, assume the missing digits have a value of zero. If you want to downscale from satoshis to cents, the code won’t break either; only you’ll lose precision beyond 2 decimal digits.

Floating-point numbers are a trap. They fit the problem correctly; until they don't. At that point, it will probably be too late to make any change.

Don’t use the fractional representation of the JavaScript Number type to represent money. In fact, don't use floating points for anything that require precision.

You don’t need to be involved with the Mafia for something terrible to happen. If you don't care about software design, then someone might have to put down the fire later.

It can even be yourself.

Thanks for reading. If you have some feedback, reach out to me on Twitter, Facebook or Github.

Thanks to Jon Eaves, Stephan Classen and Jay Bazuzi for their insightful inputs to this post.

--

--

Writer for

I believe ideas should be open and free (as in Freedom). This is a non-profit initiative to write about challenging stuff you won’t find anywhere else.