4

I am trying to create a function in Swift that accepts an integer as a param and returns a double in the locale currency e.g:

input : 1250

output : £12.50

input: 12500

output: £125.00

I noticed there is a third party library that supports this but unfortunately the repo is archived. The type of units used is the smallest type of currency which is minorUnits.

Network Call

/// GET - Retrive a feed of transactions during a certain period
static func get(accountUID: String, categoryUID: String, queryStartDate: String, queryEndDate: String , completionHandler: @escaping ([FeedItem], Error?) -> Void) {
    let sessionObject : URLSession = URLSession.shared
    let taskObject = sessionObject.dataTask(with: URLS().feedURLObject(accountUID,categoryUID,queryStartDate,queryEndDate)) { (Data, Response, Error) in
        guard Error == nil else {
            return
        }
        guard let Data = Data else {
            return
        }
        do {
            let feed = try JSONDecoder().decode(Feed.self, from: Data).feedItems.filter({ (item) -> Bool in
                item.direction == Direction.out
            })
            completionHandler(feed, nil)
        } catch let error  {
            print(error.localizedDescription)
        }
    }
    taskObject.resume()
}

Model Feed Struct Amount

struct Amount: Codable {
let currency: Currency
let minorUnits: Int}

Single Item JSON response

FeedItem(feedItemUid: "0651afe9-f568-4623-ad26-31974e26015c", categoryUid: "a68f9445-4d59-44e5-9c3f-dce2df0f53d2", amount: Banking_App.Amount(currency: Banking_App.Currency.gbp, minorUnits: 551), sourceAmount: Banking_App.Amount(currency: Banking_App.Currency.gbp, minorUnits: 551), direction: Banking_App.Direction.out, updatedAt: "2020-02-04T14:09:49.072Z", transactionTime: "2020-02-04T14:09:48.743Z", settlementTime: "2020-02-04T14:09:48.992Z", source: Banking_App.Source.fasterPaymentsOut, status: Banking_App.Status.settled, counterPartyType: Banking_App.CounterPartyType.payee, counterPartyUid: Optional("fed4d40b-9ccc-411d-81c7-870164876d04"), counterPartyName: Banking_App.CounterPartyName.mickeyMouse, counterPartySubEntityUid: Optional("d6d444c0-942f-4f85-b076-d30c2f745a6f"), counterPartySubEntityName: Banking_App.CounterPartySubEntityName.ukAccount, counterPartySubEntityIdentifier: "204514", counterPartySubEntitySubIdentifier: "00000825", reference: Banking_App.Reference.externalPayment, country: Banking_App.Country.gb, spendingCategory: Banking_App.SpendingCategory.payments)

Thanks

Furqan Agwan
  • 43
  • 1
  • 6

3 Answers3

3

Currency codes are defined in the standard ISO 4217 and as part of the standard is the number of decimal digits used for each currency and since swift's NumberFormatter has a style for this ISO standard we can use it for this case.

Create an instance and set the style

let currencyFormatter = NumberFormatter()
currencyFormatter.numberStyle = .currencyISOCode

Set the currency code

currencyFormatter.currencyCode = "SEK"

Now currencyFormatter.minimumFractionDigit and currencyFormatter.maximumFractionDigits will both contain the number of decimals define for the given currency

So now we can put this together in a function for instance

func convertMinorUnits(_ units: Int, currencyCode: String) -> Decimal {
    let currencyFormatter = NumberFormatter()
    currencyFormatter.numberStyle = .currencyISOCode
    currencyFormatter.currencyCode = currencyCode.uppercased()

    return Decimal(units) / pow(10, currencyFormatter.minimumFractionDigits)
}

example

print(convertMinorUnits(551, currencyCode: "GBP")

5.51

Joakim Danielson
  • 43,251
  • 5
  • 22
  • 52
  • While this is a valid solution, if in the case I would like to round up to the nearest pound say 20.64 rounded up should produce 21. I have tried using NSDecimalRounding but have had no luck the result should also be able to calculate the difference in original value and rounded value. If that is possible with arithmetic such as say 21 - 20.64 = 0.36 – Furqan Agwan Feb 12 '20 at 11:11
  • 1
    @FurqanAgwan You said nothing about rounding in your question and extending your question like this is not acceptable, have some respect for us that tries to help you and post a new question instead. – Joakim Danielson Feb 12 '20 at 11:48
2

Currency should never be represented using floating point types like double. The short reason is that floats are base 2 numbers and using them to represent base 10 numbers will lead to rounding errors.

The correct data type for currencies to use is INCurrencyAmount or something like it. It's properties are NSDecimalNumber for the amount and a NSString to represent the currency.

Instead of NSDecimalNumber you can use the Swift type Decimal. It also has common math operators defined like +, *, etc..

orkoden
  • 18,946
  • 4
  • 59
  • 50
  • In Swift it's called `Decimal`. Anyway, when the amount is represented as an integer, it will work too. I am more concerned about not having the currency stored with the amount. – Sulthan Feb 11 '20 at 16:19
0

As the others rightly pointed out, decimal numbers should be used for any numbers representing amounts of money. The website 0.30000000000000004.com nicely explains it.

When it comes to converting the integer values in pennies or cents to values in pounds or dollars, you need to know the multiplicator, i.e. how many fractional currency units are there in the main currency unit (e.g. 1 GBP = 100 pennies and the multiplicator is 100 in this case). For most of the world currencies it's 100, but there are currencies which have 1000 or even don't have any fractional units (see Wikipedia).

You usually get the multiplicator from the API but if it doesn't provide it, you might store a dictionary of known currencies and their multiplicators in your app, but then it becomes your responsibility to keep it up to date.

In order to avoid dividing your amount by the multiplicator, I'd suggest to calculate the exponent and the initialize the decimal number like this:

let amountInCents = 55123
let multiplicator = 100
let exponent = -1 * Int(log10(Double(multiplicator)))
let sign: FloatingPointSign = amountInCents < 0 ? .minus : .plus
let decimalAmount = Decimal(sign: sign, exponent: exponent, significand: Decimal(amountInCents))

Then if you need to format your decimal number in order to display it in the UI, you want to use the number formatter to which you need to pass an appropriate locale:

let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_GB")
formatter.numberStyle = .currency
let formattedAmount = formatter.string(from: NSDecimalNumber(decimal: decimalAmount)) ?? decimalAmount.description
print(formattedAmount) // prints "£551.23"
Vadim Belyaev
  • 2,456
  • 2
  • 18
  • 23
  • This is also valid solution, if in the case I would like to round up to the nearest pound say 20.64 rounded up should produce 21. I have tried using NSDecimalRounding but have had no luck the result should also be able to calculate the difference in original value and rounded value. If that is possible with arithmetic such as say 21 - 20.64 = 0.36 a round up to the nearest pound and round down show be possible. – Furqan Agwan Feb 12 '20 at 11:35
  • If you'd like help in rounding decimal values (which was not mentioned in the question), I'd suggest to search for `NSDecimalRound` on SO and if you can't find the solution there, please submit a new question, I'm sure the community will be happy to help. You can mention a link to your new question here, I'll also try my best to help. – Vadim Belyaev Feb 12 '20 at 14:06