如何将带有小数点的字符串解析为双精度浮点数?

282
我想将像"3.5"这样的字符串解析为双精度数。
double.Parse("3.5") 
产生 35 和
double.Parse("3.5", System.Globalization.NumberStyles.AllowDecimalPoint) 

抛出一个FormatException

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

如何解析包含可能或可能不按照当前语言环境指定格式的小数的字符串?


小数点逗号肯定会影响输出结果。 - ChrisF
13
如果适用于你的情况,不要忘记使用double.TryParse()方法。 - Kyle Gagnet
19个回答

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

1
嗯,XmlConvert并不是用来在代码中解析单个双精度值的。我更喜欢使用double.ParseConvert.ToDouble,这样我的意图就很明显了。 - Mehrdad Afshari
5
这意味着 double.Parse 使用默认的文化设置,该设置可能不包含点作为小数点? - Ahmed
1
这并不适用于所有种类的组合。例如1,234,567.89。 - JanW
3
如果将12345678.12345678进行转换,它会转换为12345678.123457 - PUG
5
对我来说没有用:跳过逗号并将int作为double返回。 - fnc12
显示剩余4条评论

87

我通常使用一个多文化函数来解析用户输入,主要是因为如果有人习惯于使用数字键盘,并且使用一个以逗号作为小数分隔符的文化,这个人会使用数字键盘上的点而不是逗号。

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。我使用此函数是因为在法语中,我们使用逗号作为小数分隔符,但是任何在金融领域工作过的人都会使用小键盘上的小数分隔符,而不是逗号。所以即使在fr-CA文化中,我仍然需要解析具有点作为小数分隔符的数字。


22
我不确定这是否是一个好主意。如果你不了解该文化背景,就无法可靠地解析double类型:在德国,double值可能包含“.”,但它们被视为千位分隔符。因此,在Legate的情况下,GetDouble("3.5")将在德语环境中返回35,在美国英语环境中返回3.5。 - Niki
不,Pierre Alain是正确的,因为它就是按照原样书写的。如果你的文化认为“点”是千位分隔符,那么“3.5”就被看作是“35”,这是可以的。然而,如果你的文化对“点”没有规定,那么该字符就会被解析为小数点,也是可以的。我本来会避免尝试enUS文化,但这是个人选择。 - xryl669
如果您在使用逗号作为小数点的文化环境中使用数字键盘,则点键将被设置为逗号。 - CrazyBaran
数字键盘的小数分隔符取决于键盘布局(而不是区域设置 - 至少在Windows 7上是这样)。我使用匈牙利语来编写文本、电子邮件等,而使用en-US来编写代码,因此小数点可以是点或逗号。我还使用自定义区域设置,将小数分隔符从“,”更改为“。”,将列表分隔符从“;”更改为“,”。你知道的,因为csv...祝我们所有人编写多元文化应用程序好运;) - Steven Spark
如果系统文化使用逗号作为分隔符,那么这段代码对于像100.35这样的数字无法正常工作。你本应该期望它返回大约100的数字,但实际上它返回的是10035。 - Lennart

37

我无法在评论区写下评论,所以我在这里写:

double.Parse("3.5", CultureInfo.InvariantCulture) 不是一个好主意,因为在加拿大我们使用 3,5 而不是 3.5,此函数会将其解析为35。

我在我的电脑上测试了两种方法:

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

这是正确的方式,正如Pierre-Alain Vigeant所提到的。

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
回复:“...因为在加拿大,我们写成3,5而不是3.5” 你确定吗?根据“小数点”的维基百科页面:“使用点"."作为小数点的国家包括......加拿大(使用英语时)”。这不更多是关于使用法语版本的Windows吗? - Peter Mortensen
可能是因为法语版本的原因,在蒙特利尔我们写作3,5而不是3.5。 - Malus Jan
1
那么根据你的逻辑,它们总是只有一个返回 true? - Emil
看看这个 - Malus Jan
仍然存在错误。对于输入字符串GetDouble("10,,,,,,,,0", 0.0),提到的函数返回100。 - Krivers
我想建议添加以下内容:如果 (string.IsNullOrEmpty(value)) { return defaultValue; }如果 (value.Count(f => f == ',' || f == '.') > 1) { return defaultValue; } - Krivers

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

在解析前将逗号替换为点号。 在具有逗号作为小数分隔符的国家非常有用。 考虑限制用户输入(如果需要)仅输入一个逗号或点号。


1
比那个获得133票的答案更正确...它允许在使用","或"."小数分隔符的两个系统上运行... - Badiboy
@Badiboy,你能解释一下这个答案有什么问题吗?据我所知,InvariantCulture始终将“.”作为小数分隔符。因此,它应该适用于两个系统。 - Alex P.
@Alex11223 你说得对。这就是为什么我说这个答案比更受欢迎的答案好。顺便说一句,如果你把“,”作为列表分隔符(即1,234,560.01),这段代码也会失败,但我不知道该如何解决。 :) - Badiboy
9
这不是一个好的答案,因为在某些CultureInfo中,逗号被用作千位分隔符并且可以使用。如果你用点替换逗号,那么就会出现多个点,解析将会失败:Double.Parse((12345.67).ToString("N", new CultureInfo("en")).Replace(',', '.'), CultureInfo.InvariantCulture)因为(12345.67).ToString("N", new CultureInfo("en")).Replace(',', '.')将被格式化为"12.345.67"。 - codingdave
1,234.56 -> 1.234.56 不用解析器。另一个想法是检查数字是否包含 '.' 和 ',',并将 ',' 替换为空字符串,如果只有 ',' 逗号,则将其替换为 '.'。 - GDocal

18

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

所以,首先你需要问问自己,这个数字是从哪里来的,你想要解析它。如果它来自.NET Framework中的输入,那么没有问题,因为它将与相同的格式相匹配。但也许它来自外部,也许来自外部服务器,也许来自仅支持字符串属性的旧数据库。在这种情况下,数据库管理员应该规定存储数字的格式。例如,如果您知道它将是一个带有美国格式的美国数据库,您可以使用以下代码:

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

这将在全球任何地方都能正常工作。请不要使用“Convert.ToXxxx”。 “Convert”类仅被视为在任何方向上进行转换的基础。此外:您也可以使用类似的机制处理日期时间。


同意!试图手动实现文化特性最终会导致您没有预料到的情况和巨大的头痛。让.NET适当地处理它。 - Khalos
3
当用户使用的小数分隔符与其文化设置中所认定的小数分隔符不同,这就是一个大问题。 - EdmundYeung99

15

诀窍在于使用不变的文化,以解析所有文化中的点。

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

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

2

在这个话题上,我想提供一个通用的双重转换方法:

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,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

没有默认转换方式,因此无法解析 1.3,141,3.14 或类似的情况。


2
"1,000" 作为一千的意思将会失败。 - Defkon1

1

我认为如果值来自用户输入,则不可能100%正确转换。例如,如果值为123.456,则它可以是分组或小数点。如果您确实需要100%的准确性,您必须描述您的格式,并在其不正确时引发异常。

但是,我改进了JanW的代码,使我们更接近100%。其背后的想法是,如果最后一个分隔符是组分隔符,则这更像是整数类型而不是double。

添加的代码在GetDouble的第一个if中。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接