Shawn Baek
Shawn Baek

Reputation: 1957

NumberFormatter return the wrong number

I test the NumberFormatter to get the number from priceWithCurrency.

If price bigger than $70 NumberFormatter converted the wrong number.

lazy var currencyFormatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.currencyCode = "USD"
        return formatter
}()


let price = "$71.9"

currencyFormatter.number(price)
//71.90000000000001

Upvotes: 0

Views: 385

Answers (1)

Moose
Moose

Reputation: 2737

If you do the same thing with 71.8, it will work. This is just because 71.9 can't be represented precisely in floating point numbers ( due to the finitude of bits )

Use integers ( price in cents ) or decimal numbers is the only issue to deal correctly with prices. Check the Decimal and NSDecimalNumber classes to see what you can do with this.

It is specially recommended if you do some computations on prices, ( If you pay 10$ with two friends in cash, two will pay 3.33, and one will pay 3.34 - so depending of your context, divide by 3 might not be enough)

let number = currencyFormatter.number(from: price) ?? NSNumber(value: 0)
var value =  number.decimalValue
var roundedValue: Decimal = 0

// Right way: ( choose your precisions - 3 decimals here):
NSDecimalRound(&roundedValue, &value, 3, .bankers)

print(number)
print(value)
print(roundedValue)
71.90000000000001
71.90000000000001
71.9

If you just need to log your prices, simply use Int(price*100)/100 It will do the job

If you need more... Good luck :)

Edit

Following the excellent remark of @Nicholas Rees, I add this variation:

currencyFormatter.generatesDecimalNumbers = true

let number = (currencyFormatter.number(from: price) as? NSDecimalNumber) ?? NSDecimalNumber(value: 0)
var value =  number.decimalValue
var roundedValue: Decimal = 0

// Right way: ( choose your precisions - 3 decimals here):
NSDecimalRound(&roundedValue, &value, 3, .bankers)

print(number)
print(value)
print(roundedValue)

There, the result in the same when logged, but I suppose the internal format of the 'value' is correct

Another approach is to remove currency and create decimal from string:

print(Decimal(string: "71.9") ?? 0)

71.9

Upvotes: 3

Related Questions