关于C#:如何将带小数点的字符串解析为双精度?

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

我想把像"3.5"这样的字符串解析为double。然而,

1
double.Parse("3.5")

收益率为35

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

抛出一个FormatException

现在我的计算机的区域设置为德语,其中逗号用作小数分隔符。可能与此有关,double.Parse()希望"3,5"作为输入,但我不确定。

如何分析包含十进制数的字符串,该十进制数的格式可能与当前区域设置中指定的格式不符?


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


我通常使用多区域性函数来解析用户输入,主要是因为如果有人习惯于numpad,并且使用的区域性使用逗号作为小数分隔符,那么此人将使用numpad的点而不是逗号。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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;
}

不过要小心,@nikie的评论是真的。为我辩护,我在一个我知道文化可以是en-us、en-ca或fr-ca的受控环境中使用此函数。我使用此函数是因为在法语中,我们使用逗号作为小数分隔符,但任何从事金融工作的人都将始终使用numpad上的小数分隔符,但这是一个点,而不是逗号。因此,即使在fr-ca区域性中,我也需要解析将有一个点作为十进制分隔符的数字。


我不能写评论,所以我在这里写:

double.parse("3.5",cultureInfo.invariangCulture)不是一个好主意,因为在加拿大,我们编写的是3.5而不是3.5,结果这个函数给了我们35。

我在我的电脑上测试了这两项:

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

这是皮埃尔·阿兰·维吉特提到的正确方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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;
}


技巧是使用不变的区域性,解析所有区域性中的点。

1
double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint, System.Globalization.NumberFormatInfo.InvariantInfo);


1
Double.Parse("3,5".Replace(',', '.'), CultureInfo.InvariantCulture)

在分析之前用点替换逗号。在使用逗号作为小数分隔符的国家/地区中很有用。考虑将用户输入(如有必要)限制为一个逗号或点。


看,上面所有建议用常量字符串替换字符串的答案都可能是错误的。为什么?因为你不尊重Windows的区域设置!Windows确保用户可以自由设置任何分隔符。他/她可以打开控制面板,进入区域面板,单击高级,随时更改字符。即使在程序运行期间。想想看。一个好的解决方案必须意识到这一点。

所以,首先你必须问自己,这个数字是从哪里来的,你想要解析它。如果它来自.NET框架中的输入,没问题,因为它将采用相同的格式。但它可能来自外部,可能来自外部服务器,也可能来自只支持字符串属性的旧数据库。在那里,数据库管理员应该给出一个规则,以何种格式存储数字。例如,如果您知道它将是一个US DB和US格式,您可以使用这段代码:

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

这在世界上任何地方都可以。请不要使用"convert.toxyxx"。"convert"类只被认为是任何方向转换的基础。另外:对于日期时间也可以使用类似的机制。


1
2
3
4
5
6
7
8
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)));


我认为如果值来自用户输入,100%正确的转换是不可能的。例如,如果值为123.456,则可以是分组,也可以是小数点。如果你真的需要100%,你必须描述你的格式,如果不正确的话抛出一个异常。

但是我改进了Janw的代码,所以我们可以提前一点到达100%。其背后的想法是,如果最后一个分隔符是GroupSeparator,那么它将更多地是一个整数类型,而不是一个Double。

添加的代码位于getdouble的第一个if中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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;
}

以下代码在任何情况下都会执行该任务。有点解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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);
}


如果不指定要查找的小数点分隔符是很困难的,但是如果指定了,这就是我要使用的内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
    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", ',');
    }

这应该适用于任何文化。与替换而不是交换的实现不同,它正确地解析具有多个十进制分隔符的字符串失败。


我在这个主题上的两分钱,试图提供一个通用的、双重转换的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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;
}

按预期工作:

  • 一点一
  • 1,1
  • 十亿
  • 1.0.0.000 0.000
  • 100000000.99美元
  • 1.000.000.000,99
  • 五百万零一百一十一点三
  • 5.000 0.111,3
  • 0.99000111,88
  • 0,97.000 0.111.88

没有实现默认转换,因此尝试解析1.3,141,3.14或类似情况时失败。


我也改进了@janw的代码…

我需要它来格式化医疗器械的结果,它们还发送">1000"、"23.3e02"、"350e-02"和"阴性"。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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;
  }
}

1
2
3
4
5
6
        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);

我不必在所有分析中指定区域设置,而是更喜欢设置应用程序范围的区域设置,尽管如果字符串格式在整个应用程序中不一致,这可能不起作用。

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

在应用程序的开头定义这一点将使所有的双精度分析都需要一个逗号作为十进制分隔符。您可以设置适当的区域设置,以便十进制和千位分隔符适合您正在分析的字符串。


1
2
3
4
5
6
7
System.Globalization.CultureInfo ci = System.Globalization.CultureInfo.CurrentCulture;

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

double _dbl = double.Parse(_pos);

下面是效率较低的,但我使用这个逻辑。只有小数点后有两位数字时,此选项才有效。

1
2
3
4
5
6
7
8
9
10
11
12
13
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));


我认为这是最好的答案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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;
}


将数字相乘,然后除以之前的值。

例如,

1
2
perc = double.Parse("3.555)*1000;
result = perc/1000