0

Seems to be trivial but I couldn't figure out how to prevent the currency value from Rounding in Swift.

Below is my code:

let halfYearlyPrice = 71.99
var perMonthPrice = (halfYearlyPrice as Double) / 6.0

let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = Locale.init(identifier: "en-US")

if let formattedPrice = currencyFormatter.string(from: perMonthPrice as NSNumber) {
    print("formattedPrice: ", formattedPrice)
    print("\(formattedPrice) / month")
}

The output is

formattedPrice:  $12.00
$12.00 / month

I'm wondering how can I ensure the formattedPrice is 11.99?

Thanks in advance.

Mahendra Liya
  • 12,912
  • 14
  • 88
  • 114
  • The correct tool here is `Decimal` rather than `Double`. See also https://stackoverflow.com/questions/39770303/swift-issue-in-converting-string-to-double/39777334#39777334 https://stackoverflow.com/questions/65943363/round-a-double-float-in-swift-2-digits/65943498#65943498 https://stackoverflow.com/questions/69199798/round-decimal-to-nearest-increment-given-a-number/69199973#69199973 https://stackoverflow.com/questions/33712719/when-is-it-better-to-use-an-nsdecimal-nsdecimalnumber-instead-of-a-double/33712818#33712818 – Rob Napier Jan 18 '23 at 20:55
  • 4
    Rounding to the nearest 2 decimal values gives 12 so you need to round down, `currencyFormatter.roundingMode = .down` – Joakim Danielson Jan 18 '23 at 20:59
  • 1
    I disagree with the decision to close this as a duplicate of the "is floating point math broken" thread. That thread alludes to the root cause of these sorts of problems, but for doing math with money, the `Decimal` type is the correct tool (as Rob says in his comment.) There might be an answer in the duplicate question that mentions `Decimal`, but that should be the CENTRAL part of the answer to this question. – Duncan C Jan 18 '23 at 21:07

1 Answers1

2

While you should use Decimal to more accurately represent base-10 values, using a double isn't the root cause of your problem here.

The default roundingMode of a NumberFormatter is .halfEven, which is going to round 11.998 up to 12.00. In fact any value >= 11.995 will end up as 12.00.

You need to set the rounding mode to .down.

let halfYearlyPrice = Decimal(71.99)
var perMonthPrice =  halfYearlyPrice / 6

let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = Locale.init(identifier: "en-US")
currencyFormatter.roundingMode = .down

if let formattedPrice = currencyFormatter.string(from: perMonthPrice as NSNumber) {
    print("formattedPrice: ", formattedPrice)
    print("\(formattedPrice) / month")
}

Output:

formattedPrice: $11.99

$11.99 / month

You will get this result even if you don't use Decimal, but rounding errors can accrue if you perform numerous currency operations using floating point values rather than Decimal.

Paulw11
  • 108,386
  • 14
  • 159
  • 186