-1

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>
Barmar
  • 741,623
  • 53
  • 500
  • 612
eric2023
  • 9
  • 2
  • Why should it be 10**10? I mean, you've got the evidence right there that 10 is the correct value. – Pointy Jul 26 '23 at 14:27
  • "0.1" in Javascript exactly is 0.00011 0011 0011 0011... 0011, to make it becomes an integer, it should by multiply by something like 10**10, otherwise, there are still remaining figures.. – eric2023 Jul 26 '23 at 14:35
  • Where does `10**10` come from? – jabaa Jul 26 '23 at 14:36
  • 2
    `Because of Floating point arithmetic in Javascript,` Just a heads up, this is not a Javascript thing, it's how computer store numbers. IEEE 754. eg. lets try Rust for example -> `println!("{}", 0.1 + 0.2);` = `0.30000000000000004` – Keith Jul 26 '23 at 14:37
  • No, i had read the above link already, because I read that therefore i ask this question, "0.1" in Javascript exactly is 0.00011 0011 0011 0011... 0011, to make it becomes an integer, it should by multiply by something like 10**10, otherwise, there are still remaining figures.. – eric2023 Jul 26 '23 at 14:37
  • The decimal number 0.1 can't be exactly represented as binary number. It has infinite decimal digits. It's 0.0001100110011001101... It will never stop. You can't store the binary representation (IEEE 754) in memory. You always have rounding errors. 10\**10 and even 10**1000 won't solve the problem. – jabaa Jul 26 '23 at 14:38
  • That's not what 0.1 looks like, also. It is a repeating fraction, but floating point values are biased by a power of 2, so 0.1 is represented as x*2^8, with x bein 1.0 + the repeating fraction mantissa. – Pointy Jul 26 '23 at 14:41
  • Yes, "0.1" has infinite decimal digits, then why this floating addition can be solved simply by multiply all the figures by 10, not "10**10"? "10**10" can guarantee round up all the decimal figures to integer – eric2023 Jul 26 '23 at 14:41
  • `0.2 * 10` = `2` , `0.1 * 10` = `1` `2+1 = 3` <--- fits into integer.. – Keith Jul 26 '23 at 14:42
  • It doesn't eliminate the problem. It reduces the rounding error. 0.1+0.2 has a bigger rounding error than (1+2)/10. (1+2)/10 is still not exactly 0.3 in memory. – jabaa Jul 26 '23 at 14:42
  • 0.1*10 should not be equal to 1, should be something like 1.00000000XX0XXX in return in Javascript – eric2023 Jul 26 '23 at 14:44
  • But 0.1*10 is equal 1: https://jsfiddle.net/qkyaLvdp/ Programming language compilers and interpreters have optimizers. I assume 0.1*10 is calculated as 10/10. – jabaa Jul 26 '23 at 14:45
  • @eric2023 [you can play around with floating point operations on this page.](http://weitz.de/ieee/) In binary floating point (IEEE754), 0.1 is a repeating fraction, and 10 is not. The result of the multiplication is the exact value 1 (well, exact as it can be; there is no repeating fraction part). – Pointy Jul 26 '23 at 14:49
  • Also [this explanation is possibly useful](https://www.gamedeveloper.com/programming/in-depth-ieee-754-multiplication-and-addition). – Pointy Jul 26 '23 at 15:02
  • [Also this video](https://www.youtube.com/watch?v=I0ol63OXojc) – Pointy Jul 26 '23 at 15:15

2 Answers2

0

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>
Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Many thanks for your reply and help, but could you illustrate how the extra bits get rounded off and luckily ends up producing integers? 0.1 equal to 1.10011 *2exponent(-4), right? 10 equal to 1.01 * 2exponent(3)), right? how luckily ends up producing integers? Many thanks. – eric2023 Jul 27 '23 at 07:29
  • I'm not really sure I can explain it in detail. – Barmar Jul 27 '23 at 07:32
  • Re “… ends up producing integers… just happens to work for these two numbers”: This is a rough draft of a proof: Let x be a number that is some integer k divided by 10. Converting x to floating-point produces x+e for some rounding error e ≤ ½ULP(x). When we multiply by 10, 10(x+e) is rounded to floating-point. It is 10x+10e, so k+10e. 10e ≤ 10•½ULP(x) = ½ULP(k). So 10x+10e is nearer k than any other floating-point value, so the result is k. Except ULP(10x) might not actually be 10 ULP(x) when x or 10x is near a binade transition, so those need to be considered separately, hence rough draft. – Eric Postpischil Jul 27 '23 at 12:52
0

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!

Sam Mason
  • 15,216
  • 1
  • 41
  • 60
  • Julia Evans has a good article on this, see https://jvns.ca/blog/2023/02/08/why-does-0-1-plus-0-2-equal-0-30000000000000004/ – Sam Mason Jul 27 '23 at 09:37