Decoding Why 0.6 + 0.3 = 0.8999999999999999 in JS and How to Solve?
Tags: javascript, hacktoberfest, programming, floatingpoint
Published: 2023-11-16 19:05:08

#### Understanding IEEE 754 Floating-Point Numbers and also exploring how 0.1+0.2 is **0.30000000000000004 step-by-step**
![](https://cdn-images-1.medium.com/max/1024/1*5kbl9RM9GT_-lbX0qyy3gg.png)

While working on an open-source calculator repository during [Hacktoberfest](https://dev.to/jeevaramanathan/first-attempt-at-open-source-contribution-hacktoberfest23i-1dkh), I noticed that certain decimal calculations were not producing the expected results, like _0.6+0.3 will not result in 0.9_ and was wondering if there was an issue with the code. However, upon further analysis discovered that it was the actual behavior of JavaScript. And delved deep into that to understand its internal workings.

In this blog post, I will share my insights and discuss a few approaches to solve this.

In everyday math, we know adding `0.6 + 0.3` equals `0.9`, right? But when we turn to computers it results in `0.8999999999999999`. Surprisingly, this doesn’t just happen only in JavaScript; **it’s the same in many** [**programming languages**](https://0.30000000000000004.com/) **like Python, Java, C too.** Also, it’s not just about this specific calculation. **There are many more decimal calculations showing similar not-quite-right answers.**

### Why does this happen?

It’s all about how computers handle floating-point numbers. Decimal numbers are stored in the computer’s memory using a format called IEEE 754 standard. IEEE Standard 754 floating point is the most common representation today for real numbers on computers. This standard includes different types of representation, mainly _Single precision(32 bits)_ and _Double precision(64 bits)_. JavaScript follows IEEE 754 Double Precision Floating Point Standard.

![](https://cdn-images-1.medium.com/max/594/0*QStVzygLfA9pM3KO)
_IEEE 754 Double Precision Floating Point Format_

Double precision is composed of 64bits including **1 sign bit, 11 exponent bits** , and **52 mantissa (fractional part) bits**.
Any decimal number given is stored in this double precision IEEE 754 binary floating point format only. Finite 64-bit representations in computer systems can’t accurately express all decimal values, especially those with infinite decimal expansions, leading to minor discrepancies in results when working with certain numbers in binary.

### Let’s understand with an example how decimal numbers are stored and unveil why 0.6+0.3 equals 0.8999999999999999

Represent `0.6` in IEEE 754 Double Precision Floating Point Format.

**# Step 1** : Converting the decimal **(0.6)₁₀** to its binary representation `base 2`

![](https://cdn-images-1.medium.com/max/125/1*mKDkHcH2uN93OZTRfExvlg.png)

_Integer Part:_ `0/2 = 0`

_Fractional Part:_   
Repeatedly multiply by 2, noting each integer part of the results, until the fractional part reaches zero.

![](https://cdn-images-1.medium.com/max/291/1*fbpk191zTQYseS17OooRAg.png)

0.1 cannot be exactly represented as a binary fraction. The highlighted part recurs endlessly, forming an infinite sequence. Also, we didn’t get any fractional part that is zero.

![](https://cdn-images-1.medium.com/max/883/1*6O_ZnS1ySMH8DaF7tXgxTQ.png)

**# Step 2:** Normalization
A number x, written in scientific notation is normalized if it has a single non-zero digit to the left of the decimal point. i.e. Force the integer part of its mantissa to be exactly 1.  
we adjust our sequence based on the IEEE standard requirements, including a finite number of 52-bit mantissa and rounding. That’s why round-off error occurs.

![](https://cdn-images-1.medium.com/max/875/1*DOCs1d3sv8ovCzuZ3ujJwA.png)_mantissa of 0.6_

**#Step 3:** Adjust the Exponent:
For double-precision, exponents in the range -1022 to +1023 are biased by adding 1023 to get a value.  
 `exponent => -1 + 1023 => 1022` Represent the value in 11 bit binary.

(1022)**₁₀ => (010111111110)₂**

Sign bit is `0` as `0.6` is a positive number. `(-1)⁰=> 1`   
Now we have all the values to represent in IEEE 754 Floating point format.

> In the process of normalizing the mantissa, the leading (the leftmost) bit 1 is removed it’s always 1 and adjust its length to 52 bits, only if necessary (not the case here).

![](https://cdn-images-1.medium.com/max/876/1*TvYE1sSaEY0NURMOESmKcQ.png)_Representing 0.6 in 64bit IEEE format._

Similarly, using the same process, 0.3 is represented as:

![](https://cdn-images-1.medium.com/max/876/1*eFiSwdgrXpWDmKlDGwavSg.png)_0.3 in 64bit IEEE format_

**# Adding the two values**
**1. _Equalizing the exponent_** 
As we have the value of `0.6` and `0.3` we have to add them. But before doing, ensure that the exponents are same. In this case, they are not equal. Therefore, we need to adjust them by matching the smaller exponent with the larger value.

`Exponent of 0.6=> -1` `Exponent of 0.3 => -2` , we have to match the `0.3` with `0.6` as exponent of 0.6 is greater than 0.3 .

Here the difference is 1, so the **mantissa of 0.3 needs to be shifted to the right by 1 bit,** and **the exponent code is increased by 1 to match 0.6**

_Shifting the mantissa by 1 bit will cause the least significant bit to be lost to maintain the 64-bit standard, which may introduce precision errors._

![](https://cdn-images-1.medium.com/max/872/1*_L2RSemVWizromS_IpZHCw.png)_Value of 0.3 after equalizing the exponent_

**2. _Add the mantissa_** 
As the exponents are equal now, we need to perform binary addition on the mantissas.

![](https://cdn-images-1.medium.com/max/879/1*qkZh6ryPwypEBthCGOMYvQ.png)

Now the value will be, `0; 01111111110; 1.1100110011001100110011001100110011001100110011001100`

**3. _Normalizing the resulting mantissa and rounding_** 
In this case, the mantissa it already normalized [has 1 as leading bit], so this step is skipped.

And finally, the result of `0.6+0.3` is represented as

![](https://cdn-images-1.medium.com/max/876/1*EQDSVr-_MF1oga3b-tptIg.png)
_Sum of 0.6+0.3 in 64bit IEEE Floating Point Representation_

So, now we have the result of `0.6 + 0.3` which is represented in 64bit IEEE format, and is machine-readable. We have to convert this back to decimal for human readability.

#### **#Converting IEEE 754 Floating Point Representation to its Decimal Equivalent**

![](https://cdn-images-1.medium.com/max/294/1*0D8XkGFq2HhQEk1u7kXXzg.png)

![](https://cdn-images-1.medium.com/max/876/1*EQDSVr-_MF1oga3b-tptIg.png)

**Determining Sign Bit:** `0 => (-1)⁰ => +1`

**Calculate unbiased exponent:**
 `(01111111110)₂ => (1022)₁₀`
 `2^(e-1023) => 2^(1022-1023) => 2^-1`

**Fraction Part:**
Summation the values of each bit position, starting from the leftmost bit of mantissa, and multiplying them by powers of 2. `1×2^-1 + 1×2⁻^-2 + 0×2^-3 + ....... + 0×2^-52`

By substituting the values, and solving the equation we get the result as **0.8999999999999999,** which is displayed on the console. _[_[_Rounding_](https://floating-point-gui.de/errors/rounding/)_]_

```js
=> +1 (1+ 1×2^-1 + 1×2⁻^-2 + 0×2^-3 + ....... + 0×2^-52) x 2^-1
=> +1 (1 + 0.79999999999999982236431605997495353221893310546875) x 2^-1
=> 0.899999999999999911182158029987476766109466552734375      
≈ 0.8999999999999999 //numbers are rounded

//Because floating-point numbers have a limited number of digits,
//they cannot represent all real numbers accurately: when there 
//are more digits, the leftover ones are omitted,i.e. the number is rounded
```

Let’s explore one more example to gain a deeper understanding of the well-known expression **0.1 + 0.2 = 0.30000000000000004.**

![](https://cdn-images-1.medium.com/max/1024/1*SxSURLdUpSuYiPWCuFWlOw.png)_Adding the 64bit IEEE 754 binary floating point value of 0.1 & 0.2_

![](https://cdn-images-1.medium.com/max/1024/1*BkEsjcSBvgL2b5yEr9SYmg.jpeg)_Converting the result back to decimal_

### How to Solve That?

Let’s see how to achieve **accurate results** when working with applications that handle currency or financial calculations, where precision is crucial.

**i) Inbuilt Function: toFixed()** and **toPrecision()**

- `toFixed()` converts a number into a string and rounds the string to the specified number of decimals.
- `toPrecision()` format a number to a specific precision or length and will add trailing zeros to fulfill the specified precision if required. _parseFloat()_ is to remove trailing zeros from a number.

```js
const num1 = 0.6;
const num2 = 0.3;
const result = num1 + num2;

const toFixed = result.toFixed(1); 
const toPrecision = parseFloat(result.toPrecision(12));

console.log("Using toFixed(): " + toFixed); // Output: 0.9
console.log("Using toPrecision(): " + toPrecision); // Output: 0.9
```

**Limitation**
toFixed() always rounds the number to the given decimal place, which might not align the same in all cases. toPrecision() is also similar, and it may not produce accurate results for very small or very large numbers, as its arguments should be between 1–100.

```js
//1. Adding 0.03 and 0.255 => expected 0.283
console.log((0.03 + 0.253).toFixed(1)) // returns 0.3

//2. Values are added as a string
(0.1).toPrecision()+(0.2).toPrecision() // returns 0.10.2
```

**ii) Third-Party Libraries**  
There are various libraries like [math.js](https://mathjs.org/), [decimal.js](https://mikemcl.github.io/decimal.js/), [big.js](https://mikemcl.github.io/big.js/) that solve the problem. _Each library functions according to its documentation. This approach is comparatively better._

```js
//Example using big.js
const Big = require('big.js');

Big.PE = 1e6; // Set positive exponent for maximum precision in Big.js

console.log(new Big(0.1).plus(new Big(0.2)).toString()); //0.3
console.log(new Big(0.6).plus(new Big(0.3)).toString()); //0.9
console.log(new Big(0.03).plus(new Big(0.253)).toString()); //0.283
console.log(new Big(0.1).times(new Big(0.4)).toString()); //0.04
```

### Conclusion

The IEEE 754 standard for storing decimal numbers can lead to minor discrepancies. Various libraries can be used to achieve more precise results. Choose the appropriate approach based on application requirements. Equivalent packages exist in other languages like [BigDecimal](https://docs.oracle.com/javase/8/docs/api/java/math/BigDecimal.html) for Java and [Decimal](https://docs.python.org/3/library/decimal.html) for python.

> **_References:_**  
> [What is Double-precision floating-point format](https://en.wikipedia.org/wiki/Double-precision_floating-point_format)
> [Convert decimal to IEEE 64 Bit Double Precision](https://binary-system.base-conversion.ro/real-number-converted-from-decimal-system-to-64bit-double-precision-IEEE754-binary-floating-point.php?decimal_number_base_ten)
> [Convert IEEE 64 Bit Double Precision to Decimal](https://www.binaryconvert.com/convert_double.html)
> [Full Precision Calculator](https://www.mathsisfun.com/calculator-precision.html)
> [https://zhuanlan.zhihu.com/](https://zhuanlan.zhihu.com/p/33333351)
> [https://0.30000000000000004.com/](https://0.30000000000000004.com/)
<hr/>

> Thanks for reading. Feel free to comment your opinion😊. Hope this post was helpful. You can hit me up on [**Linked In**](https://www.linkedin.com/in/jeevaramanathan) **. Few other posts to check here.**

- [First Attempt at Open Source Contribution: Hacktoberfest'23 Journey](https://dev.to/jeevaramanathan/first-attempt-at-open-source-contribution-hacktoberfest23i-1dkh)
- [How to Hide the Source Code in React from Dev Tools [3 different ways]](https://dev.to/jeevaramanathan/hide-the-source-code-in-react-from-dev-tools-3-different-ways-5467)
- [How To Create and Set Up a React Project with Create React App in Windows?](https://medium.com/@jeevaramanathan/how-to-set-up-a-react-project-with-create-react-app-in-windows-4f30895daefa?source=post_page-----976a0fa01640--------------------------------)

* * *