Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

generatesDecimalNumbers for NumberFormatter does not work

My function is converting a string to Decimal

func getDecimalFromString(_ strValue: String) -> NSDecimalNumber {
    let formatter = NumberFormatter()
    formatter.maximumFractionDigits = 1
    formatter.generatesDecimalNumbers = true
    return formatter.number(from: strValue) as? NSDecimalNumber ?? 0
}

But it is not working as per expectation. Sometimes it's returning like

Optional(8.300000000000001)
Optional(8.199999999999999)

instead of 8.3 or 8.2. In the string, I have value like "8.3" or "8.2" but the converted decimal is not as per my requirements. Any suggestion where I made mistake?

like image 320
Ashwin Kanjariya Avatar asked Apr 21 '17 09:04

Ashwin Kanjariya


1 Answers

Setting generatesDecimalNumbers to true does not work as one might expect. The returned value is an instance of NSDecimalNumber (which can represent the value 8.3 exactly), but apparently the formatter converts the string to a binary floating number first (and that can not represent 8.3 exactly). Therefore the returned decimal value is only approximately correct.

That has also been reported as a bug:

  • NSDecimalNumbers from NSNumberFormatter are affected by binary approximation error

Note also that (contrary to the documentation), the maximumFractionDigits property has no effect when parsing a string into a number.

There is a simple solution: Use

NSDecimalNumber(string: strValue) // or
NSDecimalNumber(string: strValue, locale: Locale.current)

instead, depending on whether the string is localized or not.

Or with the Swift 3 Decimal type:

Decimal(string: strValue) // or
Decimal(string: strValue, locale: .current)

Example:

if let d = Decimal(string: "8.2") {
    print(d) // 8.2
}
like image 52
Martin R Avatar answered Sep 26 '22 23:09

Martin R