1

Okay so I've read few things about this topic :

How to deal with floating point number precision in JavaScript?

Is floating point math broken?

Why can't decimal numbers be represented exactly in binary?

My question isn't why it's happening, or how to solve it.

It's about if there is a way to predict that it will happen, and then use a solution only if it'll happen. And more than just this, I'd like a low computation cost solution.

I ran few tests, and I feel like there are conditions required for this kind of problem to happen, but I'm not sure about them :

console.log("1st calc", parseFloat(-24.05) + parseFloat(24.05));

console.log("2nd calc", parseFloat(-12.003) + parseFloat(12.003) + parseFloat(-24.05) + parseFloat(24.05));

console.log("3rd calc", parseFloat(-12.003) + parseFloat(-24.05) + parseFloat(12.003) + parseFloat(24.05));

console.log("4th calc", parseFloat(12.003) + parseFloat(24.05) + parseFloat(-12.003) + parseFloat(-24.05));

console.log("5th calc", parseFloat(12.006) + parseFloat(-12.006) + parseFloat(2.007) + parseFloat(-2.007) + parseFloat(1.009) + parseFloat(-1.009));

console.log("6th calc", parseFloat(12.006) + parseFloat(2.007) + parseFloat(1.009) + parseFloat(-12.006) + parseFloat(-2.007) + parseFloat(-1.009));

console.log("7th calc", parseFloat(12.05) + parseFloat(2.003) + parseFloat(1.005) + parseFloat(7.01) + parseFloat(-12.05) + parseFloat(-2.003) + parseFloat(-1.005) + parseFloat(-7.01));

console.log("8th calc", parseFloat(12.05) + parseFloat(-12.05) + parseFloat(2.003) + parseFloat(-2.003) + parseFloat(1.005) + parseFloat(-1.005) + parseFloat(7.01) + parseFloat(-7.01));

I got it that it's about whether the number has an exact binary representation or not, but to know this it's a long computation, so is there a faster/easier way to predict it or not ?

The thing is, I'd like to have a script that tests if it has an exact binary representation or not, and then apply a solution only if needed. But I'd like it to be faster than just apply the solution to every float. I don't know if it's possible.

Something like

def power_of_2?(number)
 return true if number == 1
 return false if number == 0 || number % 2 != 0
 power_of_2?(number / 2)
end

Might help to know wether the number has an exact binary representation, but I'm not even sure it's accurate enough for numbers such as 1.300005407.

For example, using the numbers I wrote in the tests above, I feel like it's faster to multiply them all by 1000 inside the parseFloat() (which solves the problem here) than testing them one by one.

Something I noticed too with my tests, is that according to the way you add the numbers, the problem occurs or not :

console.log("7th calc", parseFloat(12.05) + parseFloat(2.003) + parseFloat(1.005) + parseFloat(7.01) + parseFloat(-12.05) + parseFloat(-2.003) + parseFloat(-1.005) + parseFloat(-7.01));

console.log("8th calc", parseFloat(12.05) + parseFloat(-12.05) + parseFloat(2.003) + parseFloat(-2.003) + parseFloat(1.005) + parseFloat(-1.005) + parseFloat(7.01) + parseFloat(-7.01));

uses the same numbers, just not in the same order and give a totally different result.

Any idea ?

Community
  • 1
  • 1
derOtterDieb
  • 545
  • 4
  • 12
  • 35
  • 1
    I don't think that there is any easy solution. You could first see how much error (if any) a given instance of `parseFloat` introduces (a comparatively easy task) and then use numerical analysis results to bound the error on a sum as a function of the errors in the terms. Any good book in numerical analysis will discuss these sorts of issues. – John Coleman Mar 15 '17 at 17:01
  • As far as getting a precise handle on the error introduced by `floatParse`, this might help: http://croquetweak.blogspot.com/2014/08/deconstructing-floats-frexp-and-ldexp.html – John Coleman Mar 15 '17 at 17:12
  • 1
    (1) you should always expect floats will be imprecise and code accordingly, or at least that has always been considered a best practice as far as I am aware (2) it is known that addition of floating point values is not associative (your last point) and there are methods for minimizing the accumulated error. I forget the general algorithm(s) but for positive-only it can be shown that adding in order from smallest to largest is optimal. – Patrick87 Mar 15 '17 at 17:55
  • If this is about the floating point implementation in a specific language, please add the language tag. – m69's been on strike for years Mar 15 '17 at 18:00
  • @m69 Actually the language doesn't matter here, I gave examples in JS because that's the language I used to test it, but it could be anything. – derOtterDieb Mar 15 '17 at 21:27

1 Answers1

2

Well, the stupidly easy, but not perfect, approach is to parse at higher precision than you need, and check that converting to your desired precision doesn't change the value. That isn't quite right, since the rare value will not be representable as a double, but the closest double representation will be an exact float (e.g. 1.00000000000000001).

The perfect but not necessarily practical approach is to parse twice, each time with a different floating point rounding mode: First round-to-plus-infinity, then round-to-minus-infinity. If the two results are the same, the input was exactly representable. The problem is, not all string-to-float implementations behave properly WRT rounding mode, so you'll need to test whether this works for you. (And, of course, this is only relevant in environments where you get to control the rounding mode.) This approach is also appropriate for operations other than string-to-float, but there are better-adapted methods for certain operations like subtraction.

You might also consider converting the floating-point value back to a string and doing string comparison. That's fiddly, though, since you'll have to worry about whether your input is in canonical form.

Sneftel
  • 40,271
  • 12
  • 71
  • 104
  • Well, the "perfect" solution seems to be a bit too "heavy", parsing twice will take longer than simply multiply everything I guess. But it seems to be the "good" way to do it yes. Your first solution works somehow, but I'd really prefer something like the "perfect solution" but with a lower cost. Seems impossible though. I'm not sure string comparison would fit my needs also. Thanks for your answer anyway, I never thought about parsing twice before ! – derOtterDieb Mar 16 '17 at 09:19
  • @kazu I guarantee you that parsing twice will take you far less time than it took you to actually get the strings from wherever you got them from. – Sneftel Mar 16 '17 at 10:38
  • Oh, ok, I didn't know that. I'll try it then, and I'll accept your answer after tessting if it works. I'll let you know if I encounter any troubles. – derOtterDieb Mar 16 '17 at 10:47