Alexander Mills
Alexander Mills

Reputation: 277

Parse String to Decimal using any decimal seperator

I want to parse a string from an Text input to decimal. The value represents a currency value.

Currently i got this solution:

private Decimal CastToDecimal(string value) 
{
    Decimal result;
    var valid = Decimal.TryParse(value, NumberStyles.Currency, null, out result);
    return valid ? result : -1;
}

This works pretty well so far, except for possible culture-differences. I'm german and i expect most users to enter german-style puctuation. But it is possible that someone uses "." instead of "," and the conversion will fail.

"123,45€" => 123.45

"123.456,78€" => 123456.78

"123.45€" => 12345 <- I want the result to be 123.45 here

Is there a way to automatically detect the used culture for a decimal value? Such that it does not matter if you use german or english punctuation, you still get the same result?

Update:

Thanks to your help, i created a method which does what i want (i think).

private static Decimal CastToDecimal(string value)
{
    Decimal resultDe;
    Decimal resultEn;
    var style = NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands;
    var cultureDe = CultureInfo.CreateSpecificCulture("de-DE");
    var cultureEn = CultureInfo.CreateSpecificCulture("en-GB");
    var deValid = Decimal.TryParse(value, style, cultureDe, out resultDe);
    var enValid = Decimal.TryParse(value, style, cultureEn, out resultEn);
    var minVal = Math.Min(resultDe, resultEn);
    var maxVal = Math.Max(resultDe, resultEn);
    if (!deValid)
        return resultEn;
    if (!enValid)
        return resultDe;
    return BitConverter.GetBytes(decimal.GetBits(minVal)[3])[2] > 2 ? maxVal : minVal;
}

This code...

    Console.WriteLine(CastToDecimal("123,45"));
    Console.WriteLine(CastToDecimal("123.45"));
    Console.WriteLine(CastToDecimal("123,450"));
    Console.WriteLine(CastToDecimal("123.450"));
    Console.WriteLine(CastToDecimal("123.123,45"));
    Console.WriteLine(CastToDecimal("123,123.45"));

returns this:

123,45
123,45
123450
123450
123123,45
123123,45

Upvotes: 4

Views: 833

Answers (4)

Mubashar
Mubashar

Reputation: 12668

The only solution is to add validation on input and give user and example by the way if it is a webpage then find a way to get input according to user's culture. I suggests you to not to try to do what you are trying because there are some culture which contradict each others for example in currency;

US/Australia/Many others uses following format

45,999.95

where , is thousand separator and . is decimal separator

whereas in some European countries

45.999,95  

means the same as above but thousands separator is . and , is used as decimal separator.

Now issue is there is no guarantee that user use both separator and your system may assume thousand separator as decimal and so on.

If you really don't want to bother user then make separate input fields for major and minor currencies.

So its better to not to go there. I believe this may help. Happy Coding :)

Update:

Same case is with date format e.g. in US format month comes first and then day whereas in Australia day comes first and then month now 02/01/2015 input will mean differently system can't tell the intention of user.

Upvotes: 0

Jemolah
Jemolah

Reputation: 2192

I encountered the same problem some time ago. My solution was writing my own parser in Java. The algorithm first cleans up the string. Brief description follows:

  1. Scan string from left to right
  2. If char = '.' then dotFound=true ; lastSeparatorPosition = index ; dots++
  3. If char = ',' then commaFound=true ; lastSeparatorPosition = index ; commas++
  4. If dots == 0 && commas == 0 then its an integer => done
  5. If dots > 0 && commas > 0 then the one at lastSeparatorPosition is the decimal separator. Remove the others from the string => done
  6. /* only one separator type */ if ( dots + commas ) > 1 then remove them // because must be thousands separator => done
  7. /* separator occurs once */ if numberOfDigits right of separator == 3 then you have to decide :-) either integer or decimal with 3 digits in fraction

7 is the only remaining problem like chiastic-security already stated. Here you can only decide taken the conceptual environment into account. All other cases are safe.

Have fun

Upvotes: 2

shelbypereira
shelbypereira

Reputation: 2245

the solution at http://msdn.microsoft.com/en-us/library/3s27fasw%28v=vs.110%29.aspx which includes setting the NumberStyle may be helpful.

...
value = "1.345,978";
style = NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands;
culture = CultureInfo.CreateSpecificCulture("es-ES");
if (Double.TryParse(value, style, culture, out number))
   Console.WriteLine("Converted '{0}' to {1}.", value, number);
else
   Console.WriteLine("Unable to convert '{0}'.", value);
// Displays:  
//       Converted '1.345,978' to 1345.978. 

value = "1 345,978";
if (Double.TryParse(value, style, culture, out number))
   Console.WriteLine("Converted '{0}' to {1}.", value, number);
else
   Console.WriteLine("Unable to convert '{0}'.", value);
...

Upvotes: 2

chiastic-security
chiastic-security

Reputation: 20520

This can't be done, simply because there are strings that are meaningful in two different cultures, but mean different things. For instance:

123.456

123,456

The first is a bit over 123 in the UK, but 123456 in Germany; the second is 123456 in the UK but a bit over 123 in France.

Upvotes: 1

Related Questions