Using Currency in Swift - ios

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

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

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..

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"

Related

Converting a value to double always returns sceintific format in Swift

I am getting some coordinates from server in string array. And I am trying to save those coordinates in SQLite Database by splitting and converting them to double value. But some coordinates are getting saved in scientific notations. For example I am getting the following coordinate from server:
"-0.0000558,51.3368066"
I am splitting the string and converting it to double resulting in the following values:
[-5.58e-05,51.3368066]
I have tried following solutions but still returning same result:
1.
Double(latLongArr[0])
extension String{
var doubleValue: Double? {
return NumberFormatter().number(from: self)?.doubleValue
}
}
extension String{
var doubleValue: Double? {
let numberFormatter = NumberFormatter()
numberFormatter.allowsFloats = true
numberFormatter.maximumFractionDigits = 10
numberFormatter.numberStyle = .decimal
return numberFormatter.number(from: "\(self)")!.doubleValue
}
}
I have used the above code but it still returns in scientific format but I need it in normal decimal format. So what is the issue?
The last option is the option I would go for and I believe it works right.
I believe your issue is only when you print to console:
As you can see, the double variable is actually converted properly but just when it is formatted to print to the console it shows it as a scientific notation string.
Your other option besides using doubleValue is to use decimalValue
I suggest putting a breakpoint and checking the actual value of your double than reviewing it from the console output which is a formatted string.
Just for reference, code used in the image above:
let number = "-0.0000558"
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.maximumFractionDigits = 10
let finalNumber = numberFormatter.number(from: number)
let decimalNumber = finalNumber!.decimalValue
let doubleNumber = finalNumber!.doubleValue
print(decimalNumber)
print(doubleNumber)
If you want to print your Doubles without scientific notation use
String(format: "%.7f", value).
Example:
let value = Double(3.141592)
print(String(format: "%.7", value)
will print 3.1415920.
I have used the below extension to represent scientific values in the decimal format.
extension String {
func getDecimalValue() -> Decimal{
return NSNumber(value: Double(self)!).decimalValue
}
}
Usage:
let numberString = "+5.58e-05"
print(numberString.getDecimalValue()) //0.0000558

NSNumberFormatter numberFromString is adding a small fraction when converting certain numbers

I'm seeing some strange bugs in my iPhone app that I have narrowed down to my use of NSNumberFormatter.
A stripped down example...
In Xcode playground I have:
let numberFormatter = NSNumberFormatter()
//numberFormatter.numberStyle = .DecimalStyle - does not change behavior
let numberString = "546000.06"
let number: NSNumber = numberFormatter.numberFromString(numberString)!
print("number: \(number)")
let number1: NSNumber = NSDecimalNumber(string: numberString)
print("number1: \(number1)")
This is the output:
number: 546000.0600000001
number1: 546000.06
Note that setting the numberStyle to .DecimalStyle doesn't change anything.
This issue only happens for certain numeric values (for example, 8.03 is another one). I thought NSNumberFormatter was safe for this type of conversion and I haven't seen much noise about this issue on the internet so I want to assume it is something I am doing wrong.
Can anyone explain what I am seeing? Any help is very much appreciated!
It looks like there is an issue with NSNumberFormatter. There are certain values where this rounding error creeps up. In the XCode 7.2.1 playground, it shows up around 8.03.
One way that I've solved this is to round the decimal number. Since the difference is +/- a tiny amount, rounding to 4 fraction places should work. You can use various rounding modes. In this example I used RoundPlain.
var initialValue = NSDecimalNumber(string: "7")
let handler = NSDecimalNumberHandler(roundingMode: NSRoundingMode.RoundPlain, scale: 4, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false)
let numberFormatter = NSNumberFormatter()
for index in 1...300 {
initialValue = initialValue.decimalNumberByAdding(0.01)
let stringValue = "\(initialValue)"
var number = numberFormatter.numberFromString(stringValue)
var decimalNumber: NSDecimalNumber = NSDecimalNumber(decimal: number!.decimalValue)
decimalNumber = decimalNumber.decimalNumberByRoundingAccordingToBehavior(handler)
print("stringValue = \(stringValue), decimalNumber = \(decimalNumber), number = \(number!)")
}
You are seeing a rounding error.
If you would like to display a number to the user with two fraction digits, set maximumFractionDigits:
let numberFormatter = NSNumberFormatter()
numberFormatter.maximumFractionDigits = 2
let firstNumber = NSNumber(float:546000.06)
let firstNumberString = numberFormatter.stringFromNumber(firstNumber)!
let secondNumber = NSNumber(float:5.1337)
let secondNumberString = numberFormatter.stringFromNumber(secondNumber)!
print("firstNumber: \(firstNumberString)")
print("secondNumber: \(secondNumberString)")
The output will be:
"firstNumber: 546000.06\n"
"secondNumber: 5.13\n"
If you try to parse a number from a string, then you are all set.
let thirdNumber = numberFormatter.numberFromString("546000.06")!
print("thirdNumber \(thirdNumber.className): \(thirdNumber)")
The last line prints the description of the object itself (w/ rounding error):
"thirdNumber __NSCFNumber: 546000.0600000001\n"
Update (2016-03-18)
You want to parse the following string:
let currencyString = "$546,000.06"
First, we create a formatter with a locale and .CurrencyStyle. Be aware that the locale depends on the string you try to parse and not on the system you are running on.
let currencyFormatter = NSNumberFormatter.init()
currencyFormatter.locale = NSLocale.init(localeIdentifier: "en_US")
currencyFormatter.numberStyle = .CurrencyStyle
We can now use the formatter to prase the string.
let currencyNumber = currencyFormatter.numberFromString(currencyString)!
You are now free to store this object, for example in CoreData. However, if you wan't to present the value to the user (or print it on the console), you have to use a NSNumberFormatter. If you print the object directly, the description (or debugDescription) of the object is used.
So, let's create another formatter to print the value of your NSNumber object:
let outputFormatter = NSNumberFormatter.init()
outputFormatter.maximumFractionDigits = 10
Using many fraction digits should print any possible rounding errors. But
print("Number: \(outputFormatter.stringFromNumber(currencyNumber)!)")
outputs the desired result:
"Number: 546000.06\n"
What you have seen is the result of the internal representation of an NSNumber object (with description/debugDescription).

Swift: String from float without rounding the values

It is recommended to round the decimals but i am facing an scenario where i just need to cut down the precision.
Output: 15.96 to 16.0
Desired output: 15.96 to 15.9
Codes:
var value: AnyObject = dict.valueForKey("XXX")!
var stringVal = NSString(format:"%.1f", value.floatValue)
I thought this will be simple but found tricky. Your thoughts on this is highly appreciated.
Use a NSNumberFormatter and configure its rounding mode accordingly:
let formatter = NSNumberFormatter()
formatter.maximumFractionDigits = 1
formatter.roundingMode = .RoundDown
let s = formatter.stringFromNumber(15.96)
// Result: s = "15.9"
If you need to use the rounded number in future math operations, you can use the following function:
func roundToPlaces(_ value: Double, places: Int, rule: FloatingPointRoundingRule = .toNearestOrAwayFromZero) -> Double {
let divisor = pow(10.0, Double(places))
return (value * divisor).rounded(rule) / divisor
}
You can then call it with
var value: AnyObject = dict.valueForKey("XXX")!
var rounded = roundToPlaces(value.doubleValue, places: 1, rule: .down)
var stringVal = "\(rounded)"
What this actually did was the following:
15.96 * 10.0 = 159.6
floor(159.6) = 159.0
159.0 / 10.0 = 15.9
Caveat: This won't help in situations where you're using scientific precision, i.e.
1.49850e0 --> 1.4e0 // (5 places --> 1 place)
1.39e10 --> 1.3e10 // (3 places --> 1 place)
It will treat all numbers as e0
[update 2018-08-09]
Since it seems like my answer is getting some views, I would like to point out that rounding floating-point numbers by division can introduce errors because of how floating-point numbers are stored in memory. as user #mattt has pointed out elsewhere:
floor(1.5679999 * 1000) / 1000 == 1.5669999999999999
(if you want to get your math on, this paper is a great primer on numbers and computers)
If you need that level of precision, use fixed-point numbers instead. Swift provides the Decimal type for this purpose.
The important thing is to understand your problem. If you're working with money or sensor data, you probably want Decimals. If you're working with computer graphics, you can go with Floats.
Try using this:
var test : AnyObject = "15.96"
var rounded_down = floorf(test.floatValue * 10) / 10;
print(rounded_down)
Here's an updated answer in Swift 5 based on #Clafou 's answer. You can use it as an extension to any data type. Example
extension Double {
func cutToDecimalPlace(_ decimalPlaces: Int) -> String{
let formatter = NumberFormatter()
formatter.maximumFractionDigits = decimalPlaces
formatter.roundingMode = .down
return formatter.string(from: NSNumber(value: self)) ?? ""
}
}
And you can call it like this
let priceValueString = "24.124"
let updatedPriceValue = priceValueString.doubleValue.cutToDecimalPlace(1)
Output will be
24.1

Which Swift datatype do I use for currency

I have an iOS application that will be performing a lot of basic arithmetic on numbers representing USD currency (eg 25.00 representing $25.00).
I have gotten into a lot of trouble using the datatype Double in other languages like Java and Javascript so I would like to know the best datatype to use for currency in Swift.
Use Decimal, and make sure you initialize it properly!
CORRECT
// Initialising a Decimal from a Double:
let monetaryAmountAsDouble = 32.111
let decimal: Decimal = NSNumber(floatLiteral: 32.111).decimalValue
print(decimal) // 32.111 😀
let result = decimal / 2
print(result) // 16.0555 😀
// Initialising a Decimal from a String:
let monetaryAmountAsString = "32,111.01"
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.numberStyle = .decimal
if let number = formatter.number(from: monetaryAmountAsString) {
let decimal = number.decimalValue
print(decimal) // 32111.01 😀
let result = decimal / 2.1
print(result) // 15290.9571428571428571428571428571428571 😀
}
INCORRECT
let monetaryAmountAsDouble = 32.111
let decimal = Decimal(monetaryAmountAsDouble)
print(decimal) // 32.11099999999999488 😟
let monetaryAmountAsString = "32,111.01"
if let decimal = Decimal(string: monetaryAmountAsString, locale: Locale(identifier: "en_US")) {
print(decimal) // 32 😟
}
Performing arithmetic operations on Doubles or Floats representing currency amounts will produce inaccurate results. This is because the Double and Float types cannot accurately represent most decimal numbers. More information here.
Bottom line:
Perform arithmetic operations on currency amounts using Decimals or Int
I suggest you start with a typealias for Decimal. Example:
typealias Dollars = Decimal
let a = Dollars(123456)
let b = Dollars(1000)
let c = a / b
print(c)
Output:
123.456
If you are trying to parse a monetary value from a string, use a NumberFormatter and set its generatesDecimalNumbers property to true.
There's a really nice lib called Money:
let money: Money = 100
let moreMoney = money + 50 //150
There a lot of nice features besides that, such as type-safe currencies:
let euros: EUR = 100
let dollars: USD = 1500
euros + dollars //Error
Binary operator '+' cannot be applied to operands of type 'EUR' (aka '_Money') and 'USD' (aka '_Money')
The Flight-School/Money is probably the best choice for a project full of monies.
In the ending of README #mattt provides a nice solution for a simple Money type:
struct Money {
enum Currency: String {
case USD, EUR, GBP, CNY // supported currencies here
}
var amount: Decimal
var currency: Currency
}
Chapter 3 in 'Flight School Guide to Swift Numbers' is provides excellent intro into the topic.
We use this approach with Currency being a huge enum that has been generated in advance (don't forget a monetary value is just a number without its currency - they both belong together):
struct Money: Hashable, Equatable {
let value: Decimal
let currency: Currency
}
extension Money {
var string: String {
CurrencyFormatter.shared.string(from: self)
}
}
You need the Decimal and its initializer:
Decimal(string: "12345.67890", locale: Locale(identifier: "en-US"))
When considering the other answers, please note that Float and Double literals affect the accuracy of the Decimal result. The same applies to the NumberFormatter.
Decimal(5.01) // 5.009999999999998976 ❌
Decimal(floatLiteral: 5.01) // 5.009999999999998976 ❌
NSNumber(floatLiteral: 0.9999999).decimalValue // 0.9999999000000001 ❌
let decimalFormatter = NumberFormatter()
decimalFormatter.numberStyle = .decimal
decimalFormatter.number(from: "0.9999999")?.decimalValue // 0.9999999000000001 ❌
Decimal(string: "5.01", locale: Locale(identifier: "en-US")) // 5.01 ✅
Decimal(string: "0.9999999", locale: Locale(identifier: "en-US")) // 0.9999999 ✅

Using Swift how to convert a string to a number

I am getting values back from a web service that gives me back prices in a string format, this is put into a Dictionary, so I get prices back as "1.5000" for example, which is obviously 1.50 in currency. However for the life of me I cannot get anything to work in Swift to format this correctly. In most other languages you can do this in a couple of seconds, so I'm getting a bit frustrated with something that is so simple.
Here's my test code:
var testnumber = "1.5000"
let n = NSNumberFormatter()
n.numberStyle = NSNumberFormatterStyle.DecimalStyle
n.maximumFractionDigits = 2
n.minimumFractionDigits = 2
let returnNumber = n.numberFromString(testnumber)
println("Returned number is \(returnNumber)")
This prints out in debug "number is Optional(1.5)" not 1.50!
I have changed NSNumberFormatterStyle.DecimalStyle to NSNumberFormatterStyle.CurrencyStyle as I thought that may do it for me as the returned number is a currency anyway, but that gives me back in debug "Returned number is nil" - which is even more confusing to me!
I have tried using maximumIntegerDigits and minimumIntegerDigits, setting locales using n.locale = NSLocale.currentLocale(), setting formatWidth, setting paddingPosition and paddingCharacter but nothing helps, I either get nil back to 1.5.
All I ultimately need to do is convert a string to a float or a currency value, and ensure there are 2 decimal places, and I can't believe it's this hard to accomplish!
Any help would be very gratefully received.
You are printing a number not a string
Xcode 11.4 • Swift 5.2 or later
extension Formatter {
static let usCurrency: NumberFormatter = {
let formatter = NumberFormatter()
formatter.locale = .init(identifier: "en_US")
formatter.numberStyle = .currency
return formatter
}()
}
extension String {
var double: Double? { Double(self) }
var usCurrencyFormatted: String {
Formatter.usCurrency.string(for: double) ?? Formatter.usCurrency.string(for: 0) ?? ""
}
}
"1.1222".usCurrencyFormatted // "$1.12"
"2".usCurrencyFormatted // "$2.00"
The problem is about numberFromString returning an optional - so you have to unwrap before printing. Just to be safe, you can use optional binding:
if let returnNumber = n.numberFromString(testnumber) {
println("Returned number is \(returnNumber)")
}
otherwise if it's ok for the app to crash if the optional is nil (in some cases this is a wanted behavior if the optional is expected to always contain a non nil value) just use forced unwrapping:
let returnNumber = n.numberFromString(testnumber)!
println("Returned number is \(returnNumber)")
That fixes the unwanted "Optional(xx)" text. As for formatting a float/double number, there are probably several ways of doing it - the one I would use is c-like string formatting, available via NSString:
let formattedNumber = NSString(format: "%.2f", returnNumber)
println("Returned number is \(formattedNumber)")
Use String Format Specifiers as reference if you want to know more about format specifiers.
You could probably just use the NSNumberFormatter that you just created.
let returnNumber = n.stringFromNumber(n.numberFromString(testnumber))
returnNumber will now be of type String.
The following returns to 2 decimal places for me in playgrounds. May be of some help to you. Uses NSNumberFormatter and then unwraps the optional
let testnumber: String = "1.50000"
let numberFormatter = NSNumberFormatter()
let number = numberFormatter.numberFromString(testnumber)
if let final = number?.floatValue {
println("Returned number is " + String(format: "%.2f", final))
}

Resources