18
votes

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

6
2,5 may be interpreted differently in different countries. All you need is using the correct CultureInfoEZI
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
Why do you use InvariantCulture when it's a culture specific thing?Jeroen Vannevel
@ng80092b can I change it only in the tryparse scope? Of course. See the method CultureInfo.GetCultureInfo.EZI
@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.user743382

6 Answers

7
votes

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

0
votes

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.

0
votes

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;
}
-1
votes

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!

-1
votes

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
        }
-2
votes

Instead of checking the culture or replace, it's possible to use TryParse and supporting both separators without too much code.

double d;
if (double.TryParse("12.3", System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture, out d))
    Console.WriteLine("Value converted {0}", d);
else
    Console.WriteLine("Unable to parse this number");