why this Floating point addition can be solved only by multiply all the figures by ten?

121 Views Asked by At

Because of Floating point arithmetic in Javascript, i understand 0.2 + 0.1 = 0.30000000000000004 will be in Javascript, but i don't understand why only multiply the figures by 10 i.e. let x = (0.2 * 10 + 0.1 * 10) / 10;, then the problem can be solved. Should be multiply by 10**10, right?

let y = (0.2 * 10 + 0.1 * 10) / 10;
document.getElementById("abc").innerHTML = "0.2 + 0.1 = " + y;
<!DOCTYPE html>
<html>

<body>

  <p id="abc"></p>

</body>

</html>

3

There are 3 best solutions below

3
Barmar On

When you multply by 10 the extra bits get rounded off, and this luckily ends up producing integers. But it's not always guaranteed, it just happens to work for these two numbers.

The proper solution is to use explicit rounding after multiplying by 10.

let y = (Math.round(0.2 * 10) + Math.round(0.1 * 10)) / 10;
document.getElementById("abc").innerHTML = "0.2 + 0.1 = " + y;
<!DOCTYPE html>
<html>

<body>

  <p id="abc"></p>

</body>

</html>

1
Sam Mason On

You seem to be mixing bases without specifying when you're using which one. It also helps to know that an IEEE754 implementation will give correctly rounded operations after each operation.

As you pointed out, 0.1 can't be represented exactly as a binary fraction. The nearest value (for a 64bit float) is:

0.1000000000000000055511151231257827021181583404541015625

when you're saying "0.00011 0011 0011 0011... 0011" it would help to say that these are base-2 digits, not decimal digits.

When you say 0.1 * 10, you're actually asking for:

10 * 0.1000000000000000055511151231257827021181583404541015625

and the computer does that exact calculation and then round that to the nearest representable float. The other values near 1.0 (which can be represented accurately by a 64bit binary float) are:

0.99999999999999988897769753748434595763683319091796875
1.0000000000000002220446049250313080847263336181640625

These are both approx 1.7e-16 away from the number, while the error associated with choosing 1 is only ~0.5e-16. Hence the FPU should choose 1.

The same applies to 0.2, which gets rounded to exactly 2.

Unfortunately I don't think Javascript exposes many useful primitives to see what's going on, so I've been using Python as it's good for interactively exploring things like this.

# arbitrary precision decimal
from decimal import Decimal as D

# see https://en.cppreference.com/w/c/numeric/math/nextafter
from math import inf, nextafter

print(D(0.1))
# => 0.1000000000000000055511151231257827021181583404541015625

print(D(nextafter(1, inf)))
# => 1.0000000000000002220446049250313080847263336181640625

print(D(nextafter(1, -inf)))
# => 0.99999999999999988897769753748434595763683319091796875

print(D(nextafter(1, -inf)) - D(0.1)*10)
# => -1.665334536935156540423631668E-16

hopefully that gives you some idea of what's going on internally!

0
Monke On

Well multiplying by any number as long as both numbers are then integers, can be operated on. 10 is just a great number for this because it's just powers of 10 that are needed to convert the decimal places into the integer part. The reason why getting rid of the decimal places is helpful is because binary can then better represent the number, for example 1/3 isn't represented as 0.333... because it's redundant. The same issues happen with binary happen but with different numbers due to it using powers of 2 and other methods of representing decimals and eventually it has to do something. That is why 0.1 + 0.2 = 0.300...4. I'm not necessarily sure why you would think 10^10 is supposed to be used, it might have to do with the 0.300...4 but the answer is most likely wrong with float-point answers so that's why the equation's terms are multiplied, not the answer, also more floating-point issues could occur leaving an even worse result. Here is my function that I like to use to solve annoying arithmetic floating-point issues.

//z = 1 or 0 (+ or -)
function float_operator(x, y, z){
    const precision = Math.max((Number.isNaN(x) ? 0 : (x.toString().split(".")[1] || "").length),(Number.isNaN(y) ? 0 : (y.toString().split(".")[1] || "").length)) || 1;
    const multiplier = Math.pow(10, precision);
    const result = z === 1 ? (x * multiplier + y * multiplier) / multiplier : (x * multiplier - y * multiplier) / multiplier;
    return result;
}
//> float_operator(0.1, 0.2, 1)
//> 0.3