2

I am using NSDecimalNumber to format currency and want the following inputs and outputs:

9.99 --> 9.99 10 --> 10 10.00 --> 10 9.90 --> 9.90 9.9 --> 9.90 0 --> 0 0.01 --> 0.01 20 --> 20 10.01 --> 10.01

How can I do this in Swift.

EDIT: Essentially if there are cents (i.e. cents > 0) then display the cents. Otherwise, don't.

mergesort
  • 5,087
  • 13
  • 38
  • 63
  • 1
    We need more information. What if the input is 0? What if the input is 0.01? What if the input is 10.01? What if the input is 20? What rule are you following to decide how many digits to show to the right of the decimal point? – rob mayoff May 16 '15 at 04:00
  • 1
    I think you missed the currency part. It would be fairly simple to infer what those inputs and outputs would be. I'll add more examples though – mergesort May 16 '15 at 04:35

6 Answers6

6

Your rule is "Display two fractional digits if either is non-zero; otherwise, display no fractional digits and no decimal point”. I would do it in the most straightforward way:

let number = NSDecimalNumber(string: "12345.00")
let formatter = NSNumberFormatter()
formatter.positiveFormat = "0.00"
let formattedString = formatter.stringFromNumber(number)!
    .stringByReplacingOccurrencesOfString(".00", withString: "")
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Being picky here but some countries use periods to group three digits. 1,015,000 -> 1.015.000 – Tobias May 16 '15 at 15:11
  • And some currencies don't use two digits to the right of the radix point. The question is fairly locale-specific. – rob mayoff May 16 '15 at 17:30
  • That's true. I was being picky. I do like how simple your answer is compared to others. – Tobias May 16 '15 at 19:54
3

You can use NSNumberFormatter's currency formatting for this. However, there doesn't seem to be a built-in way to do rounding the way you want. Here's a workaround:

let formatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.CurrencyStyle

func numToCurrency (num: Double) -> String {
    if floor(num) == num {
        formatter.minimumFractionDigits = 0
        formatter.maximumFractionDigits = 0
    }
    else {
        formatter.minimumFractionDigits = 2
        formatter.maximumFractionDigits = 2
    }
    return formatter.stringFromNumber(num)!
}

numToCurrency(9)    // "$9"
numToCurrency(9.9)  // "$9.90"

Check the NSNumberFormatter class reference for further configuration options (you might need to set a locale for this formatter to automatically use the correct international currency sign for the current user).

Greg
  • 9,068
  • 6
  • 49
  • 91
2

(Answering here, as a closed question was re-directed to this one...)

Perhaps the most straightforward route, particularly since this is tagged "Swift", is to determine if it's a whole number or not:

if value.truncatingRemainder(dividingBy: 1) == 0 {
    // it's a whole number,
    // so format WITHOUT decimal places, e.g. $12
} else {
    // it's a fraction,
    // so format WITH decimal places, e.g. $12.25
}

the added benefit is avoiding issues with locales and currency formats... no search/replace of ".00" when you're in Germany, for example, where the format is ",00"

DonMag
  • 69,424
  • 5
  • 50
  • 86
1

edit/update: Xcode 8.3 • Swift 3.1

extension Formatter {
    static let noFractionDigits: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.minimumFractionDigits = 0
        formatter.maximumFractionDigits = 0
        formatter.minimumIntegerDigits = 1
        return formatter
    }()
    static let twoFractionDigits: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.minimumFractionDigits = 2
        formatter.maximumFractionDigits = 2
        formatter.minimumIntegerDigits = 1
        return formatter
    }()
}

extension FloatingPoint {
    var customDescription: String {
        return rounded(.down) == self ?
        Formatter.noFractionDigits.string(for: self) ?? "" :
        Formatter.twoFractionDigits.string(for: self) ?? ""
    }
}

extension String {
    var double: Double { return Double(self) ?? 0 }
}

let array = ["9.99","10","10.00","9.90","9.9"]
let results = array.map { $0.double.customDescription }
results    // ["9.99", "10", "10", "9.90", "9.90"]
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
0

Here's how to create a custom formatter class to handle this for you:

import Foundation

class CustomFormatter: NSNumberFormatter {
  required init(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
  }

  override init() {
    super.init()
    self.locale = NSLocale.currentLocale()
    self.numberStyle = .DecimalStyle
  }

  func isIntegerNumber(number:NSNumber) -> Bool {
    var value: NSDecimal = number.decimalValue
    if NSDecimalIsNotANumber(&value) { return false }
    var rounded = NSDecimal()
    NSDecimalRound(&rounded, &value, 0, NSRoundingMode.RoundPlain)
    return NSDecimalCompare(&rounded, &value) == NSComparisonResult.OrderedSame
  }

  override func stringFromNumber(number: NSNumber) -> String? {
    if isIntegerNumber(number)  {
      self.minimumFractionDigits = 0
      self.maximumFractionDigits = 0
      return super.stringFromNumber(number)
    }
    else {
      self.minimumFractionDigits = 2
      self.maximumFractionDigits = 2
      return super.stringFromNumber(number)
    }
  }
}

let formatter = CustomFormatter()
formatter.stringFromNumber(NSDecimalNumber(double: 5.00)) // -> "5"
formatter.stringFromNumber(NSDecimalNumber(double: 5.01)) // -> "5.01"
formatter.stringFromNumber(NSDecimalNumber(double: 5.10)) // -> "5.10"

Thanks to this post for the proper way to test if a NSDecimal is an integer.

Community
  • 1
  • 1
0

I think it's best to let the currencyStyle determine the maximumFractionDigits. Just set the minimumFractionDigits to 0 where desired. The code is slightly shorter, but as a bonus if you set the locale, this way will allow for languages that don't have 2 decimal places.

Using NSNumberFormatter gives you the benefit of currency symbols, decimal places and comma’s, all in the perfect places for the different locale’s.

extension NSNumber {
    func currencyString()  -> String? {
        let formatter = NSNumberFormatter()
        formatter.numberStyle = .CurrencyStyle
        if self.isEqualToNumber(self.integerValue) {
            formatter.minimumFractionDigits = 0
        }
        return formatter.stringFromNumber(self)
    }
}

let inputArray: [NSDecimalNumber] = [9.99, 10, 10.00, 9.90, 0, 0.01, 20, 10.01, 0.5, 0.055, 5.0]
let outputArray: [String] = inputArray.map({return $0.currencyString() ?? "nil"})
print(outputArray)

["$9.99", "$10", "$10", "$9.90", "$0", "$0.01", "$20", "$10.01", "$0.50", "$0.06", "$5"]

Adding a locale to a NSNumberFormatter looks like this(ex. from an SKProduct object):

formatter.locale = product!.priceLocale

For an OSX app you need to add:

formatter.formatterBehavior = .Behavior10_4
Roselle Tanner
  • 357
  • 3
  • 11