Is JavaScript unsuitable for financial or precise calculations ?
Dealing Javascript's floating-point numbers.
Azeem Chauhan | LinkedIn
I stumbled upon a knowledge gap while exploring the world of precise calculations in JavaScript. Intrigued, I decided to share my learnings and demystify the topic with clear explanations and examples in this article.
The Problem
Javascript just like other programming languages (e.g Go Language), can be used for financial computation where accuracy to reasonable precision matters.
Let think about the
(Bitcoin) treading application, sells/purchase Bitcoins in different currencies and levy
0%
commission on each transactions in Bitcoin. Current Price of Bitcoin is1 BTC = 8340213.20 INR
An user want to make purchase of some part of Bitcoin, for example, A User want to purchase worth of 11 INR only, another user want to purchase of 101 INR only. According to Bitcoin current Price: ->11 INR =0.000001318911128 BTC
->101 INR = 0.000012110002176 BTC
(* Calculation performed in scientific calculator )
Let's see hypothetical but possible scenario, where you might find yourself scratching your head when dealing with floating numbers:
let bitcoinINR = 8340213.20;
let firstTransaction = 11; // 15
let secondTransaction = 101;
let thirdTransaction = firstTransaction + secondTransaction;
let firstTransactionBTCValue = (1/bitcoinINR) * firstTransaction;
let secondTransactionBTCValue = (1/bitcoinINR) * secondTransaction;
let sumOfFirstTwoTransactions = firstTransactionBTCValue + secondTransactionBTCValue;
let thirdTransactionBTCValue = (1/bitcoinINR) * thirdTransaction;
console.log("First Transaction BTC value:" + firstTransactionBTCValue);
console.log("Second Transaction BTC value:" + secondTransactionBTCValue);
console.log("Sum of First and Second Transaction BTC:" + sumOfFirstTwoTransactions);
console.log("Third Transaction BTC value:" + thirdTransactionBTCValue);
console.log("Compare (First + Second) transaction and Third transaction: " + (sumOfFirstTwoTransactions === thirdTransactionBTCValue));
First Transaction BTC value:0.0000013189111280752389
Second Transaction BTC value:0.000012110002175963558
Sum of First and Second Transaction BTC:0.000013428913304038797
Third Transaction BTC value:0.000013428913304038797
Compare (First + Second) transaction and Third transaction: true
Above Javascript snippet is producing the expected results, Purchasing Bitcoin worth of 11 INR
and 101 INR
separately is equal to Bitcoin worth of 112 INR
.
Let's update the firstTransaction
to 15
and see, now Sum of First + Second transaction is not equal third transaction.
First Transaction BTC value:0.000001798515174648053
Second Transaction BTC value:0.000012110002175963558
Sum of First and Second Transaction BTC:0.00001390851735061161
Third Transaction BTC value:0.000013908517350611612
Compare (First + Second) transaction and Third transaction: false
Purchasing Bitcoin worth of 15 INR
and 101 INR
separately should be equal to Bitcoin worth of 116 INR
but in Javascript, they are not equals 🤔.
But why does this happen?
Floating-Point Numbers
A number is stored in memory in its binary form, a sequence of bits – ones and zeroes. But fractions like 0.1
, 0.2
that look simple in the decimal numeric system are actually unending fractions in their binary form.
console.log(0.1.toString(2)); // 0.0001100110011001100110011001100110011001100110011001101
console.log(0.2.toString(2)); // 0.001100110011001100110011001100110011001100110011001101
console.log((0.1 + 0.2).toString(2)); // 0.0100110011001100110011001100110011001100110011001101
In Decimal Numeral System
What is 0.1
? It is one divided by ten 1/10
, one-tenth. such numbers are easily representable.
Compare it to one-third: 1/3
. It becomes an endless fraction 0.33333(3)
.
So, division by powers 10
is guaranteed to work well in the decimal system, but division by 3
is not.

In Binary Numeral System
For the same reason, the division by powers of 2
is guaranteed to work, but 1/10
becomes an endless binary fraction.
"There’s just no way to store exactly 0.1 or exactly 0.2 using the binary system, just like there is no way to store one-third as a decimal fraction."
Internally, a number is represented in 64-bit format IEEE-754, so there are exactly 64 bits to store a number: 52 of them are used to store the digits, 11 of them store the position of the decimal point, and 1 bit is for the sign. Thus, JavaScript engines convert floating-point numbers into binary.
Underlying Representation: Computers fundamentally operate using binary (base-2) code. This means that all data, including numbers, must be represented in binary format for the computer to understand and process it.
IEEE 754 Standard: JavaScript, like most programming languages, adheres to the IEEE 754 standard for representing floating-point numbers. This standard defines how decimal numbers are converted into a binary format with three components:
Sign (1 bit): Indicates whether the number is positive or negative.
Exponent (52 bits): A binary integer that determines the magnitude of the number.
Mantissa or Significand (11 bits): A binary fraction that represents the precision of the number.
The Conversion Process ->
Step - 1
Decimal to Binary
The decimal number is converted into its binary equivalent. This can involve converting both the integer and fractional parts.
Step - 2
Normalization
The binary number is normalized to a specific format defined by the IEEE 754 standard.
Step - 3
Split into Components:
The normalized binary number is then split into the sign, exponent, and mantissa components
Step - 4
Storing the Components
These components are stored in a specific memory layout according to the IEEE 754 standard (usually 32 or 64 bits).
Storing the components to numeric format IEEE-754 solves this by fitting it (rounding) to the nearest possible number. These rounding rules normally don’t allow us to see that “tiny precision loss”, but it exists.
You've likely seen this classic example countless times For example,
0.1 + 0.2
results in0.30000000000000004
instead of0.3
.
Best Practices to mitigate inaccuracies in Javascript calculations:
Use integers: Whenever possible, work with integers (e.g., cents instead of dollars) to avoid floating-point errors. You can convert back to decimal format for display purposes.
Rounding: Implement appropriate rounding strategies to minimize the impact of floating-point errors.
Libraries: Consider using specialized libraries like:
Big.js: Handles arbitrary-precision decimal arithmetic.
Decimal.js: Provides another robust solution for decimal calculations.
Dinero.js: Specifically designed for working with monetary values.
Math.js: Offers a broader range of mathematical functions, including financial ones.
Using different approached to solve famous problem 0.1 + 0.2
Integers Approach:
In order to solve this, first we need to make these number close to integer.
First multiply each number by 10
and then sum up both number and then convert them to decimal.
// This factor means, we want to support calculation with precision upto 20 decimal places.
let precisionFactor = 10000000000
let firstNumber = 0.1;
let secondNumber = 0.2;
let sum = (firstNumber*precisionFactor + secondNumber*precisionFactor)/factor;
console.log(sum); // 0.3
Rounding Approach:
Another way to solve this, by rounding it to 1 decimal places ( you can also take 5)
let firstNumber = 0.1;
let secondNumber = 0.2;
let sum = firstNumber + secondNumber;
console.log(+sum.toFixed(1); // 0.3
Let's fix our hypothetical scenario: Let's first identify how much precise calculation, we want. lets say for our use, we are okay with precision upto 10 decimal places.
Mix Approach (Integer + Rounding approaches):
// This factor means, we want to support calculation with precision upto 10 decimal places.
let precisionFactor = 10000000000
let bitcoinINR = 8340213.20;
let firstTransaction = 15;
let secondTransaction = 101;
let thirdTransaction = firstTransaction + secondTransaction;
let firstTransactionBTCValue = Math.round((1/bitcoinINR) * firstTransaction * precisionFactor);
let secondTransactionBTCValue = Math.round((1/bitcoinINR) * secondTransaction * precisionFactor);
let sumOfFirstTwoTransactions = firstTransactionBTCValue + secondTransactionBTCValue;
let thirdTransactionBTCValue = Math.round((1/bitcoinINR) * thirdTransaction * precisionFactor);
console.log("First Transaction BTC value:" + firstTransactionBTCValue/precisionFactor);
console.log("Second Transaction BTC value:" + secondTransactionBTCValue/precisionFactor);
console.log("Sum of First and Second Transaction BTC:" + sumOfFirstTwoTransactions/precisionFactor);
console.log("Third Transaction BTC value:" + thirdTransactionBTCValue/precisionFactor);
console.log("Compare (First + Second) transaction and Third transaction: " + (sumOfFirstTwoTransactions === thirdTransactionBTCValue));
First Transaction BTC value:0.0000017985
Second Transaction BTC value:0.00001211
Sum of First and Second Transaction BTC:0.0000139085
Third Transaction BTC value:0.0000139085
Compare (First + Second) transaction and Third transaction: true
Now we have fixed this, and purchasing Bitcoin worth of 15 INR
and 101 INR
separately should be equal to Bitcoin worth of 116 INR
upto 10th decimal places. 🎉
Comparing floating-point number
Instead of comparing like this
console.log(0.1 + 0.2 === 0.3); // false
Use can also use Math.abs()
apart from converting them Integer or rounding them, while comparing floating-point numbers
console.log(Math.abs((0.1 + 0.2) - 0.3) < 0.0001) // true
Question: Do you know why following snippets return incorrect value?
console.log( 9999999999999999 ); // shows 10000000000000000
Answer: Large number handling: JavaScript may struggle with extremely large numbers, potentially leading to inaccuracies or unexpected behaviour. JavaScript stores numbers using 64 bits, but only 52 bits are available for actual digits (Remember: IEEE-754). This limited space can cause precision loss when representing some large numbers. JavaScript tries to fit the number into this format, but it's not always big enough, leading to inaccuracies.
Exercise
Question 1 : If you add 11 dimes ($0.10 ) to an empty piggyBank, what is the final balance? and try to fix the value if you got incorrect value.
Question 2 : Suppose you want to save some money to buy a gift for your friend. Write a program that randomly places nickels ($0.05), dimes ($0.10), and quarters ($0.25) into an empty piggy bank until it contains at least $20.00
.
Display the running balance of the piggy bank after each deposit, formatting it with an appropriate width and precision.
Last updated