Is JavaScript unsuitable for financial or precise calculations ?
Dealing Javascript's floating-point numbers.
Last updated
Dealing Javascript's floating-point numbers.
Last updated
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.
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:
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.
But why does this happen?
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.
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.
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.
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.
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.
Another way to solve this, by rounding it to 1 decimal places ( you can also take 5)
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.
Instead of comparing like this
Use can also use Math.abs()
apart from converting them Integer or rounding them, while comparing floating-point numbers
Question: Do you know why following snippets return incorrect value?
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.
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.
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 .
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.
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).