I was able to write this function as an extension method of NSDecimalNumber which worked out pretty well for me. It takes some number and formats it to the currency string. If it contains .00, it will truncate it, but if it's something like .23 or .45, it will retain the decimals.
func toCurrencyString() -> String
{
let nf = NSNumberFormatter()
nf.numberStyle = NSNumberFormatterStyle.CurrencyStyle
//Determine whether to show decimals or not (if trailing zeros exist, do not show)
if (trunc(self.floatValue) == self.floatValue) {
nf.maximumFractionDigits = 0
} else {
nf.maximumFractionDigits = 2
}
return nf.stringFromNumber(self)
}
I had a question:
How could implement a similar function above that returns a formatted string, but rounds it up? So something like 45.55 would be 46 (no decimals)
I tried using round() which works in playground, but when I use it extension methods, it says 'ambiguous use of round()'.
also..out of curiosity, is the above safe for different locales like it shows up for me perfectly in America, but if the user's phone was in the UK would it automatically work?
Thanks so much!
Took me a little bit but I got it:
func toRoundedCurrencyString() -> String
{
let nf = NSNumberFormatter()
nf.numberStyle = NSNumberFormatterStyle.CurrencyStyle
nf.maximumFractionDigits = 0
var roundingStyle = NSDecimalNumberHandler(roundingMode: NSRoundingMode.RoundBankers, scale: 0, raiseOnExactness: false, raiseOnOverflow: false, raiseOnUnderflow: false, raiseOnDivideByZero: false)
var roundedNumber = self.decimalNumberByRoundingAccordingToBehavior(roundingStyle)
return nf.stringFromNumber(roundedNumber)
}
Works well, but concerned with others said. I'm using NSDecimalNumber extension methods. Isn't NSDecimalNumber the preferred way to handle currency? btw i'm using currencies < 100,000 if that matters? Is it only larger currencies where it's a big deal? I'm always using up to 2 decimal places, if even.
Related
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"
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).
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).
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
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))
}