-2

Trying to create a Decimal (or NSDecimalNumber) out of the given Double value for MONEY calculations within my app. I am using Swift 4 in Xcode 10.

But it generates following values which are wrong.

let doubleVal: Double = 56.203
Decimal(doubleVal)               // Wrong Answer: 56.20299999999998976
Decimal.init(doubleVal)          // Again Wrong Answer: 56.20299999999998976

If I use the String initialization, that works correct.

Decimal(string: "56.203")!      // Correct Decimal: 56.203

I remember had the same problem in java when work with BigDecimal if call constructor with a double value. E.g. new BigDecimal(double val)... So they have this extra method valueOf BigDecimal.valueOf(amount); that creates correct BigDecimal out of the double.

How do I generate a Decimal out of a given Double value with Swift?

Cœur
  • 37,241
  • 25
  • 195
  • 267
JibW
  • 4,538
  • 17
  • 66
  • 101
  • 4
    Your `let doubleVal: Double = 56.203` is already “imprecise” because a binary floating point number cannot store that exact value. Compare https://stackoverflow.com/q/42781785/1187415 or https://stackoverflow.com/a/54621386/1187415. Creating the decimal number from a string is a correct solution. – Martin R Feb 18 '19 at 15:10
  • This is NOT regarding Rounding. I had the same problem in Android with Java and they have this extra method as a fix to the problem. BigDecimal.valueOf(amount). MONEY calculations should use Decimal to avoid these cases – JibW Feb 18 '19 at 15:12
  • This is NOT regarding Rounding or create Decimal with String value as I have mention in my question. Dont know how this become a duplcate for this two links. – JibW Feb 18 '19 at 15:21
  • 2
    @JibW floating point literals are always stored as `Double`. So if you declare a value as a floating point literal, there's no way to not lose precision and be able to later convert it into `Decimal` while keeping the correct value. This is a duplicate, since the duplicate targets do explain why the issue exists and how to overcome it. – Dávid Pásztor Feb 18 '19 at 15:23
  • 1
    Your `doubleVal` does not contain the exact value 56.203, but some approximation of it. So the precision is lost *before* your create the Decimal. Creating a Decimal from a string is a possible solution (as you already figured out). More possible approaches are shown in https://stackoverflow.com/q/42781785/1187415. – Martin R Feb 18 '19 at 15:25
  • 2
    Note that Java's [BigDecimal.valueOf()](https://docs.oracle.com/javase/7/docs/api/java/math/BigDecimal.html#valueOf(double)) converts the double to a string first and then creates the BigDecimal from that string ... – Martin R Feb 18 '19 at 15:55
  • 1
    The other way, besides using `Decimal(string: "56.203")`, is to specify the exponent and integer significand, e.g. `let value = Decimal(sign: .plus, exponent: -3, significand: 56203)` – Rob Feb 18 '19 at 18:15

1 Answers1

1

When Swift is converting a base 10 decimal number (56.203) to a binary number, there will always be some floating point error. As you have stated that you are using these for money calculation, Int is recommended (maybe multiply by 1000 when showing to users?).

KeroppiMomo
  • 564
  • 6
  • 18
  • That's why this Decial class is there to avoid these floating poit errors. String initialisation works ok it's this double initialiazation have the issue. Surely there should be a proper way to overcome this like JAVA HAS. – JibW Feb 18 '19 at 15:16
  • If multiply by thousand it is 56202 which is wrong. Right one should be 56203 , when multiply by 1000, yeah? – JibW Feb 18 '19 at 15:23
  • 1
    @JibW Actually, even in Java you have a double. `BigDecimal` then does some mathematical magic to convert the double to a `String` using approximations (therefore it is not the exact decadic value). Then it initializes `BigDecimal` from a `String`. Actually, Swift will have this, but it doesn't yet. Initialize `Decimal` from a `String`. – Sulthan Feb 18 '19 at 17:36