0

I was expecting Decimal to treat number like a String but instead I reproduced small rounding during testing.

let locale = Locale(identifier: "en_GB")
let price: String? = "1535132527181273627"
let decimal: Decimal? = price != nil ? Decimal(string: price!, locale: locale) : nil
XCTAssertEqual(decimal, 1535132527181273627.0)

The result bothers me:

XCTAssertEqual failed: ("Optional(1535132527181274000)") is not equal to ("Optional(1535132527181273497.6)") -

Boris Y.
  • 2,333
  • 1
  • 26
  • 43
  • 1
    Possible duplicate of [Is floating point math broken?](https://stackoverflow.com/questions/588004/is-floating-point-math-broken) – ielyamani Aug 24 '18 at 19:03
  • 1
    Make sure you understand what `Decimal` does. It certainly does not treat number as a `String`. It just uses decadic arithmetic. Also it doesn't support numbers of any length, it supports 35 decimal digits. – Sulthan Aug 24 '18 at 21:18

3 Answers3

1

I tried your code and the result was:

XCTAssertEqual failed: ("Optional(1535132527181273627)") is not equal to ("Optional(1535132527181273497.6)")

using Xcode 9.4.1 on a MacBook Pro 2015

I think the problem is in the fact that Double is only able to deal with 15 decimal places. So the number 1535132527181273627.0 cannot be represented accurately. Take a look at Double precision - decimal places

Steve O'Connor
  • 1,445
  • 10
  • 20
1

This is a design problem of the Swift compiler. It has been reported as SR-3317 but that was closed as duplicate of SR-920.

The problem is that the literal 1535132527181273627.0 is parsed as a Double literal (that is, with limited precision) even if the type should be Decimal. Using a String to initialize Decimal is the only workaround for now.

The solution is to redesign the built-in protocol ExpressibleByFloatLiteral to allow for longer numbers.

Sulthan
  • 118,286
  • 20
  • 194
  • 245
0

So it looks like the problem is in the test, not the code. Swift uses Double to store number from the test and it's not long enough, Decimal wasn't rounded but the number to compare in the test was.

Solution is to compare Decimal.

XCTAssertEqual(price, Decimal(string:"1535132527181273627"))
Boris Y.
  • 2,333
  • 1
  • 26
  • 43
  • Why don't you make your String non optional? `let locale = Locale(identifier: "en_GB")` `let price = "1535132527181273627"` `let decimal = Decimal(string: price)` – Leo Dabus Aug 24 '18 at 20:02
  • @LeoDabus it's just part of specs I am working with, price from a server is optional so I want to compare it to the same optional type in my unit test. – Boris Y. Aug 25 '18 at 18:56