4

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
9
  • 2
    There is none. You need to know which culture was used to format it. Or use InvariantCulture everywhere. Here's my answer to very related question. Commented Dec 12, 2014 at 12:16
  • But 123.456 means 123.456 with UK punctuation and it means 123456 with German! So there's no clear way to disambiguate. Commented Dec 12, 2014 at 12:16
  • 1
    Since the field is for currency only, 123.456 would be 123456, since there are no half cents. It's only a seperator if there are less than 3 digits on the right side of the seperator Commented Dec 12, 2014 at 12:18
  • 1
    @AlexanderMills That might be true for your wallet, but not for some applications which calculate the average price for a product for example. Commented Dec 12, 2014 at 12:19
  • 1
    Yes but decimal.Parse does not understand the context it just sees a string. You should be able to get the user's culture and then pass in the appropriate CultureInfo so that the parse will work and fail correctly. Commented Dec 12, 2014 at 12:21

4 Answers 4

2

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);
...
Sign up to request clarification or add additional context in comments.

Comments

2

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

3 Comments

This only works it's guaranteed that there is a decimal separator.
Am not sure this is going to work. May be I'm missing something. There are number group separator, decimal separator, etc. How'll you disambiguate all of them?
the first non-digit can be thousand separator
1

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.

2 Comments

This isn't true if the OP can assume that there will only be two places after the decimal.
@juharr It might be possible to work on this assumption, but it seems downright dangerous to me.
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.