Shotgun: Building the OSS Moneysafe Library from Scratch

Eric Elliott
JavaScript Scene
Published in
4 min readAug 15, 2017

--

Photo: Brandon Bailey — Midnight Cowboy (CC-BY-2.0)

The Shotgun video series lets you ride shotgun with me while I build real software (and open-source libraries).

Watch me build an OSS library from scratch, using unit tests as a guide. Moneysafe is designed to make it easier to work with money in JavaScript. It uses functional programming ideas to build an intuitive API that’s easy to learn and use.

Check out the Moneysafe library on GitHub.

There’s a new 7-episode Shotgun mini-series for members of EricElliottJS.com. Members can sign in and watch the episodes right now.

Check out the first episode here:

What is Moneysafe?

Moneysafe is an open-source JavaScript library designed to make it ridiculously easy to do money-safe calculations in JavaScript.

Writing software that deals with money is a bit of a pain in JavaScript. Money-safe calculations are harder than they should be.

Why? Because JavaScript Numbers are IEEE 754 64-bit floating point. The result is that we can’t safely add money because the decimal will get skewered by floating point rounding errors.

.2 + .1 === .3; // false

However, this problem effectively goes away if you perform the same calculations in cents and then round to the nearest cent. Moneysafe converts your dollar values into cents and then exposes them to the normal JavaScript math operators, so you can use +, -, *, / as you normally would.

With Moneysafe

Install from npm:

npm install --save moneysafe

Then import and use:

import { $ } from 'moneysafe';$(.1) + $(.2) === $(.3).cents;

Even better. There’s a convenient ledger form for common calculations like shopping carts:

import { $ } from 'moneysafe';
import { $$, subtractPercent, addPercent } from 'moneysafe/ledger';
$$(
$(40),
$(60),
// subtract discount
subtractPercent(20),
// add tax
addPercent(10)
).$; // 88

How Does Moneysafe Work?

It works by storing and acting on the amounts in cents instead of dollars, which reduces the floating point rounding errors you get when you represent them as decimal dollars. Of course, you’ll still get rounding errors with lots of multiplication and division, but errors are less common and less significant when scaled to cents.

Best practice with money is to do your calculations in cents with full floating-point precision, and then round to the nearest cent when you’re ready to use the final values.

Those $() signs aren’t jQuery. That’s the money factory that scales your calculations from dollar values to cents, and adds some convenient tools, like the money.$ and money.cents getters that round to the nearest cent automatically, and the money.valueOf() method that allows you to use normal JavaScript math operators.

Moneysafe Accuracy Notes

There are a lot of misconceptions about accurate money calculations. Here’s why Moneysafe uses the native JavaScript Number type:

Decimal Types Don’t Fix Rounding Errors

Some people think “just use a decimal type” is a good solution for floating point rounding errors in money calculations, but that view is wrong.

All decimal types have rounding errors, regardless of the base or precision, because some fractions can’t be represented accurately as decimals without an infinite number of digits. Some fractions that must be rounded in base 2 are finite in base 10. That causes confusion, but does not make base 10 more accurate.

For example, 5/6 is always rounded, regardless of base. 1/3 is always rounded in base 10. Likewise, if your decimal type is set to 2 decimal precision (for cents), you can’t accurately represent 1/1000 without rounding — a common mistake among people who think they can “just use a decimal type” to do accurate money calculations.

All you do when you switch from a base 2 float to a base 10 decimal is change which fractions get rounding errors.

Some decimal types round to the nearest cent automatically with each calculation, but that makes accuracy worse, not better, by introducing cumulative rounding errors at much smaller decimal precision.

Moneysafe uses the full IEEE 754 floating point decimal precision for fractions of cents.

In Moneysafe, IEEE 754 gives us roughly 16 decimal digits of decimal precision.

Unlike most decimal libraries, the entire range of available precision is retained rather than rounded away with each calculation. We don’t make rounding errors bigger by rounding with each calculation. We round only when the user says it’s time to round, so rounding errors stay as small as possible.

We make that rounding automatic by encouraging the use of the .$ getter when you need to get the value back in dollars, or .cents when you need the value in cents, rounded to the nearest cent.

Moneysafe has perfect representation accuracy for all whole cent values.

Whole cent accuracy also extends to all + & calculations. No rounding means no rounding errors.

Money Composition with Functional Programming

Moneysafe uses composable functions as the data type. To learn how that works, check out the “Composing Software” series (to learn function composition, higher order functions, reduce), and the “Composable Datatypes with Functions” post, in particular.

Next Steps

Want to learn more about TDD & functional programming in JavaScript?

Learn JavaScript with Eric Elliott. If you’re not a member, you’re missing out!

Eric Elliott is the author of “Programming JavaScript Applications” (O’Reilly), and cofounder of DevAnywhere.io. He has contributed to software experiences for Adobe Systems, Zumba Fitness, The Wall Street Journal, ESPN, BBC, and top recording artists including Usher, Frank Ocean, Metallica, and many more.

He works anywhere he wants with the most beautiful woman in the world.

--

--