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).
Related
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
This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 2 years ago.
I've developed a custom control for money input, which contains UITextField and UILabel. When the user taps on it, it becomes active and switches to the UITextField for data input and accepts only numbers and dot symbol, when the user finishes editing it becomes passive and switches to UILabel just to show formatted money value. But there is one little issue which I'm unable to fix a lot of days already.
Let's say the user writes down 88.99 and presses done, this becomes "$ 88.99" in a UILabel, next when the user again taps on it to edit the initial value I get the following value "88.98999999999999". To not present the entire code I selected the core part in a playground format which gives the same result as in my complete project:
extension NumberFormatter {
static public func defaultCurrencyFormatter() -> NumberFormatter {
let formatter = NumberFormatter()
formatter.usesGroupingSeparator = true
formatter.numberStyle = .currency
formatter.currencySymbol = ""
formatter.minimumFractionDigits = 1
formatter.maximumFractionDigits = 2
formatter.currencyGroupingSeparator = ","
formatter.currencyDecimalSeparator = "."
return formatter
}
}
let stringValue = NumberFormatter.defaultCurrencyFormatter().number(from: "88.99")?.stringValue
print(stringValue) // result is Optional("88.98999999999999")
I have no idea why using this NumberFormatter I get such a result. I was thinking that explicitly setting minimumFractionDigits and maximumFractionDigits will solve my issue but it does not affect my result
NumberFormatter is legacy from objc and it operates with NSNumber/CGFloat etc. and usually it is helpful for localized text formatting. Much powerful and convenient parser for numbers is Scanner but if you don't have complex data structure to parse and don't want to deal with Floating-point error mitigation just use swift's Float:
// Float from string
if let float = Float("88.99") {
print(float)
// String from float
let text = String(float)
print(text)
}
Prints:
88.99
88.99
Try this:
extension String {
var currencyStyle: String? {
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 1
formatter.maximumFractionDigits = 2
formatter.usesGroupingSeparator = true
formatter.groupingSize = 3
formatter.currencyGroupingSeparator = ","
formatter.currencyDecimalSeparator = "."
if let double = Double(self) {
let number = NSNumber(value: double)
return formatter.string(from: number)
}
return nil
}
}
to use it:
let str = "12388.98999999999999".currencyStyle
print(str) // Optional("12,388.99")
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 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
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))
}