2

I've read a lot that NSDecimalNumber is the best format to use when using currency.

However, I'm still getting floating point issues.

For example.

let a: NSDecimalNumber = 0.07 //0.07000000000000003
let b: NSDecimalNumber = 7.dividing(by: 100) //0.06999999999999999

I know I could use Decimal and b would be what I'm expecting:

let b: Decimal = 7 / 100 //0.07

I'm using Core Data in my app. So I'm stuck with NSDecimalNumber. Unless I want convert a lot of NSDecimalNumbers to Decimals.

Can someone help me get 0.07?

Cœur
  • 37,241
  • 25
  • 195
  • 267
LateNate
  • 763
  • 9
  • 22
  • 2
    You should rather use a `NumberFormatter` when displaying currency data to the user. – Dávid Pásztor Feb 05 '18 at 23:29
  • 1
    Just use NumberFormatter to display your number. Btw 0.06999999999999999 and 0.07 it is practically the same number. Btw I would use Decimal and cast it to NSDecimalNumber when needed – Leo Dabus Feb 05 '18 at 23:29
  • Thanks @DávidPásztor. Helped me realise I'm going about this the wrong way. – LateNate Feb 05 '18 at 23:32
  • 3
    The problem is that the `0.07` that you're assigning is itself a `Double`, which is introducing the precision issues before `NSDecimalNumber` comes into play. If you want a lossless representation of `0.07`, you need to use the `NSDecimalNumber` initializer that takes a string `"0.07"` – Alexander Feb 05 '18 at 23:57
  • "Unless I want convert a lot of `NSDecimalNumbers` to `Decimals`" ... Note, you don't really have to "convert" as it's toll-free bridged for you. So to get the `NSDecimalNumber` rendition, it just requires a simple `as NSDecimalNumber`. But I get your point. – Rob Feb 06 '18 at 02:38
  • 1
    Compare https://stackoverflow.com/questions/42781785/how-to-store-1-66-in-nsdecimalnumber – Martin R Feb 06 '18 at 03:06
  • @MartinR - Sorry, I didn’t see your answer out there (which turns out to be virtually identical to my answer; I swear I didn’t see yours!). Feel free to mark this as dupe and I can delete my answer here, if you’d like. – Rob Feb 06 '18 at 20:37
  • 1
    @Rob: There is no need to apologize or to explain yourself – I would never have thought anything else. – Martin R Feb 06 '18 at 20:48

1 Answers1

10

The problem is that you’re effectively doing floating point math (with the problems it has faithfully capturing fractional decimal values in a Double) and creating a Decimal (or NSDecimalNumber) from the Double value that already has introduced this discrepancy. Instead, you want to create your Decimal values before doing your division (or before having a fractional Double value, even if a literal).

So, the following is equivalent to your example, whereby it is building a Double representation (with the limitations that entails) of 0.07, and you end up with a value that is not exactly 0.07:

let value = Decimal(7.0 / 100.0)                                // or NSDecimalNumber(value: 7.0 / 100.0)

Whereas this does not suffer this problem because we are dividing a decimal 7 by a decimal 100:

let value = Decimal(7) / Decimal(100)                           // or NSDecimalNumber(value: 7).dividing(by: 100) 

Or, other ways to create 0.07 value but avoiding Double in the process include using strings:

let value = Decimal(string: "0.07")                             // or NSDecimalNumber(string: "0.07")

Or specifying the mantissa/significant and exponent:

let value = Decimal(sign: .plus, exponent: -2, significand: 7)  // or NSDecimalNumber(mantissa: 7, exponent: -2, isNegative: false)

Bottom line, avoid Double representations entirely when using Decimal (or NSDecimalNumber), and you won't suffer the problem you described.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 1
    Really well put. Thanks a lot! This has really helped me out. – LateNate Feb 06 '18 at 07:58
  • ```Decimal(7)``` will work for a simple digit like 7 but a more accurate approach is to use ```NSNumber(floatLiteral: 7).decimalValue```. I use ```Decimal``` for financial transactions and there are several cases where ```Decimal(7)``` like approach doesn't result in accurate results with currencies. Though that difference is minute, but it adds up in financial transactions. – zeeshan May 18 '20 at 10:04
  • I’d suggest one be careful with `NSNumber(floatLiteral: x).decimalValue` pattern, too, because that takes `x`, with any concomitant floating point issues, and simply captures those issues. I’d still advise, as a general rule, when using non-integer values, using one of the patterns above. – Rob May 18 '20 at 13:41