Debugging Microservices & Distributed Systems
4 min read
intermediate

Precise Decimal Math in JavaScript with Fraction.js

How to handle exact decimal calculations in JavaScript when floating-point precision isn't good enough

Ever tried to do precise calculations in JavaScript? These issues can cause serious problems, especially when dealing with money, percentages, or any calculations that need to be exact.

JavaScript
// Basic arithmetic issues
0.1 + 0.2 // 0.30000000000000004
1.0 - 0.9 // 0.09999999999999998
0.7 + 0.1 // 0.7999999999999999
// Division problems
1/3 // 0.3333333333333333
1/6 // 0.16666666666666666
// Multiplication can be surprising
0.3 / 0.1 // 2.9999999999999996
1/98 * 98 // 0.9999999999999999
// Money calculations break
19.99 * 0.1 // 1.9989999999999999

This isn’t a JavaScript bug. It’s a fundamental limitation of how computers store decimal numbers, and it affects nearly every programming language. To understand why we need Fraction.js, we first need to understand what’s really happening under the hood.

Computers store numbers in binary (base-2). While this works perfectly for whole numbers, it creates problems with decimals.

In base-10 (our decimal system), we can represent 1/10 simply as 0.1. But in binary, 0.1 is a repeating fraction - like trying to represent 1/3 in decimal (0.333333…). When this gets stored in JavaScript’s 64-bit floating-point format (IEEE 754), it has to be truncated, leading to tiny rounding errors.

You might think BigInt could solve this problem, but it has its own limitations:

JavaScript
// BigInt only works with whole numbers
BigInt(1) / BigInt(3) // Error: Cannot divide BigInts
BigInt(0.1) // TypeError: Cannot convert decimal to BigInt

BigInt solves precision for whole numbers but can’t handle decimals at all. It’s designed for different use cases like cryptography or very large integer calculations.

A common workaround is to multiply everything by 100 to avoid decimals entirely:

JavaScript
// Common attempt: multiply to remove decimals
const dollars = 19.99 * 100 // 1999 cents
const tax = 8.5 * 100 // 850 basis points
// Still breaks down
console.log((dollars * tax) / 10000) // 169.91499999999996 - More rounding errors

This approach looks simple but falls apart with complex calculations or multiple operations.

Working with Fractions Instead of Decimals

Let’s solve these precision problems using Fraction.js:, a library that handles numbers as fractions instead of floating-point decimals. Just like we learned math in school: 1/3 is more precise than 0.333333.

Terminal window
npm i fraction.js
Fraction.js uses a BigInt representation for both the numerator and denominator, ensuring minimal performance overhead while maximizing accuracy. Its design is optimized for precision, making it an ideal choice as a foundational library for other math tools, such as Polynomial.js and Math.js
JavaScript
import Fraction from 'fraction.js';
// Basic arithmetic is now exact
new Fraction(0.1).add(0.2).toString(2) // "0.30"
new Fraction(1.0).sub(0.9).toString(2) // "0.10"
new Fraction(0.7).add(0.1).toString(1) // "0.8"
// Division with controlled precision
new Fraction(1, 3).toString(2) // "0.33"
new Fraction('1/6').toString(2) // "0.17"
// Money calculations stay precise
const price = new Fraction('19.99')
const tax = new Fraction('0.085') // 8.5% tax
const total = price.add(price.mul(tax))
total.toString(2) // "21.69"
// For fractions, we might want the actual fraction representation
new Fraction('0.333333').toFraction() // "1/3"
new Fraction('1.4166666').toFraction() // "17/12"

Instead of trying to represent decimals in binary, which causes those rounding errors we saw earlier, Fraction.js stores each number as a ratio of whole numbers. For example, 0.1 becomes 1/10 internally. This means our calculations stay exact, just like they would on paper.

Understanding the Limitations

Fraction.js is powerful, but it’s not magic. It works with rational numbers - numbers that can be expressed as a ratio of integers (like 3/4 or 22/7). This makes it perfect for money calculations or simple fractions, but not so great for irrational numbers like π or √2.

While it uses BigInt internally, you’re still working with JavaScript’s number system at the edges.

This means you get more precision than regular decimals, but not infinite precision. Some operations have limitations too. Square roots give approximations rather than exact values, and transcendental functions like sin or cos aren’t supported. Complex numbers are also off the table.

Even with these limitations, Fraction.js is incredibly useful. You can use it to calculate exact values ahead of time, handle money calculations without rounding errors, build educational software that works with fractions, or solve any problem where decimal precision really matters. The key is knowing when to use it and when something else might be more appropriate.


Related Articles

If you enjoyed this article, you might find these related pieces interesting as well.

Recommended Engineering Resources

Here are engineering resources I've personally vetted and use. They focus on skills you'll actually need to build and scale real projects - the kind of experience that gets you hired or promoted.

Imagine where you would be in two years if you actually took the time to learn every day. A little effort consistently adds up, shaping your skills, opening doors, and building the career you envision. Start now, and future you will thank you.


This article was originally published on https://www.trevorlasn.com/blog/fraction-numbers-in-javascript. It was written by a human and polished using grammar tools for clarity.

Interested in a partnership? Shoot me an email at hi [at] trevorlasn.com with all relevant information.