• Design
  • Shotty
  • Blog
  • Reading
  • Photos
Menu

Jacob Ruiz

Product Designer
  • Design
  • Shotty
  • Blog
  • Reading
  • Photos
rounding issues@2x.png

Mastering Javascript Fundamentals: AccountingJS, toFixed and Rounding Issues

May 14, 2018

Get the fundamentals down and the level of everything you do will rise. - Michael Jordan

As stated in my original post, I do 1 hour of video lessons from Watch and Code every day. If you're interested in learning Javascript in a way that goes beyond basic tutorials and gives you a foundational, practical knowledge without relying on frameworks - I'd highly recommend it. If you're reading these posts, please keep in mind that these are just my notes, and I'm not an expert (yet!). If your goal is also to master the fundamentals of Javascript, please head over to Watch and Code and start your journey there!

All screenshots were annotated using Shotty.


toFixed and Rounding Issues

Now we're going to look at the actual API methods of AccountingJS.

Let's start with the toFixed method. This fixes rounding issues with the native toFixed method that will sometimes round down where you'd expect it to round up.

    /**
     * Implementation of toFixed() that treats floats more like decimals
     *
     * Fixes binary rounding issues (eg. (0.615).toFixed(2) === "0.61") that present
     * problems for accounting- and finance-related software.
     */
    var toFixed = lib.toFixed = function(value, precision) {
        precision = checkPrecision(precision, lib.settings.number.precision);
        var power = Math.pow(10, precision);

        // Multiply up by precision, round accurately, then divide and use native toFixed():
        return (Math.round(lib.unformat(value) * power) / power).toFixed(precision);
    };

This has to do with how computers deal with numbers:

Demo visualization: http://bartaz.github.io/ieee754-visualization/

Numbers are the sum of powers of 2.

The number 1 is just 2 to the 0th power:

Screen Shot 2018-05-15 at 9.55.34 AM.png

The number 2 is 2 to the 1st power:

 

3 is 2 to the 1st power plus 2 to the 0th power:

Screen Shot 2018-05-15 at 9.57.17 AM.png

Every positive integer up to a certain point can be represented this way. We say "up to a certain point" because JS and other languages limit the amount of data you can use to represent numbers. So it trips up with really big numbers, but most reasonable numbers are ok.

Let's look at fractions:

Screen Shot 2018-05-15 at 10.03.04 AM.png
Screen Shot 2018-05-15 at 10.05.20 AM.png
Screen Shot 2018-05-15 at 10.05.27 AM.png
Screen Shot 2018-05-15 at 10.05.40 AM.png
Screen Shot 2018-05-15 at 10.06.04 AM.png

All these numbers can be easily represented using powers of 2 (using negative exponents).

But not all decimals work this nicely. Let's look at the example that AccountingJS gives: 0.615

Screen Shot 2018-05-15 at 10.08.32 AM.png

0.615 can't be perfectly represented by powers of 2.

Because of this, Javascript will try to approximate that and get as close as it can. But it never quite gets there. Depending on the number, sometimes it will be higher and sometimes it will be lower.

toFixed is going to round based on the approximate value that Javascript gets. So it's really rounding 0.51499999999 or something like that.

So now it's clear that the browser's toFixed won't work to round decimals in accounting software.

Let's look at a solution.

We can multiply by 100 to get the number out of the deep decimals, use Math.round, then divide by 100 to get back to our desired number:

Screen Shot 2018-05-15 at 10.13.29 AM.png

Let's look at AccountingJS. First we'll notice something weird: both var toFixed and lib.toFixed are give then value of this function...

Screen Shot 2018-05-15 at 10.17.06 AM.png

toFixed is used elsewhere in the application, so this makes it shorter to type, so you don't have to type lib.toFixed every time. An additional benefit is that if you changed the name of lib, then every time you used lib.toFixed you'd have to change it, but with this approach that wouldn't be necessary.

AccountingJS's toFixed takes two arguments: value and precision.

Screen Shot 2018-05-15 at 10.22.50 AM-annotated.png

Precision is the number of decimal places. The default for currencies will be 2, and for numbers will be 0.

Screen Shot 2018-05-15 at 10.25.24 AM-annotated.png

Here it's going to set precision to the provided precision argument, and if none was provided it will use the default precision value:

Screen Shot 2018-05-15 at 10.29.10 AM-annotated.png

The next line is where we choose what to multiply by:

Screen Shot 2018-05-15 at 10.33.13 AM.png

Remember this was our approach (100 is the same as 10^2):

.615 * (10^2) ==> 61.5 ==> 62 ==> .62 

The thing to note is that the exponent is the number of decimal places we want. We don't necessarily always want 2. You can vary the output by changing the exponent.

Math.pow is how we do exponents in Javascript.

So 10^2 is represented as Math.pow(10, 2).

The rest of the logic is stuff we've seen already.

You take the value:

Screen Shot 2018-05-15 at 10.49.41 AM.png

Then, in this case we have this extra layer of processing that removes any formatting (for example, if you had a currency symbol with the value, lib.unformat would strip that off because we need just the number):

Screen Shot 2018-05-15 at 10.51.57 AM.png

Once we have that value, we multiply it by power (100 for example):

Screen Shot 2018-05-15 at 10.52.59 AM.png

Then we're going to round this result:

Screen Shot 2018-05-15 at 10.54.30 AM.png

Once we have that rounded result, we divide by power to get the original number of decimal points that we want:

Screen Shot 2018-05-15 at 10.55.39 AM.png

Then finally, this gives us a number:

Screen Shot 2018-05-15 at 10.56.38 AM.png

But we want to transform this into a string. An easy way to do that (which is kind of funny) is that we're using the native toFixed to do that, although we're using it in a different way.

Screen Shot 2018-05-15 at 10.57.49 AM.png

We're using it strictly to convert our number to a string. Here's an example of toFixed being used to simply return a string:

Screen Shot 2018-05-15 at 10.58.39 AM.png

This seems to fix our original problem of numbers not rounding the way they should in accounting software, but let's not celebrate too early. More to come...

Summary

  • Javascript doesn't round the way we want in accounting software.
  • This is to do with some decimal numbers being not completely precise because computers have to think in terms of powers of 2.
  • AccountingJS creates its own toFixed method that essentially does this:
    • Multiply to get the number to only one decimal place
    • Use Math.round to round using more predictable behavior
    • Then divide the number back down to get it to the number of decimal places you wanted.

 

 

← Mastering Javascript Fundamentals: AccountingJS, Better Rounding with Scientific NotationMastering Javascript Fundamentals: AccountingJS, checkCurrencyFormat →
shotty-skinny2x.jpg

Shotty - Faster Access To Your Screenshots on Mac

Shotty is an award-winning Mac app I created to give you instant access to all your recent screenshots, right from the menu bar. You can even add annotations on-the-fly. Stop wasting time digging through Finder for your screenshots. I promise it’ll change your workflow forever (just read the App Store reviews!).



Most popular

information-architecture

Information Architecture: The Most Important Part of Design You're Probably Overlooking

Follow @JacobRuizDesign