How do I parse a string with a decimal point to a

2018-12-31 14:54发布

问题:

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?

回答1:

double.Parse(\"3.5\", CultureInfo.InvariantCulture)


回答2:

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.



回答3:

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


回答4:

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);


回答5:

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.



回答6:

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.



回答7:

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)));


回答8:

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);
}


回答9:

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


回答10:

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.



回答11:

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.



回答12:

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


回答13:

        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);


回答14:

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.



回答15:

System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.CurrentCulture;

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

double _dbl = double.Parse(_pos);


回答16:

I think it is the best answer:

public static double StringToDouble(string toDouble)
{
    toDouble = toDouble.Replace(\",\", \".\"); //Replace every comma with dot

    //Count dots in toDouble, and if there is more than one dot, throw an exception.
    //Value such as \"123.123.123\" can\'t be converted to double
    int dotCount = 0;
    foreach (char c in toDouble) if (c == \'.\') dotCount++; //Increments dotCount for each dot in toDouble
    if (dotCount > 1) throw new Exception(); //If in toDouble is more than one dot, it means that toCount is not a double

    string left = toDouble.Split(\'.\')[0]; //Everything before the dot
    string right = toDouble.Split(\'.\')[1]; //Everything after the dot

    int iLeft = int.Parse(left); //Convert strings to ints
    int iRight = int.Parse(right);

    //We must use Math.Pow() instead of ^
    double d = iLeft + (iRight * Math.Pow(10, -(right.Length)));
    return d;
}


回答17:

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));


回答18:

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