1

I have decimal numbers stored in my database as decimal. And I display them according to the user's locale:

- (NSString *) getLocalizedCurrencyStringWithDigits
{
    NSNumberFormatter *numberFormatter =[[NSNumberFormatter alloc] init];
    [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
    [numberFormatter setLocale:[NSLocale currentLocale]];
    [numberFormatter setMinimumFractionDigits:2];
    [numberFormatter setMaximumFractionDigits:2];

    NSString *numberString = [numberFormatter stringFromNumber:self];

    return numberString;
}

And I display these strings in textfields, that are editable. So if a user starts to edit the field, I want to remove the thousand separator. Otherwise (in my country) I enter 1'000.55 and it then doesn't recognize the "'" and saves just a 1. Here is my function to parse the textfields and in the view controllers this exact return value will be saved to the database:

+ (NSDecimalNumber *) getUnLocalizedDecimalNumberWithString:(NSString *)currencyString
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
    [numberFormatter setLocale:[NSLocale currentLocale]];
    [numberFormatter setMinimumFractionDigits:2];
    [numberFormatter setMaximumFractionDigits:2];

    BOOL isDecimal = [numberFormatter numberFromString:currencyString] != nil;
    if(isDecimal){
        return [NSDecimalNumber decimalNumberWithString:currencyString locale:[NSLocale currentLocale]];
    } else {
        return [NSDecimalNumber zero];
    }

However I don't get it to work...actually it would be best if numbers with"'" and without would be saved correctly but I don't get it to work :/

EDIT (my solution): Got it working like this:

- (NSString *) getLocalizedCurrencyStringWithDigits:(int)digits
{
    NSNumberFormatter *numberFormatter =[[NSNumberFormatter alloc] init];
    [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
    [numberFormatter setLocale:[NSLocale currentLocale]];
    [numberFormatter setMinimumFractionDigits:digits];
    [numberFormatter setMaximumFractionDigits:digits];

    NSString *numberString = [numberFormatter stringFromNumber:self];

    return numberString;
}

+ (NSDecimalNumber *) getUnLocalizedDecimalNumberWithString:(NSString *)currencyString
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    [numberFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
    [numberFormatter setLocale:[NSLocale currentLocale]];
    [numberFormatter setMinimumFractionDigits:2];
    [numberFormatter setMaximumFractionDigits:2];

    NSNumber *formatedCurrency = [numberFormatter numberFromString:currencyString];

    BOOL isDecimal = formatedCurrency != nil;
    if(isDecimal){
        return [NSDecimalNumber decimalNumberWithDecimal:[formatedCurrency decimalValue]];
    } else {
        return [NSDecimalNumber zero];
    }
}

+(NSDecimalNumber *) getUnLocalizedDecimalNumberWithString:(NSString *)currencyString

interprets now every string correctly. 1'000 and 1000 and 1000.00 are all 1000 now :-)

MichiZH
  • 5,587
  • 12
  • 41
  • 81
  • If you fix your own problem, you should post the solution as an answer, then accept it (once the system allows you to). – Almo Jan 27 '14 at 21:00
  • Yeah first I wanted to, but then I wanted to see first if my solution is ok like that. I mean if I lose any precision by converting the string first to an NSNumber and then to an NSDecimalNumber. Or if I'm creating too many objects...I'm still learning with every line of code :) – MichiZH Jan 27 '14 at 21:11

5 Answers5

9

The simplest and most clean solution I believe is this:

NSLocale *locale = [NSLocale currentLocale];
NSString *thousandSeparator = [locale objectForKey:NSLocaleGroupingSeparator];
NSString *result = [decimalStringWithThousandSeperator stringByReplacingOccurrencesOfString:thousandSeparator withString:@""];
neowinston
  • 7,584
  • 10
  • 52
  • 83
jki
  • 4,617
  • 1
  • 34
  • 29
5

Set usesGroupingSeparator to NO (or false in Swift) on your NSNumberFormatter instance.

It maintains the decimal separator while removing the rest of grouping separators.

monchote
  • 3,440
  • 2
  • 20
  • 20
0

I see a couple of problems with your code, and am also not clear what it is you want to do.

The first problem is that in your method getLocalizedCurrencyStringWithDigits, you are calling stringFromNumber and passing in "self". That does not make sense, unless you are creating a custom subclass of NSNumber, which I doubt you are doing.

Next problem is that you are using the NSDecimalNumber class, which I suspect is not what you think it is. It is a special subclass of NSNumber that is designed for doing base 10 math without the rounding errors you get doing math on fractional numbers using binary arithmetic. NSDecimalNumber is a very specialized class, and unless you know exactly why you are using it, it is not what you need. Just use a regular NSNumber instead.

Then the next problem. You say you are trying to "remove the thousands separator." What thousands separator? If you convert a number value to a string then it should be in the correct format for the user's locale. Also, why are you using NSNumberFormatterDecimalStyle If what you want to display is currency amounts? Shouldn't you be using NSNumberFormatterCurrencyStyle instead?

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • 1. Yes that's what I'm doing, it's a category on NSDecimalNumber 2. I'm using NSDecimalNumber because I'm dealing with currencies and need exact values 3. I'm using decimalstyle, because I don't need any currency sign like $ or whatever. And in my string I get from my method I do have a thousands separator and the parsing doesn't recognize it... – MichiZH Jan 27 '14 at 20:13
  • NSNumber is a class cluster. Subclassing an object that is part of a class cluster has special requirements. Are you aware of the special steps required to subclass a member of a class cluster? – Duncan C Jan 27 '14 at 20:30
  • You need to give your readers a much more complete picture of what you are doing. We need to know that the code you are posting is part of a custom subclass of NSDecimalNumber. And you should probably give an overview of what your goals are for this special subclass, along with the methods you are adding, and how you are handling the fact that you are subclassing a member of a class cluster. – Duncan C Jan 27 '14 at 20:32
  • Sorry I'm still new to iOS :-/ No I didn't know there is a special requirement for subclassing? Well, I'm actually not subclassing only making a category, or is that the same? I just want to convert numbers I've stored as decimal in my core data database into a localized string for display. And a user ccan change these values. So my category (class method) should also parse this changed value and make a NSDecimalNumber out of this (localized) string to store it back to coredata – MichiZH Jan 27 '14 at 20:35
  • 1
    @MichiZH, no a category is different than a subclass, and as long as you don't try to override a method already defined in the class, does not pose the same problems with class clusters that you get with subclassing. – Duncan C Jan 27 '14 at 21:55
  • Thx Duncan, that's what I actually thought. I'm glad I'm not doing anything wrong here :-) – MichiZH Jan 27 '14 at 21:59
-1

From this answer, remove all characters that are not a number or a decimal and you should get a stripped string with only a valid number when converted using NSNumberFormatter or floatValue

NSScanner *scanner = [NSScanner scannerWithString:originalString];
NSCharacterSet *numbers = [NSCharacterSet 
        characterSetWithCharactersInString:@".0123456789"];

while ([scanner isAtEnd] == NO) {
  NSString *buffer;
  if ([scanner scanCharactersFromSet:currencyString intoString:&buffer]) {
    [strippedString appendString:buffer];

  } else {
    [scanner setScanLocation:([scanner scanLocation] + 1)];
  }
}

Or you could use a regular expression:

NSString * strippedNumber = [currencyString stringByReplacingOccurrencesOfRegex:@"^([0-9]+)?(\\.([0-9]{1,2})?)?$" withString:@""];

I can't swear by the regular expression syntax used here as I stole if from here. I don't have much experience there.

Community
  • 1
  • 1
Putz1103
  • 6,211
  • 1
  • 18
  • 25
  • 1
    This is dangerous, because it doesn't take the user's locale into account. For example the German string "1.000,80" will be turned into "1.0008" losing the correct decimal mark. – nschum Jun 29 '14 at 16:41
-1

Here is a Swift 5 version

var numberStr = "1,000"
let thousandsSep = Locale.current.groupingSeparator
numberStr = numberStr.replacingOccurrences(of: thousandsSep!, with: "")
let numberDouble = Double(numberStr)
Tom Coomer
  • 6,227
  • 12
  • 45
  • 82