NumberFormatter gives incorrect values with lots of extra zeros

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

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.

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.

The formatter isn’t producing the decimal value. @adamovitch is calling the NSNumber.decimalValue getter:

let output = formatter.number(from: input)?.decimalValue
1 Like

Good point. Presumably one “solution” then would be to set generatesDecimalNumbers on the formatter?

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"

Well that is really surprising.

Why not just:

Decimal(string: "579.988") // 579.988

Isn’t locale-sensitive, you want “123,456” to parse as a fraction in certain locales.

There’s a Decimal initializer that takes a Locale.

2 Likes

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

Thanks