I found that for some strings the NumberFormatter produces incorrect results, as in the example below.
let input = "579.988"
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 3
formatter.numberStyle = .decimal
let output = formatter.number(from: input)?.decimalValue
Result: 579.9880000000001
The error also appears for other strings. For example, "589.988". The error can be reproduced in the playground and an application for Swift versions 5.2, 4.2 at least
The NumberFormatter property 'doubleValue' produces correct results (at least for strings that I tested) but the problem accure again if I try to convert Double to Decimal (with 024 on the end).
Do you have ideas what can be an approach in the situation?
1 Like
ksluder
(Kyle Sluder)
2
There’s nothing wrong here. You’re encountering a fundamental property of floating point numbers: they can’t precisely represent all decimals. formatter.number(from: "579.988") is a lossy operation; the NSNumber object that is returned wraps the closest-representable floating point number.
Nevin
3
Right, but the formatter still has maximumFractionDigits set to 3. It’s a bit surprising that it doesn’t respect that when producing a decimal value.
ksluder
(Kyle Sluder)
4
The formatter isn’t producing the decimal value. @adamovitch is calling the NSNumber.decimalValue getter:
let output = formatter.number(from: input)?.decimalValue
1 Like
Nevin
5
Good point. Presumably one “solution” then would be to set generatesDecimalNumbers on the formatter?
ksluder
(Kyle Sluder)
6
Nope, the floating-point inaccuracy happens prior to the conversion to NSDecimalNumber:
1> import Foundation
2> let fmt = NumberFormatter()
3> fmt.generatesDecimalNumbers = true
4> fmt.number(from: "579.988")!
$R0: NSDecimalNumber = 5799880000000001 x 10^-13
5> $R0.description(withLocale: [NSLocale.Key.decimalSeparator : "."])
$R1: String = "579.9880000000001"
Nevin
7
Well that is really surprising.
tera
8
Why not just:
Decimal(string: "579.988") // 579.988
jrose
(Jordan Rose)
9
Isn’t locale-sensitive, you want “123,456” to parse as a fraction in certain locales.
ksluder
(Kyle Sluder)
10
2 Likes
tera
11
This works fine:
Decimal(string: "579,988", locale: .current) // current locale set to fr_FR
Interestingly passing nil locale (same as not passing this parameter at all) doesn't work correctly indeed.
2 Likes