I'm trying to format a number as a currency.
let formatter = NSNumberFormatter()
formatter.locale = NSLocale(localeIdentifier: "en_GB")
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle
let limit = formatter.numberFromString("12.99") as? NSDecimalNumber
However, the constant limit is returned a nil. The weird thing is that this piece of code was working up until recently but not sure what may have changed. Does the example look okay or am I missing something?
EDIT
To give context, I am trying to parse a string number (from an input element) to a decimal number for storing as currency value. My error appears setting the number style, I was using .CurrencyStyle but should use .DecimalStyle.
As a first test I tried
println(formatter.stringFromNumber(12.99)) // prints "£12.99"
Then I tried
let limit = formatter.numberFromString("£12.99") as? NSDecimalNumber // gives nil
But
let limit = formatter.numberFromString("£12.99") // gives 12.99
Whereas
let limit = formatter.numberFromString("12.99") // gives nil
So the currency symbol is mandatory
Related
I have an app that this was all working correctly before iOS 13. I've checked a few posts but they seem to be saying what I have already done.
I'm passing a string that has currency symbols and formatting, and I want to strip that and use the string value.
func changeCurrencyToDecimal(stringNumber:String) -> Decimal {
let numberFormatter = NumberFormatter()
// Pull apart the components of the user's locale
var locComps = Locale.components(fromIdentifier: Locale.current.identifier)
// Set the specific currency code
locComps[NSLocale.Key.currencyCode.rawValue] = options?.currencyCode // or any other specific currency code
// Get the updated locale identifier
let locId = Locale.identifier(fromComponents: locComps)
// Get the new custom locale
//numberFormatter.locale = Locale(identifier: locId)
if(options?.currencyCode == nil) {
print("There is no currency code so use the default locale")
numberFormatter.locale = Locale.current
}
else{
print("We have a currency code!")
numberFormatter.locale = Locale(identifier: locId)
}
numberFormatter.numberStyle = .currency
numberFormatter.currencySymbol = ""
numberFormatter.decimalSeparator = ","
print("\(stringNumber) is the number being passed")
let number = numberFormatter.number(from: stringNumber)
// check to see if the number is nil and if so, return 0.
print("\(number) is the number converted")
if number == nil{
return 0
}
else{
print("\(number!) is the number")
let amount = number?.decimalValue
return amount!
}
}
An example of a string that I am passing: $300.00 the $ always triggers a return of nil. If I pass 300.00, then the converter works just fine. But I need to be able to check for any currency the user has set for their device.
The currency codes that I am checking are the ones Apple supplies in the var currencyCode: String? { get } options is just where I am storing those codes.
The numberFormatter produces nil every time, and it seems because my decimal is not being stripped of its formatting.
Again this worked before iOS 13, so I am guessing something changed on Apple's side and I just might not have come acrossed it yet.
UPDATE
Here is a user seniaro. The user enters an amount. If the user hits save right away, I take the amount and convert it into a decimal to save in coredata. But if the user dismisses the keyboard, I take that amount and convert it into a string to display the currency symbol. I allow the user to set their currency, so using the locale doesn't work for me, as sometimes they are not using that system. So after the keyboard is dismissed and their amount is displayed with the correct formatting, I have it programmed that if they would happen to change their amount and once again dismiss the keyboard, and the same steps are repeated. The currency symbols are stripped and the string is converted into a decimal.
I hope this better gives you an idea of how this works in my app.
UPDATE
I've added an if/else statement to see if the locale has been set or if it comes back nil, and if so to set it to the Locale.current
Assuming the input string is using the device's current locale you can create a NumberFormatter with Locale.current and set the numberStyle to currency.
I used a small playground to test it:
import Foundation
func changeCurrencyToDecimal(_ stringNumber: String) -> Decimal? {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .currency
let number = numberFormatter.number(from: stringNumber)
return number?.decimalValue
}
let numbersFormatted = ["$300", "$3,000.04", "3.000,04", "Wazaa", "3000000"]
numbersFormatted
.map { changeCurrencyToDecimal($0) }
.forEach { print($0 ?? "Not a value") }
This prints:
300
3000.04
Not a value
Not a value
3000000
If you keep getting nil as a result your device's locale is different as the sample string number you are using in the question ($300).
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).
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))
}
When we convert like String.Format("{0:C}", 126.45) it returns $126.45
but if we convert like String.Format("{0:C}", -126.45) it returns ($126.45)
Why negative conversion return braces?
What to do if we don't want this braces?
Why don't you try something like:
String.Format("{0:$#,##0.00}", -126.45)
According to the documentation here a format of "{0:C1}" or "{0:C2}" should work, but for some strange reason it is not..
Another approach could be setting the CultureInfo:
CultureInfo culture = (CultureInfo)CultureInfo.CurrentCulture.Clone();
culture.NumberFormat.CurrencyNegativePattern = 1;
string s = string.Format(culture, "{0:c}", -126.45);
Reference here
Swift 5 Here is best solution if you get after formate this kind of value (-300)
extension Double {
static let twoFractionDigits: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
formatter.currencySymbol = "$"
formatter.currencyCode = "USD"
formatter.numberStyle = .currency
formatter.usesGroupingSeparator = true
return formatter
}()
var formatted: String {
return Double.twoFractionDigits.string(for: self) ?? ""
}
}
In the en-US locale, parentheses are used to denote negative values. Visually scanning a long column of numbers, many people find it easier to see the negatives. Often it's the negative values that are of most concern (oops! my checking account balance is overdrawn).
To get different formatting behavior, you can change your locale, or you can change your format specifier to something like F.
It does parentheses, because that's the standard on whatever CultureInfo you are using.
Never done it myself but the make up of the format is controlled by the current NumberFormatInfo instance.
If you figure it out, answer your own question, and I'll plus you