281

I want to parse a string like "3.5" to a double. However,

double.Parse("3.5") 

yields 35 and

double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint) 

throws a FormatException.

Now my computer's locale is set to German, wherein a comma is used as decimal separator. It might have to do something with that and double.Parse() expecting "3,5" as input, but I'm not sure.

How can I parse a string containing a decimal number that may or may not be formatted as specified in my current locale?

CodeCaster
  • 147,647
  • 23
  • 218
  • 272

19 Answers19

510
double.Parse("3.5", CultureInfo.InvariantCulture)
Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
  • I like to use the `XmlConvert` class ... do you have any ideas whether this is better, worse, and/or different than using `CultureInfo.InvariantCulture`? – ChrisW Aug 30 '09 at 21:23
  • 1
    Well, `XmlConvert` is not really intended to be used to parse a single double value in code. I prefer to use `double.Parse` or `Convert.ToDouble` that make my intention obvious. – Mehrdad Afshari Aug 30 '09 at 21:29
  • 5
    This means doulble.Parse uses the default culture which may not contain dot as a decimal point?? – Ahmed Aug 30 '09 at 22:32
  • 1
    this does not work in all kind of combinations. 1.234.567,89 e.g – JanW Aug 20 '10 at 21:20
  • 3
    if converting 12345678.12345678, it converts too 12345678.123457 – PUG Dec 02 '11 at 16:18
  • 5
    did not work for me: skips comma and returns and int as double – fnc12 Jul 08 '15 at 15:44
  • @MehrdadAfshari It uses the current culture of the thread? => CultureInfo.InvariantCulture. Would it not be rather Thread.CurrentThread.CurrentCulture ? – Pascal Feb 13 '16 at 13:49
  • @Pascal I was replying to the above comment, not explaining the code in my answer body. I meant to say `double.Parse` uses current culture of the thread if not given one explicitly. I can see how it could have been confusingly interpreted. Thanks for clarifying. – Mehrdad Afshari Feb 13 '16 at 22:26
  • Can you elaborate a little bit (in the answer)? – Peter Mortensen Aug 10 '17 at 16:43
87

I usualy use a multi-culture function to parse user input, mostly because if someone is used to the numpad and is using a culture that use a comma as the decimal separator, that person will use the point of the numpad instead of a comma.

public static double GetDouble(string value, double defaultValue)
{
    double result;

    //Try parsing in the current culture
    if (!double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.CurrentCulture, out result) &&
        //Then try in US english
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.GetCultureInfo("en-US"), out result) &&
        //Then in neutral language
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture, out result))
    {
        result = defaultValue;
    }

    return result;
}

Beware though, @nikie comments are true. To my defense, I use this function in a controlled environment where I know that the culture can either be en-US, en-CA or fr-CA. I use this function because in French, we use the comma as a decimal separator, but anybody who ever worked in finance will always use the decimal separator on the numpad, but this is a point, not a comma. So even in the fr-CA culture, I need to parse number that will have a point as the decimal separator.

Pierre-Alain Vigeant
  • 22,635
  • 8
  • 65
  • 101
  • 21
    I'm not sure that's a good idea. You can't reliably parse a double if you don't know the culture: In Germany, double values may contain '.'s, but they're considered to be thousands-separators there. So in Legate's case, GetDouble("3.5") would return 35 in a German locale and 3.5 in a en-us environment. – Niki Oct 15 '09 at 13:58
  • No, Pierre Alain is right, as it's exactly as written. If your culture says "dot is a thousand" separator, then "3.5" is seen as "35" and it's good. However if you culture as no rules for the "dot", then the character is parsed as a decimal point, and it also works. I would have avoided trying the enUS culture, but it's a personal choice. – xryl669 Apr 10 '13 at 15:33
  • If you use numpad in culture with comma, the dot key will be set comma. – CrazyBaran Aug 25 '17 at 09:59
  • The numpad decimal separator depends on the keyboard layout (and not the regional settings - at least on Windows 7) (I use Hungarian to write text, e-mails... and en-US to write code, so it can be either dot or comma. I also use customized regional settings where I changed the decimal separator from ',' to '.' and list separator from ';' to ','. You know, cause csv... Good luck to us all writing multi-culture apps ;) – Steven Spark Jun 22 '18 at 13:15
  • this code doesn't work for a number like 100.35 if the system culture uses a comma as the separator. you'd expect it to return a number of about 100, but it returns 10035. – Lennart Aug 06 '20 at 11:31
37

I couldn't write a comment, so I write here:

double.Parse("3.5", CultureInfo.InvariantCulture) is not a good idea, because in Canada we write 3,5 instead of 3.5 and this function gives us 35 as a result.

I tested both on my computer:

double.Parse("3.5", CultureInfo.InvariantCulture) --> 3.5 OK
double.Parse("3,5", CultureInfo.InvariantCulture) --> 35 not OK

This is a correct way that Pierre-Alain Vigeant mentioned

public static double GetDouble(string value, double defaultValue)
{
    double result;

    // Try parsing in the current culture
    if (!double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.CurrentCulture, out result) &&
        // Then try in US english
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.GetCultureInfo("en-US"), out result) &&
        // Then in neutral language
        !double.TryParse(value, System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture, out result))
    {
        result = defaultValue;
    }
    return result;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Malus Jan
  • 1,860
  • 2
  • 22
  • 26
  • 1
    Re: *"...because in Canada we write 3,5 instead of 3.5"* Are you sure about that? According to *[Decimal mark](https://en.wikipedia.org/wiki/Decimal_mark#Countries_using_Arabic_numerals_with_decimal_point): "Countries where a dot "." is used as decimal mark include ... Canada (when using English)"*. Isn't it more about using a French version of Windows? – Peter Mortensen Nov 21 '16 at 16:57
  • Baybe because of french version. In montreal we write 3,5 not 3.5 – Malus Jan Nov 23 '16 at 03:43
  • 1
    So according to your logic, always only 1 of them returns true? – Emil Oct 09 '17 at 00:56
  • Look at [it](https://stackoverflow.com/questions/1354924/how-do-i-parse-a-string-with-a-decimal-point-to-a-double#answer-1354980) – Malus Jan Oct 09 '17 at 19:19
  • There is still an error. For the input string like GetDouble("10,,,,,,,,0", 0.0). Mentioned function returns 100. – Krivers Aug 14 '18 at 03:56
  • I would like to suggest to add this if (string.IsNullOrEmpty(value)) { return defaultValue; } if (value.Count(f => f == ',' || f == '.') > 1) { return defaultValue; } – Krivers Aug 14 '18 at 04:12
26
Double.Parse("3,5".Replace(',', '.'), CultureInfo.InvariantCulture)

Replace the comma with a point before parsing. Useful in countries with a comma as decimal separator. Think about limiting user input (if necessary) to one comma or point.

Baluda
  • 369
  • 2
  • 2
  • 1
    Much more correct answer than the one that have +133 votes... It allows to live on both systems with "," or "." decimal separator... – Badiboy Jun 11 '14 at 15:14
  • @Badiboy can you explain what is wrong with this answer? As I understand InvariantCulture always has '.' as a decimal separator. So it should work for both systems. – Alex P. Jun 26 '14 at 20:10
  • @Alex11223 You are right. That's why I said that this answer is better then more popular one. PS: Friendly speaking this code will also fail if you have the "," as LIST SEPARATOR (i.e. 1,234,560.01), but I do not know how solve this at all. :) – Badiboy Jun 27 '14 at 04:56
  • 9
    This is not a good answer because in some cultureinfos the , is the thousands separator and can be used. If you replace it to a dot then you end up having several dots and parse will fail: Double.Parse((12345.67).ToString("N", new CultureInfo("en")).Replace(',', '.'), CultureInfo.InvariantCulture) because (12345.67).ToString("N", new CultureInfo("en")).Replace(',', '.') will be formatted as "12.345.67" – codingdave Feb 27 '18 at 11:18
  • 1,234.56 ->1.234.56 not parser. another idea is to check if number contains '.' and ',' and replace ',' with empty string and if only ',' comma presented replace it to '.' – GDocal Aug 07 '19 at 13:49
18

Look, every answer above that proposes writing a string replacement by a constant string can only be wrong. Why? Because you don't respect the region settings of Windows! Windows assures the user to have the freedom to set whatever separator character s/he wants. S/He can open up the control panel, go into the region panel, click on advanced and change the character at any time. Even during your program run. Think of this. A good solution must be aware of this.

So, first you will have to ask yourself, where this number is coming from, that you want to parse. If it's coming from input in the .NET Framework no problem, because it will be in the same format. But maybe it was coming from outside, maybe from a external server, maybe from an old DB that only supports string properties. There, the db admin should have given a rule in which format the numbers are to be stored. If you know for example that it will be an US DB with US format you can use this piece of code:

CultureInfo usCulture = new CultureInfo("en-US");
NumberFormatInfo dbNumberFormat = usCulture.NumberFormat;
decimal number = decimal.Parse(db.numberString, dbNumberFormat);

This will work fine anywhere on the world. And please don't use 'Convert.ToXxxx'. The 'Convert' class is thought only as a base for conversions in any direction. Besides: You may use the similar mechanism for DateTimes too.

  • Agreed! Trying to manually implement Culture features is eventually going to result in a case you didn't expect and a big headache. Let .NET handle it properly. – Khalos Jul 04 '15 at 16:48
  • 3
    a big problem is when users are using a decimal separator that is not considered the decimal separator for its cultural settings – EdmundYeung99 Sep 30 '15 at 01:28
15

The trick is to use invariant culture, to parse dot in all cultures.

double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint, System.Globalization.NumberFormatInfo.InvariantInfo);
Yakeen
  • 2,142
  • 1
  • 17
  • 21
3
string testString1 = "2,457";
string testString2 = "2.457";    
double testNum = 0.5;
char decimalSepparator;
decimalSepparator = testNum.ToString()[1];

Console.WriteLine(double.Parse(testString1.Replace('.', decimalSepparator).Replace(',', decimalSepparator)));
Console.WriteLine(double.Parse(testString2.Replace('.', decimalSepparator).Replace(',', decimalSepparator)));
Martin
  • 47
  • 1
2

My two cents on this topic, trying to provide a generic, double conversion method:

private static double ParseDouble(object value)
{
    double result;

    string doubleAsString = value.ToString();
    IEnumerable<char> doubleAsCharList = doubleAsString.ToList();

    if (doubleAsCharList.Where(ch => ch == '.' || ch == ',').Count() <= 1)
    {
        double.TryParse(doubleAsString.Replace(',', '.'),
            System.Globalization.NumberStyles.Any,
            CultureInfo.InvariantCulture,
            out result);
    }
    else
    {
        if (doubleAsCharList.Where(ch => ch == '.').Count() <= 1
            && doubleAsCharList.Where(ch => ch == ',').Count() > 1)
        {
            double.TryParse(doubleAsString.Replace(",", string.Empty),
                System.Globalization.NumberStyles.Any,
                CultureInfo.InvariantCulture,
                out result);
        }
        else if (doubleAsCharList.Where(ch => ch == ',').Count() <= 1
            && doubleAsCharList.Where(ch => ch == '.').Count() > 1)
        {
            double.TryParse(doubleAsString.Replace(".", string.Empty).Replace(',', '.'),
                System.Globalization.NumberStyles.Any,
                CultureInfo.InvariantCulture,
                out result);
        }
        else
        {
            throw new ParsingException($"Error parsing {doubleAsString} as double, try removing thousand separators (if any)");
        }
    }

    return result;
}

Works as expected with:

  • 1.1
  • 1,1
  • 1,000,000,000
  • 1.000.000.000
  • 1,000,000,000.99
  • 1.000.000.000,99
  • 5,000,111.3
  • 5.000.111,3
  • 0.99,000,111,88
  • 0,99.000.111.88

No default conversion is implemented, so it would fail trying to parse 1.3,14, 1,3.14 or similar cases.

eduherminio
  • 1,514
  • 1
  • 15
  • 31
1

The following code does the job in any scenario. It's a little bit parsing.

List<string> inputs = new List<string>()
{
    "1.234.567,89",
    "1 234 567,89",
    "1 234 567.89",
    "1,234,567.89",
    "123456789",
    "1234567,89",
    "1234567.89",
};
string output;

foreach (string input in inputs)
{
    // Unify string (no spaces, only .)
    output = input.Trim().Replace(" ", "").Replace(",", ".");

    // Split it on points
    string[] split = output.Split('.');

    if (split.Count() > 1)
    {
        // Take all parts except last
        output = string.Join("", split.Take(split.Count()-1).ToArray());

        // Combine token parts with last part
        output = string.Format("{0}.{1}", output, split.Last());
    }

    // Parse double invariant
    double d = double.Parse(output, CultureInfo.InvariantCulture);
    Console.WriteLine(d);
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JanW
  • 1,799
  • 13
  • 23
1
        var doublePattern = @"(?<integer>[0-9]+)(?:\,|\.)(?<fraction>[0-9]+)";
        var sourceDoubleString = "03444,44426";
        var match = Regex.Match(sourceDoubleString, doublePattern);

        var doubleResult = match.Success ? double.Parse(match.Groups["integer"].Value) + (match.Groups["fraction"].Value == null ? 0 : double.Parse(match.Groups["fraction"].Value) / Math.Pow(10, match.Groups["fraction"].Value.Length)): 0;
        Console.WriteLine("Double of string '{0}' is {1}", sourceDoubleString, doubleResult);
Alexander
  • 1,152
  • 1
  • 16
  • 18
1

I think 100% correct conversion isn't possible, if the value comes from a user input. e.g. if the value is 123.456, it can be a grouping or it can be a decimal point. If you really need 100% you have to describe your format and throw an exception if it is not correct.

But I improved the code of JanW, so we get a little bit more ahead to the 100%. The idea behind is, that if the last separator is a groupSeperator, this would be more an integer type, than a double.

The added code is in the first if of GetDouble.

void Main()
{
    List<string> inputs = new List<string>() {
        "1.234.567,89",
        "1 234 567,89",
        "1 234 567.89",
        "1,234,567.89",
        "1234567,89",
        "1234567.89",
        "123456789",
        "123.456.789",
        "123,456,789,"
    };

    foreach(string input in inputs) {
        Console.WriteLine(GetDouble(input,0d));
    }

}

public static double GetDouble(string value, double defaultValue) {
    double result;
    string output;

    // Check if last seperator==groupSeperator
    string groupSep = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator;
    if (value.LastIndexOf(groupSep) + 4 == value.Count())
    {
        bool tryParse = double.TryParse(value, System.Globalization.NumberStyles.Any, System.Globalization.CultureInfo.CurrentCulture, out result);
        result = tryParse ? result : defaultValue;
    }
    else
    {
        // Unify string (no spaces, only . )
        output = value.Trim().Replace(" ", string.Empty).Replace(",", ".");

        // Split it on points
        string[] split = output.Split('.');

        if (split.Count() > 1)
        {
            // Take all parts except last
            output = string.Join(string.Empty, split.Take(split.Count()-1).ToArray());

            // Combine token parts with last part
            output = string.Format("{0}.{1}", output, split.Last());
        }
        // Parse double invariant
        result = double.Parse(output, System.Globalization.CultureInfo.InvariantCulture);
    }
    return result;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Schorsch
  • 137
  • 1
  • 12
0

Instead of having to specify a locale in all parses, I prefer to set an application wide locale, although if string formats are not consistent across the app, this might not work.

CultureInfo.DefaultThreadCurrentCulture = new CultureInfo("pt-PT");
CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo("pt-PT");

Defining this at the begining of your application will make all double parses expect a comma as the decimal delimiter. You can set an appropriate locale so that the decimal and thousands separator fits the strings you are parsing.

Miguel Mesquita Alfaiate
  • 2,851
  • 5
  • 30
  • 56
0

It's difficult without specifying what decimal separator to look for, but if you do, this is what I'm using:

    public static double Parse(string str, char decimalSep)
    {
        string s = GetInvariantParseString(str, decimalSep);
        return double.Parse(s, System.Globalization.CultureInfo.InvariantCulture);
    }

    public static bool TryParse(string str, char decimalSep, out double result)
    {
        // NumberStyles.Float | NumberStyles.AllowThousands got from Reflector
        return double.TryParse(GetInvariantParseString(str, decimalSep), NumberStyles.Float | NumberStyles.AllowThousands, System.Globalization.CultureInfo.InvariantCulture, out result);
    }

    private static string GetInvariantParseString(string str, char decimalSep)
    {
        str = str.Replace(" ", "");

        if (decimalSep != '.')
            str = SwapChar(str, decimalSep, '.');

        return str;
    }
    public static string SwapChar(string value, char from, char to)
    {
        if (value == null)
            throw new ArgumentNullException("value");

        StringBuilder builder = new StringBuilder();

        foreach (var item in value)
        {
            char c = item;
            if (c == from)
                c = to;
            else if (c == to)
                c = from;

            builder.Append(c);
        }
        return builder.ToString();
    }

    private static void ParseTestErr(string p, char p_2)
    {
        double res;
        bool b = TryParse(p, p_2, out res);
        if (b)
            throw new Exception();
    }

    private static void ParseTest(double p, string p_2, char p_3)
    {
        double d = Parse(p_2, p_3);
        if (d != p)
            throw new Exception();
    }

    static void Main(string[] args)
    {
        ParseTest(100100100.100, "100.100.100,100", ',');
        ParseTest(100100100.100, "100,100,100.100", '.');
        ParseTest(100100100100, "100.100.100.100", ',');
        ParseTest(100100100100, "100,100,100,100", '.');
        ParseTestErr("100,100,100,100", ',');
        ParseTestErr("100.100.100.100", '.');
        ParseTest(100100100100, "100 100 100 100.0", '.');
        ParseTest(100100100.100, "100 100 100.100", '.');
        ParseTest(100100100.100, "100 100 100,100", ',');
        ParseTest(100100100100, "100 100 100,100", '.');
        ParseTest(1234567.89, "1.234.567,89", ',');    
        ParseTest(1234567.89, "1 234 567,89", ',');    
        ParseTest(1234567.89, "1 234 567.89",     '.');
        ParseTest(1234567.89, "1,234,567.89",    '.');
        ParseTest(1234567.89, "1234567,89",     ',');
        ParseTest(1234567.89, "1234567.89",  '.');
        ParseTest(123456789, "123456789", '.');
        ParseTest(123456789, "123456789", ',');
        ParseTest(123456789, "123.456.789", ',');
        ParseTest(1234567890, "1.234.567.890", ',');
    }

This should work with any culture. It correctly fails to parse strings that has more than one decimal separator, unlike implementations that replace instead of swap.

osexpert
  • 485
  • 1
  • 6
  • 18
0

Here is a solution that handles any number string that many include commas and periods. This solution is particular for money amounts so only the tenths and hundredths place are expected. Anything more is treated as a whole number.

First remove anything that is not a number, comma, period, or negative sign.

string stringAmount = Regex.Replace(originalString, @"[^0-9\.\-,]", "");

Then we split up the number into the whole number and decimal number.

string[] decimalParsed = Regex.Split(stringAmount, @"(?:\.|,)(?=\d{2}$)");

(This Regex expression selects a comma or period that is two numbers from the end of the string.)

Now we take the whole number and strip it of any commas and periods.

string wholeAmount = decimalParsed[0].Replace(",", "").Replace(".", "");

if (wholeAmount.IsNullOrEmpty())
        wholeAmount = "0";

Now we handle the decimal part, if any.

string decimalAmount = "00";

if (decimalParsed.Length == 2)
    {
        decimalAmount = decimalParsed[1];
    }

Finally we can put the whole and decimal together and parse the Double.

double amount = $"{wholeAmount}.{decimalAmount}".ToDouble();

This will handle 200,00, 1 000,00 , 1,000 , 1.000,33 , 2,000.000,78 etc.

A. Finity
  • 47
  • 1
  • 9
0

I am developing a .Net Maui app that runs on Windows, Mac, Android, and iPhone. I have 3 double values that I parse and store using '.' (e.g. "32.5") in all cases: latitude, longitude, and altitude. I happen to have the Android and iPhone set for Spanish and noticed that the Android parsed the '.' string just fine. However, the iPhone refused to parse it correctly unless I substituted ',' for the '.'. Otherwise, the result was always a huge number.

Rather than deal with the complications of localization, I came up with a simple solution that takes advantage of the specific limits of my double numbers.

                     case "Lat":
                        waypoint.Lat = ParseDouble(xmlVal, 90);
                        break;
                     case "Lon":
                        waypoint.Lon = ParseDouble(xmlVal, 180);
                        break;
                     case "Alt":
                        waypoint.Alt = ParseDouble(xmlVal, 32000);

   public static double ParseDouble(string val, double limit)
   {
      double result;
      if (double.TryParse(val, out result))
      {
         if (Math.Abs(result) <= limit)
            return result;
         else if (double.TryParse(val.Replace('.', ','), out result))
         {
            if (Math.Abs(result) <= limit)
               return result;
         }
      }
      return 0;
   }
charles young
  • 2,269
  • 2
  • 23
  • 38
0

I improved the code of @JanW as well...

I need it to format results from medical instruments, and they also send ">1000", "23.3e02", "350E-02", and "NEGATIVE".

private string FormatResult(string vResult)
{
  string output;
  string input = vResult;

  // Unify string (no spaces, only .)
  output = input.Trim().Replace(" ", "").Replace(",", ".");

  // Split it on points
  string[] split = output.Split('.');

  if (split.Count() > 1)
  {
    // Take all parts except last
    output = string.Join("", split.Take(split.Count() - 1).ToArray());

    // Combine token parts with last part
    output = string.Format("{0}.{1}", output, split.Last());
  }
  string sfirst = output.Substring(0, 1);

  try
  {
    if (sfirst == "<" || sfirst == ">")
    {
      output = output.Replace(sfirst, "");
      double res = Double.Parse(output);
      return String.Format("{1}{0:0.####}", res, sfirst);
    }
    else
    {
      double res = Double.Parse(output);
      return String.Format("{0:0.####}", res);
    }
  }
  catch
  {
    return output;
  }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
JacekK
  • 1
-3
System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.CurrentCulture;

string _pos = dblstr.Replace(".",
    ci.NumberFormat.NumberDecimalSeparator).Replace(",",
        ci.NumberFormat.NumberDecimalSeparator);

double _dbl = double.Parse(_pos);
ArnonZ
  • 3,822
  • 4
  • 32
  • 42
-4

The below is less efficient, but I use this logic. This is valid only if you have two digits after decimal point.

double val;

if (temp.Text.Split('.').Length > 1)
{
    val = double.Parse(temp.Text.Split('.')[0]);

    if (temp.Text.Split('.')[1].Length == 1)
        val += (0.1 * double.Parse(temp.Text.Split('.')[1]));
    else
        val += (0.01 * double.Parse(temp.Text.Split('.')[1]));
}
else
    val = double.Parse(RR(temp.Text));
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
-7

Multiply the number and then divide it by what you multiplied it by before.

For example,

perc = double.Parse("3.555)*1000;
result = perc/1000
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
percy
  • 27
  • 5