13

I try to convert a String into a NSDecimalNumber here it my code:

class func stringToDecimal (dataToDecimal: String) -> NSDecimalNumber {

    let dataToDecimal = dataToDecimal.stringByReplacingOccurrencesOfString(",", withString: ".")
    print(dataToDecimal)
    let  decimalReturn = NSDecimalNumber(string: dataToDecimal)
    print(decimalReturn)

    if decimalReturn == NSDecimalNumber.notANumber(){
        return 0.00
    }
    return decimalReturn
}

First I thought that maybe the , is wrong but even with . it doesn't work. The first print (before converting) shows e.g 80,00 but the print(decimalReturn) shows only 80

the if line is the just to check if the result is not a number.

rob mayoff
  • 375,296
  • 67
  • 796
  • 848
HongKongTom
  • 207
  • 1
  • 2
  • 11

3 Answers3

23

Use an NSNumberFormatter to parse your input. Set its generatesDecimalNumbers property to true:

let formatter = NumberFormatter()
formatter.generatesDecimalNumbers = true

Here's how you use it, if you want to return 0 when the string can't be parsed:

func decimal(with string: String) -> NSDecimalNumber {
    return formatter.number(from: string) as? NSDecimalNumber ?? 0
}

decimal(with: "80.00")

// Result: 80 as an NSDecimalNumber

By default, the formatter will look at the device's locale setting to determine the decimal marker. You should leave it that way. For the sake of example, I'll force it to a French locale:

// DON'T DO THIS. Just an example of behavior in a French locale.
formatter.locale = Locale(identifier: "fr-FR")

decimal(with: "80,00")
// Result: 80

decimal(with: "80.00")
// Result: 0

If you really want to always use a comma as the decimal mark, you can set the decimalSeparator property:

formatter.decimalSeparator = ","
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • it worked, but I had to do the `let dataToDecimal = dataToDecimal.stringByReplacingOccurrencesOfString(",", withString: ".")` first, to bring my string to get not nil. If I just try a string with a "," it will bring nil as result. – HongKongTom Nov 14 '15 at 06:16
  • 2
    I ve read that NSDecimal is precise for calculation, but if I take 80.01 the outcome is 80.01000002 if I take 80.10 it s 80.09999999. How would you reduce the digits to 2? – HongKongTom Nov 14 '15 at 15:26
  • You'll have to show your code. It would be best to do so as a new question. – rob mayoff Nov 14 '15 at 15:50
  • I wonder, how is this different from `Decimal(string: "80,00", locale: Locale.current)`? – Sulthan Apr 13 '17 at 14:50
  • `Decimal(string:locale:)` was not added until iOS 10 and macOS 10.12 in 2016. – rob mayoff Apr 13 '17 at 14:51
  • @robmayoff I don't believe that's true. `NSDecimalNumber` had that initializer always. If you notice, all `Decimal` methods are marked as `iOS 10+`. – Sulthan Apr 13 '17 at 14:53
  • You're right. I was looking at the Swift docs, and everything in the `Decimal` class is available as of the 2016 OS versions. – rob mayoff Apr 13 '17 at 14:56
4

There is nothing wrong with the way you are creating a NSDecimalNumber from a String. However, you might not need to worry about replacing the comma with a period if that is how your local formats numbers.

The reason it prints 80 instead of 80,00 or 80.00 is that print just uses the description property of the number. If you want to customize the formatting, you should setup your own NSNumberFormatter.

let number = NSDecimalNumber(string: "80.00")
print(number) // 80
let formatter = NSNumberFormatter()
formatter.minimumFractionDigits = 2
let string = formatter.stringFromNumber(number)
print(string) // 80.0
Craig Siemens
  • 12,942
  • 1
  • 34
  • 51
  • I tried out, but in real project and playground it brings an error message "Cannot convert value of type "String" to expected argument type "NSNumber" but the ...minimumFractions I like and will play with that. – HongKongTom Nov 14 '15 at 06:22
  • It's possible there is another problem in your code then. The code I posted works fine in a playground. – Craig Siemens Nov 14 '15 at 19:25
0

When server forces some certain locale use that instead of Locale.current lest you want breakage depending on the user's settings

extension String {
    var decimalFromServerString: NSDecimalNumber? {
        let parsed = NSDecimalNumber(string: self, locale: ru_ru_posix)
        if parsed == .notANumber {
            return nil
        }
        return parsed
    }
}

PS. if you hardcore locale to en_US POSIX for some reason string "..." comes out as 0. Go figure :-[

Anton Tropashko
  • 5,486
  • 5
  • 41
  • 66