1

I've been doing some calculations in my App and only with certain number combinations I'm getting a very weird number:

extension Double {
    var fives: Double { return round(self * 20) / 20 }
}

let payed: Double = 97
let price: Double = 70
let tipps: Double = 48

let discount: Double = (100 - 30) / 100
let newPrice: Double = (price * discount).fives

print(newPrice - payed + tipps)

The results should print out 0.0 (which it does in playground), but sometimes in my app it prints out 7.105427357601e-15. I'm not doing anything asynchronously besides some animations and removing / inserting some table rows.

This "strange" behaviour is fixed when I don't use the extension fives. When fives is removed, then everything works just normally.

I know the information might not be enough to know what exactly is happening, but perhaps someone has an idea of what could be happening?

Henny Lee
  • 2,970
  • 3
  • 20
  • 37
  • At what point do you see a crash? – Sergey Kalinichenko Apr 02 '16 at 22:36
  • `print(newPrice - payed + tips)` <-- probably not related to error but you have a typo here w.r.t. declaration of immutable `tipps` (or vice versa). – dfrib Apr 02 '16 at 22:39
  • @dasblinkenlight: It crashes at a condition I have: when `result > 0` a row is getting inserted. Because it should be `0` so it doesn't expect a new row, but because of this strange number it is getting added so it crashes. – Henny Lee Apr 02 '16 at 22:41
  • @dfri: Sorry, these are all made up names to make the question easier to read. `tipps` is used so they have the same length, it's a very annoying bad habit of mine sorry. – Henny Lee Apr 02 '16 at 22:42
  • 2
    @HennyLee sounds like a floating point precision issue; `7.105427357601e-15` is in essence `0.0` in the eyes of floating point type `Double`. You could include some tolerance, e.g. `let tol = 1e-5` and compare `result` as `result > tol`. – dfrib Apr 02 '16 at 22:43
  • @dfri: That's what I was thinking. Thanks I'll go try it right away! – Henny Lee Apr 02 '16 at 22:44
  • @dfri: That fixed it, should I close the question or can you post it as an answer so I can mark it as answered? – Henny Lee Apr 02 '16 at 22:48
  • @dfri : One more thing. Is there a way to avoid this or at least make it more reliable? I only happen to stumble upon this problem by accident. – Henny Lee Apr 02 '16 at 22:52
  • @HennyLee Since this question covers quite a specific error of your code but where the source of the error is very common (and well-covered on SO), I think a Q&A here won't be of much use for future readers, so imho you could close this question. Also, take care to use a tolerance/threshold that makes sense w.r.t. the units your using, there isn't a universal tolerance, see e.g. [this Q&A](http://stackoverflow.com/questions/17333/). Regarding your last comment: generally avoid "direct" double arithmetics which includes some kind of equality testing (`==`), which is the case above (`0` > `0`?). – dfrib Apr 02 '16 at 22:56
  • ... So to make your code more reliable, 1. avoid floating point arithmetics including or resembling equality testing, or 2. if unavoidable, include a tolerance to "move away" from floating point equality testing. Take care however that the tolerance should be large enough for this "moving away", but not so large as to yield invalid results w.r.t. the size of the units you are calculating with you're floating points arithmetics. – dfrib Apr 02 '16 at 22:58
  • @dfri Ok thanks a lot. You've helped me tremendously since I had no idea what was wrong and didn't really know what to search for on the internet. Was a bit reluctant on posting it here since I can only give limited info. I will close this question in an hour or so (maybe someone might find this useful). – Henny Lee Apr 02 '16 at 23:03
  • @HennyLee Happy to help! Your question is totally valid and you shouldn't feel reluctant to post questions here, as long as you include sufficient (or as much as you can) information to us to help you out, which I believe you've done above. It just turned out, in this case, that the answer is quite your-code-specific while still covering a subject that is very common here on SO (floating point arithm. issues), so the Q&A will possibly have limited use to future readers. If you'd prefer, you're naturally free to keep the thread up; if so, possibly answer it yourself with your newfound insights! – dfrib Apr 02 '16 at 23:11

1 Answers1

0

Disclaimer:

I'm still not sure whether I should delete this post or keep it as dfri pointed out because this is rather specific to my own personal code but still covers a subject which has been asked quite commonly before.

Solution:

As dfri pointed out (and many other bloggers / posts), I should avoid comparing doubles as much as possible. Still there are some cases which are unavoidable (and perhaps laziness on my side), so I've decided to convert it to a NSDecimalNumber and round it to only two decimals since that would suffice my need of "accuracy" (which isn't accurate at all).

Using this post I made an extension:

var rounded: Double {
    let numbers = NSDecimalNumber(double: self)
    let handler = NSDecimalNumberHandler(roundingMode: .RoundPlain, scale: 2, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false)

    return numbers.decimalNumberByRoundingAccordingToBehavior(handler).doubleValue
}

print(0.00000001.rounded) // 0.0
print(0.00500000.rounded) // 0.01

Then I only used rounded on places where I'm storing the value or on computed properties so the values used for comparisons will always only have an accuracy of two decimals.

Probably the best solution would be to use NSDecimalNumber directly but I'd prefer to use Swift as much as possible.

I hope this solution doesn't cost me too much but even then, it would only be called a few times per minute when the app is used.

Any points on how to improve is always welcome!

Community
  • 1
  • 1
Henny Lee
  • 2,970
  • 3
  • 20
  • 37