Dealing with Floating Point Numbers in JavaScript: Lessons Learned

dealing-with-floating-point-numbers-in-javascript:-lessons-learned

Dealing with Floating Point Numbers in JavaScript: Lessons Learned

As a developer working on an B2B E-Commerce system, I recently encountered an issue related to decimal precision that taught me a valuable lesson about handling money value in JavaScript.

The problem

A user had reported that when attempting to refund an order with a specific amount, the amount entered was off by one cent. After investigating the issue, I discovered that it was caused by how JavaScript handles floating point numbers.

The IEEE 754 standard is used by JavaScript to represent and manipulate floating point numbers. It provides a way to represent a wide range of values, including very large and very small numbers, but it can also lead to rounding errors when working with certain decimal values.

The number 19.9 was one of those floating point numbers that had this issue (there are numerous more, see title 🤣). Since cents are used in the backend and database, and not decimals, conversion from decimal to cents (integer) was required.

For an example in the BE the value is represented as 1990 and in the FE 19.9 is used for input elements and certain display features.

In this case the refund amount was multiplied by 100 to convert it to cents, the rounding issues caused the return value to be 1989.9999999999998, instead of the expected 1990.

Simple solution

To solve this issue, there were options available:

  1. I could have used (19.9 * 100).toFixed(0)

  2. Or round Math.round(19.9 * 100)

  3. Or rely on a library like decimal.js or big.js

I decided to go with the second approach, as the return value had to be a number still. The first approach required to parse it from string to integer again and the third approach was a big step for a small problem.

Most of the mainstream languages use the same standard as JavaScript, so it’s a condition on them as well.

Here is an example in C:

#include 
#include 

int main() {
    int val = 19.9 * 100;
    printf("%d", val);

    return 0;
}

Avoiding the issue getting to production

There were unit tests to verify the logic,however it did cover only the happy paths (1 or 2 values) with one invalid value being tested. While it was an OK test coverage it was not enough to reveal the floating point problem. So property testing.

Property testing is a testing method that generates random inputs to test if a program behaves correctly for a range of inputs. By using property testing in addition to unit tests, we can ensure that our code is robust and handles edge cases appropriately. In the case of the decimal precision issue, had we used property testing, we might have discovered the issue earlier.

Here is an example. I am using Vitest as a testing library and Fast-Check for property testing:

// essentially we are using `fc` from `fast-check`, but we have
import { test, fc } from '@fast-check/vitest';
import { it, describe, expect } from 'vitest';

function toCents(value: number) {
    return Math.round(value * 100);
}

describe('Money value', () => {
    test([fc.float({ min: 1, max: 1000000, noNaN: true })])(
        'is converted correctly to cents',
        floatValue => {
            const value = parseFloat(floatValue.toFixed(3));

            // using another approach in the test to get the correct value
            // so that we can confirm that `toCents` works correctly 
            const expectedValue = parseInt((value * 100).toFixed(0));

            expect(expectedValue).toEqual(toCents(value));
        }
    );
}

Conclusion

The lesson to be learned from this experience is that when working with money in JavaScript, it’s important to be aware of the limitations of the IEEE 754 standard and try to prevent similar issue from occurring in the future.

I hope that the post has been helpful and has given some insights on working with floating point numbers. 🙌

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
how-to-structure-a-marketing-dream-team-for-any-size-company

How to Structure a Marketing Dream Team for Any Size Company

Next Post
private-internet-access-avast-review

Private Internet Access Avast Review

Related Posts