将字符串转换为十进制数的最佳方法是什么,且不区分小数点 "." 和 ","?

25

该应用程序处理来自不同文化背景的表示十进制数的字符串。例如,"1.1"和"1,1"代表相同的值。

我尝试使用Decimal.TryParse标志组合,但无法获得想要的结果。最终,"1,1"变成了"11"或"0"。

是否有可能在一行代码中将这种字符串转换为十进制数,而不需要预先将","字符替换为".",或者玩弄NumberFormat.NumberDecimalSeparator呢?

您如何处理这种情况?

提前感谢!


5
你如何确定 1,234 的意思? - Kobi
1
这是介于1和2之间的浮点数值 :) “1.234”是相同的浮点值。 - Andrew Florko
1
赞同Kobi最先发表的观点。在法语中,1,234是一千二百三十四分之一,而在英语中是一千二百三十四。如果你不知道文化背景(如你在下面评论的),你就不能有意义地确定最初的意思。 - dlanod
1
Regex.Replace(value, @"(?<=\d)[.,](?=\d)", CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator) - JohnG79
8个回答

39

在解析时,您可以创建一个临时的CultureInfo对象来使用。

// get a temporary culture (clone) to modify
var ci = CultureInfo.InvariantCulture.Clone() as CultureInfo;
ci.NumberFormat.NumberDecimalSeparator = ",";
decimal number = decimal.Parse("1,1", ci); // 1.1

1
好的,更认真地说 - 你几乎永远不想在服务器上使用CurrentCulture - 因为你不知道它是如何设置的。 - Kobi
2
不过,如果使用InvariantCulture并覆盖NumberDecimalSeparator呢? - Andrew Florko
1
@Kobi:重点是获取一个可修改的CultureInfo对象实例,而不必任意选择一个。由于静态属性的实例是只读的,因此克隆是必要的。虽然我想克隆"InvariantCulture"可能是更好的选择。 - Jeff Mercado

13

我发现了另一种做法。它看起来很奇怪,但对我来说很有效。

所以如果您不了解目标系统的文化,并且不知道会得到哪个值,比如12.33或12,33,您可以按照以下步骤操作

string amount = "12.33";
// or i.e. string amount = "12,33";

var c = System.Threading.Thread.CurrentThread.CurrentCulture;
var s = c.NumberFormat.CurrencyDecimalSeparator;

amount = amount.Replace(",", s);
amount = amount.Replace(".", s);

decimal transactionAmount = Convert.ToDecimal(amount); 

3
字符串很危险,可能存在千位分隔符的问题?1.2345,40? - Evilripper

8

只需在调用Parse时设置正确的区域设置即可,如下所示:

string s = "11,20";

decimal c1 = decimal.Parse(s, new CultureInfo("fr-FR"));
decimal c2 = decimal.Parse(s, new CultureInfo("en-AU"));

Console.WriteLine(c1);
Console.WriteLine(c2);

1
但是我不知道原始文化 :( - Andrew Florko
3
你必须确保了解原始文化,否则你无法确定数字的含义。 - Noon Silk
1
如果值来自表单怎么办?在解析之前将 "," 替换为 "." ?(编辑:好的,虽然这是被禁止使用的问法,但仍然...) - Bertvan
1
@Bertvan:CultureInfo.CurrentCulture 提供了“你的窗体”(实际上是操作系统的)的默认 CultureInfo。 - Cheng Chen
1
@Danny Chen:好的,但我是一个使用英语操作系统(en-us - 小数点)的比利时(nl-be - 逗号小数)用户。虽然实际的CultureInfo已知,但你永远不知道用户会输入什么... - Bertvan

7
你有以下几种可能性:
  1. 你了解文化背景
    1. 使用当前安装在电脑上的文化设置
    2. 让用户决定设定他们自己的文化设置 -> 在你的程序中设置用户设置
  2. 你不知道文化背景
    1. 你必须做出决定:你需要定义和记录下你的决定
    2. 猜测:你尝试解析,不断地尝试,直到你得到有效的数字

4
以下是我的实现,有什么好的想法吗?
/// <summary>
/// 
/// </summary>
public static class NumberExtensions
{
    /// <summary>
    /// Convert string value to decimal ignore the culture.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <returns>Decimal value.</returns>
    public static decimal ToDecimal ( this string value )
    {
        decimal number;
        string tempValue = value;

        var punctuation = value.Where ( x => char.IsPunctuation ( x ) ).Distinct ( );
        int count = punctuation.Count ( );

        NumberFormatInfo format = CultureInfo.InvariantCulture.NumberFormat;
        switch ( count )
        {
            case 0:
                break;
            case 1:
                tempValue = value.Replace ( ",", "." );
                break;
            case 2:
                if ( punctuation.ElementAt ( 0 ) == '.' )
                    tempValue = value.SwapChar ( '.', ',' );
                break;
            default:
                throw new InvalidCastException ( );
        }

        number = decimal.Parse ( tempValue, format );
        return number;
    }
    /// <summary>
    /// Swaps the char.
    /// </summary>
    /// <param name="value">The value.</param>
    /// <param name="from">From.</param>
    /// <param name="to">To.</param>
    /// <returns></returns>
    public static string SwapChar ( this 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 ( );
    }
}

[TestClass]
public class NumberTest
{

    /// <summary>
    /// 
    /// </summary>
    [TestMethod]
    public void Convert_To_Decimal_Test ( )
    {
        string v1 = "123.4";
        string v2 = "123,4";
        string v3 = "1,234.5";
        string v4 = "1.234,5";
        string v5 = "123";
        string v6 = "1,234,567.89";
        string v7 = "1.234.567,89";

        decimal a1 = v1.ToDecimal ( );
        decimal a2 = v2.ToDecimal ( );
        decimal a3 = v3.ToDecimal ( );
        decimal a4 = v4.ToDecimal ( );
        decimal a5 = v5.ToDecimal ( );
        decimal a6 = v6.ToDecimal ( );
        decimal a7 = v7.ToDecimal ( );

        Assert.AreEqual ( ( decimal ) 123.4, a1 );
        Assert.AreEqual ( ( decimal ) 123.4, a2 );
        Assert.AreEqual ( ( decimal ) 1234.5, a3 );
        Assert.AreEqual ( ( decimal ) 1234.5, a4 );
        Assert.AreEqual ( ( decimal ) 123, a5 );
        Assert.AreEqual ( ( decimal ) 1234567.89, a6 );
        Assert.AreEqual ( ( decimal ) 1234567.89, a7 );
    }
    /// <summary>
    /// 
    /// </summary>
    [TestMethod]
    public void Swap_Char_Test ( )
    {
        string v6 = "1,234,567.89";
        string v7 = "1.234.567,89";

        string a1 = v6.SwapChar ( ',', '.' );
        string a2 = v7.SwapChar ( ',', '.' );

        Assert.AreEqual ( "1.234.567,89", a1 );
        Assert.AreEqual ( "1,234,567.89", a2 );
    }
}

3
很好,但仍然不是100%正确的。 当您使用case 1时,自动假定“,”表示小数位。 您应该至少检查它是否出现超过一次,因为在这种情况下它是组分隔符。
            case 1:
                var firstPunctuation = linq.ElementAt(0);
                var firstPunctuationOccurence = value.Where(x => x == firstPunctuation).Count();

                if (firstPunctuationOccurence == 1)
                {
                    // we assume it's a decimal separator (and not a group separator)
                    value = value.Replace(firstPunctuation.ToString(), format.NumberDecimalSeparator);
                }
                else
                {
                    // multiple occurence means that symbol is a group separator
                    value = value.Replace(firstPunctuation.ToString(), format.NumberGroupSeparator);
                }

                break;

1
那么如果逗号(,)只出现一次,但是它仍然是分组分隔符,该怎么办呢? 例如:“111,222”表示111222.0。 - Jiří Herník

1

使用TryParse两次,分别使用代表两种可能性的2个样式
如果只有一个返回值,请使用该值 如果两个都返回值,请使用绝对值较小的值。

如果您使用错误的样式,则TryParse将为同时使用分组和小数分隔符的数字返回0,但是如果您的字符串中没有分组分隔符(例如,数字小于1000),它将在两种情况下返回值,但“错误”的数字将更大(同样是绝对值)


0
更简单的方法是从带有逗号或点作为小数分隔符或逗号作为千位分隔符的字符串值中获取十进制值作为字符串。
public static string StringToDecimalString(string str)
 {
        if (str.TrimEnd(new char[0]).Length == 0)
     {
            return "0";
        }
        decimal num = 0;
        try
     {
            num = Convert.ToDecimal(str.Replace(".", ",").Replace(" ", ""));
        }
        catch
     {
            try
         {
                num = Convert.ToDecimal(str.Replace(",", "."));
            }
            catch
         {
                return "0";
            }
        }
        return num.ToString();
    }

感谢您对Stack Overflow社区做出贡献的兴趣。这个问题已经有了很多答案,其中一个答案已经得到社区的广泛验证。您确定您的方法之前没有被提到过吗?如果是这样的话,能否解释一下您的方法与众不同的地方,以及在什么情况下您的方法可能更好,或者为什么您认为之前的答案不够满意。您能否友好地编辑您的答案并提供解释? - undefined

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