5

I am working in JavaScript, but the problem is generic. Take this rounding error:

>> 0.1 * 0.2
0.020000000000000004

This StackOverflow answer provides a nice explanation. Essentially, certain decimal numbers cannot be represented as precisely in binary. This is intuitive, since 1/3 has a similar problem in base-10. Now a work around is this:

>> (0.1 * (1000*0.2)) / 1000
0.02

My question is how does this work?

Community
  • 1
  • 1
jds
  • 7,910
  • 11
  • 63
  • 101
  • [What Every Computer Scientist Should Know About Floating-Point Arithmetic](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – adeneo Jun 11 '14 at 20:59
  • 1
    Interesting question, I guess the answer must come down to where the precision in lost – Ian Jun 11 '14 at 21:01
  • 1
    The last one can simply be written as `0.1 * 200 / 1000`, or `20 / 1000`, which of course don't have the floating point issue – adeneo Jun 11 '14 at 21:01
  • @adeneo "of course"? Explain. – Niet the Dark Absol Jun 11 '14 at 21:06
  • @adeneo: Perhaps you should read [What Every Computer Scientist Should Know About Floating-Point Arithmetic](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) – tmyklebu Jun 11 '14 at 22:56
  • @tmyklebu - I posted that 3 hours ago, and I have read it. – adeneo Jun 12 '14 at 00:10
  • 1
    @adeneo: That `0.1 * 200 == 20` is nontrivial and depends in a fundamental way on the binary expansion of `0.1`. Compare with `0.145 * 200 < 29`, for instance. This doesn't deserve to be swept under the rug with just the word 'or.' – tmyklebu Jun 12 '14 at 00:16
  • @tmyklebu - actually it depends on masking (guards), and how they are applied when one side of the expression is an integer. – adeneo Jun 12 '14 at 00:24
  • 1
    @adeneo: I don't believe it does. First, numbers in Javascript are always 64-bit floating-point. Second, I don't see how to reconcile the result of `0.145 * 200` with the scheme you seem to be proposing. Perhaps another look at [WECSSKAFPA](http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html) is in order for you. – tmyklebu Jun 12 '14 at 00:41
  • @tmyklebu - what I believe doesn't really matter, I didn't answer the question, you did, with two lines that doesn't really say anything. Try explaining it to me by posting a proper answer that explains this behaviour so anyone can understand it, and I'm sure you'll get some more upvotes. That would be time much better spent then arguing with me about how floating points works. – adeneo Jun 12 '14 at 00:48
  • And what's much more interesting than the fact that numbers are 64 bits, is that they are stored in a 53 bits mantissa, once you get how that works, it's obvious why the bitwise conversion doesn't match certain floats. – adeneo Jun 12 '14 at 00:53
  • @adeneo: I don't care about upvotes. You're confused about floating-point arithmetic and you wrote a confused comment. Go read the document you linked and clear things up in your head. – tmyklebu Jun 12 '14 at 02:21
  • @tmyklebu - Again, if you think you have a good grasp on why this is happening, post an answer that explains this behaviour, and not just one line saying "if you try other numbers it gets worse", post something more substantial that is more understandable. – adeneo Jun 12 '14 at 11:35

3 Answers3

4

It doesn't. Try 0.684 and 0.03 instead and this trick actually makes it worse. Or 0.22 and 0.99. Or a huge number of other things.

tmyklebu
  • 13,915
  • 3
  • 28
  • 57
  • 1
    It doesn't surprise me that it doesn't always work. What I'm asking is why does it work at all. For example, `(0.22 * (10 * 0.99)) / 10` is fine, but `(0.22 * (100 * 0.99)) / 100` isn't. – jds Jun 11 '14 at 21:19
  • 2
    @ggundersen: Each of these hacks gives roughly the same probability of the "desired" answer. So does the naive formula. This is pure coincidence. In particular, this hack **does no better than just multiplying the two**. – tmyklebu Jun 11 '14 at 21:24
  • 1
    @ggundersen Almost anything that changes the exact sequence of operands and operations may change the result. Sometimes the new result will be one you like better, sometimes one you don't like as well as the original. – Patricia Shanahan Jun 11 '14 at 22:55
  • 1
    @ggundersen You got lucky, and your rounding errors canceled out, rather than adding together. – Kendall Frey Jun 12 '14 at 11:42
4

It doesn't work. What you see there is not exactly 0.02, but a number that is close enough (to 15 significant decimal digits) to look like it.

It just happens that multiplying an operand by 1000, then dividing the result by 1000, results in rounding errors that yield an apparently "correct" result.

You can see the effect for yourself in your browser's Console. Convert numbers to binary using Number.toString(2) and you'll see the difference:

Console showing <code>0.1</code>, <code>0.2</code>, <code>0.1*0.2</code> and <code>((0.1*(0.2*1000))/1000</code> each with their binary representations

Correlation does not imply causation.

Niet the Dark Absol
  • 320,036
  • 81
  • 464
  • 592
0

Since the numbers are constants, the expression can be evaluated before assigning the numbers to variables, so no float variables need to be used except for the one that stores the result of the expression.

Aefix
  • 177
  • 1
  • 8
  • Assigning the values to variables and then working on the variables yields an "exact" result too. – Niet the Dark Absol Jun 11 '14 at 21:07
  • In that case, it's possible that the numbers used to represent the fraction in the floating point number were actually able to do it exactly, so there was no loss of precision. – Aefix Jun 11 '14 at 21:09