27

The problem: Let's assume you are using a dot "." as a decimal separator in your regional setting and have coded a string with a comma.

string str = "2,5";

What happens when you decimal.TryParse(str, out somevariable); it?

somevariable will assume 0.

What can you do to solve it?

1- You can

decimal.TryParse(str, NumberStyles.Any, CultureInfo.InvariantCulture, out somevariable);

And it will return 25, and not 2.5 which is wrong.

2- You can

decimal.TryParse(str.Replace(",","."), out num);

And it will return the proper value, BUT, if the user uses "," as a decimal separator it will not work.

Possible solution that I can't make it work:

Get the user decimal separator in regional settings:

char sepdec = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);

And make somehow the replace from ",",sepdec , that way it would stay a comma if its a comma, and replace by an actual dot if the user uses dots.

Hints?

Edit: Many users posted useful information, lately, using the arguments NumberStyles.Any, CultureInfo.GetCultureInfo("pt-PT") on a tryParse wouldn't work if your separator is set to "," So it pretty much doesnt fullfill the premise of making a tryparse "universal".

I'll work around this, if anyone has more hints you'r welcome

ng80092b
  • 621
  • 1
  • 9
  • 24
  • 6
    `2,5` may be interpreted differently in different countries. All you need is using the correct *CultureInfo* – EZI Apr 04 '15 at 22:56
  • 4
    Suppose the value was `string str = "2,500";` what value does this represent now? 2.5 or 2500.0 ? There is no way to tell, unless you know the culture that is associated with this string value. – Alex Apr 04 '15 at 22:58
  • Why do you use InvariantCulture when it's a culture specific thing? – Jeroen Vannevel Apr 04 '15 at 23:05
  • @EZI but can I change it? does it change for the whole code, can I change it only in the tryparse scope? I've took a reading on msdn but I still didnt manage to clearly understand it – ng80092b Apr 04 '15 at 23:05
  • 1
    @ng80092b `can I change it only in the tryparse scope?` Of course. See the method `CultureInfo.GetCultureInfo`. – EZI Apr 04 '15 at 23:07
  • "`somevariable` will assume 0." -- This suggests that you're disregarding the return value of `TryParse`. Don't do that. It returns a `bool` indicating whether the parse succeeded. If it returns `false`, ignore the value that is assigned to `somevariable`, as it does not and cannot correspond to the string you passed as input. –  Apr 04 '15 at 23:14
  • @EZI Wow, this is just amazing, I used the argument `CultureInfo.GetCultureInfo("pt-PT")` inside the `tryParse` and it returned `2.5` as it should. Why do I have to set it this way for the tryParse to work correctly? This is a very interesting issue, it's too bad msdn is so deep and imprecise about this (because there's a lot of info about it, i'm sure). Does anyone knows a jump-start guide on this stuff? – ng80092b Apr 04 '15 at 23:23
  • @hvd I'm only using `tryParse` to convert my `strings` into `variables` I know very little about how they work. What use could a bool have for the end-user when I'm converting some`string` to a `variable` ? ps: I'm a beginner in programming – ng80092b Apr 04 '15 at 23:25
  • 1
    @ng80092b Your program should treat a return value of `false` as an error, and handle it as appropriate for your program. For instance: `if (decimal.TryParse(str, out val)) { Console.WriteLine("val: " + val); } else { Console.WriteLine(str + " cannot be parsed!"); }` Note how the value of `val` is only used in the `if` branch that gets taken when `TryParse` returns `true`. This tells the user that the value of `str` is incorrect, and that the user should have used a different value. It generally doesn't make sense to treat gibberish such as "avh5v3l" as a valid `decimal` with a value of zero. –  Apr 04 '15 at 23:28
  • @hvd took note!! very nice stuff I didnt knew about tryparsing :) danke – ng80092b Apr 04 '15 at 23:32

7 Answers7

19

I know the thread is a little bit older, but I try to provide an answer.

I use regular expression to determine the used number format in the string. The regex also matches numbers without decimal separators ("12345").

var numberString = "1,234.56"; // en
// var numberString = "1.234,56"; // de
var cultureInfo = CultureInfo.InvariantCulture;
// if the first regex matches, the number string is in us culture
if (Regex.IsMatch(numberString, @"^(:?[\d,]+\.)*\d+$"))
{
    cultureInfo = new CultureInfo("en-US");
}
// if the second regex matches, the number string is in de culture
else if (Regex.IsMatch(numberString, @"^(:?[\d.]+,)*\d+$"))
{
    cultureInfo = new CultureInfo("de-DE");
}
NumberStyles styles = NumberStyles.Number;
bool isDouble = double.TryParse(numberString, styles, cultureInfo, out number);

HTH

Thomas

ace lafit
  • 249
  • 3
  • 6
1

I just want to say that HTH Thomas solution worked really well in my project, except for when trying to parse negative decimal numbers with commas. One solution to this, which is probably not optimized because I don't fully understand the regex Ismatch code, but that works is adding the possibility of finding a "-" before the number in the ifs statements, like this:

        var cultureInfo = CultureInfo.InvariantCulture;
        if (Regex.IsMatch(equation.inputFieldsTexts[i], @"^(:?[\d,]+\.)*\d+$") || Regex.IsMatch(equation.inputFieldsTexts[i], @"^(:?[-\d,]+\.)*\d+$"))
        {
            cultureInfo = new CultureInfo("en-US");
        }
        // if the second regex matches, the number string is in DE culture
        if (Regex.IsMatch(equation.inputFieldsTexts[i], @"^(:?[\d.]+,)*\d+$") || Regex.IsMatch(equation.inputFieldsTexts[i], @"^(:?[-\d.]+,)*\d+$"))
        {
            cultureInfo = new CultureInfo("de-DE");
        }
Adrian
  • 11
  • 1
0

The solution I use is to simply show the user what the parsed value is.

I have a custom TextBox control which verifies the input when the control loses focus and such. If the control expects a floating point value (which is a property), then it will try to parse the value entered. If the TryParse succeeds, I display the out value in the control's text.

This way, when a user enters 12.3 the value might change to 123 because in the current culture 12,3 is expected. It's then up to them to decide to correct this.

John Willemse
  • 6,608
  • 7
  • 31
  • 45
0

How about this method:

  • clean the string from anything else than numbers, dot, comma and negative sign
  • take the last index of dot or comma
  • split the clean string and remove all thousands separators from the first part
  • convert both parts to integer
  • change the sign of the second part if necessary
  • add the first part with the second part divided by decimal places
public static bool TryParseDoubleUniversal(this string s, out double result) {
  result = 0.0;
  if (string.IsNullOrEmpty(s)) return false;

  var clean = new string(s.Where(x => char.IsDigit(x) || x == '.' || x == ',' || x == '-').ToArray());
  var iOfSep = clean.LastIndexOfAny(new[] { ',', '.' });
  var partA = clean.Substring(0, iOfSep).Replace(",", string.Empty).Replace(".", string.Empty);
  var partB = clean.Substring(iOfSep + 1);
  if (!int.TryParse(partA, out var intA)) return false;
  if (!int.TryParse(partB, out var intB)) return false;
  if (intA < 0) intB *= -1;
  var dp = double.Parse("1".PadRight(partB.Length + 1, '0'));

  result = intA + intB / dp;
  return true;
}
MartinHoly
  • 147
  • 1
  • 11
0

The question is old but since it was my first hit on Google. And the approach in How to change symbol for decimal point in double.ToString()? seems to be a valid solution you can use the NumberFormatInfo to set the decimal separator like this:

string value = "3,2";
NumberFormatInfo nfi = new NumberFormatInfo();
nfi.NumberDecimalSeparator = ",";

decimal.TryParse(value, NumberStyles.Any, nfi, out decimal dec);
Jonny
  • 1,037
  • 7
  • 15
-1

I found a solution, I'm a beginner on this regional and comma-dots theme so if you have comments to improve the understanding of this please be welcome.

We start of by getting what decimal separator the user has set in his regional options outside before the Form{InitializeComponent();} (I want a universal variable that will allow me to correct the code)

 char sepdec = Convert.ToChar(CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator);

In the tryParse, to get it to behave universally we will read the dots and commas in the string, and turn them into the decimal separator we defined as sepdec

decimal.TryParse(str.Replace(",",sepdec.ToString()).Replace(".",sepdec.ToString()),  out somevariable);

I hope this helps, please comment improvement suggestions!

ng80092b
  • 621
  • 1
  • 9
  • 24
  • this wont work either because you are just replacing comma with dot that dot with dot again. i dont think you can have a global version. but if you have say one US and one local version you can do like in my answer. use the result that tryparse returns true – btevfik Apr 05 '15 at 01:22
  • it wont work because 2,500.55 will result to 2.500.55 which wont parse probably. – btevfik Apr 05 '15 at 01:24
  • It works because on my code I only allow one dot or one comma to be placed in the textboxes. There is never a dot and a comma simultaneously. I will have 2500.55 or 2500,55 accordingly to what the user has a decimal separator. There is no other possibility. Maybe it's because I'm using Portuguese regional settings, but well, good luck is always welcome ;) Also your method doesn't work in "pt-PT" if i'm using a dot separator. – ng80092b Apr 05 '15 at 01:56
-1

In Android Xamarin, I ran into the same issue several times. Some solutions worked until the Android got upgraded into a new version, then the problem came out again. So I came with an universal solution, which works fine. I read the numeric input as text, then parse it into decimal with a custom parser.

The custom parser is returning 0 when parsing into decimal is not possible. It does allow input text containing decimal number with either comma or dot, with no group separators:

public static decimal ParseTextToDecimal(string decimalText)
        {
            if (decimalText == String.Empty) return 0;
            string temp = decimalText.Replace(',', '.');
            var decText = temp.Split('.');
            if (!Int32.TryParse(decText[0], out int integerPart)) return 0;
            if (decText.Length == 1) return integerPart;
            if (decText.Length == 2)
            {
                if (!Int32.TryParse(decText[1], out int decimalPart)) return 0;

                decimal powerOfTen = 10m;
                for (int i = 1; i < decText[1].Length; i++) powerOfTen *= 10;
                return integerPart + decimalPart / powerOfTen;
            }
            return 0; // there were two or more decimal separators, which is a clear invalid input
        }
Codigo
  • 366
  • 2
  • 9
  • The imagine one million in 3 possible formats depending on the Culture `1,000,000.00`, `1.000.000,00` and `1 000 000,00`. Two commas can give you a surprise. – aepot Feb 21 '21 at 22:03
  • @aepot as it is clearly mentioned in the proposed solution, group separator is nor allowed. It could be allowed with a small modification of the code, but usually when you enter decimal digits on the phone, you do not allow the group separator. So in your cases, only 1000000,00 or 1000000.00 would be a valid text input before parsing. – Codigo Feb 22 '21 at 20:07