C# Decimal.Parse issue with commas

2019-01-17 11:18发布

Here's my problem (for en-US):

Decimal.Parse("1,2,3,4") returns 1234, instead of throwing an InvalidFormatException.

Most Windows applications (Excel en-US) do not drop the thousand separators and do not consider that value a decimal number. The same issue happens for other languages (although with different characters).

Are there any other decimal parsing libraries out there that solve this issue?

Thanks!

4条回答
我想做一个坏孩纸
2楼-- · 2019-01-17 11:27

I ended up having to write the code to verify the currency manually. Personally, for a framework that prides itself for having all the globalization stuff built in, it's amazing .NET doesn't have anything to handle this.

My solution is below. It works for all the locales in the framework. It doesn't support Negative numbers, as Orion pointed out below, though. What do you guys think?

    public static bool TryParseCurrency(string value, out decimal result)
    {
        result = 0;
        const int maxCount = 100;
        if (String.IsNullOrEmpty(value))
            return false;

        const string decimalNumberPattern = @"^\-?[0-9]{{1,{4}}}(\{0}[0-9]{{{2}}})*(\{0}[0-9]{{{3}}})*(\{1}[0-9]+)*$";

        NumberFormatInfo format = CultureInfo.CurrentCulture.NumberFormat;

        int secondaryGroupSize = format.CurrencyGroupSizes.Length > 1
                ? format.CurrencyGroupSizes[1]
                : format.CurrencyGroupSizes[0];

        var r = new Regex(String.Format(decimalNumberPattern
                                       , format.CurrencyGroupSeparator==" " ? "s" : format.CurrencyGroupSeparator
                                       , format.CurrencyDecimalSeparator
                                       , secondaryGroupSize
                                       , format.CurrencyGroupSizes[0]
                                       , maxCount), RegexOptions.Compiled | RegexOptions.CultureInvariant);
        return !r.IsMatch(value.Trim()) ? false : Decimal.TryParse(value, NumberStyles.Any, CultureInfo.CurrentCulture, out result);
    }

And here's one test to show it working (nUnit):

    [Test]
    public void TestCurrencyStrictParsingInAllLocales()
    {
        var originalCulture = CultureInfo.CurrentCulture;
        var cultures = CultureInfo.GetCultures(CultureTypes.SpecificCultures);
        const decimal originalNumber = 12345678.98m;
        foreach(var culture in cultures)
        {
            var stringValue = originalNumber.ToCurrencyWithoutSymbolFormat();
            decimal resultNumber = 0;
            Assert.IsTrue(DecimalUtils.TryParseCurrency(stringValue, out resultNumber));
            Assert.AreEqual(originalNumber, resultNumber);
        }
        System.Threading.Thread.CurrentThread.CurrentCulture = originalCulture;

    }
查看更多
来,给爷笑一个
3楼-- · 2019-01-17 11:37

It is a common issue never solved by microsoft. So, I don't understand why 1,2,3.00 (english culture for example) is valid! You need to build an algorith to examine group size and return false/exception(like a failed double.parse) if the test is not passed. I had a similar problem in a mvc application, which build in validator doesn't accept thousands..so i've overwrite it with a custom, using double/decimal/float.parse, but adding a logic to validate group size.

If you want read my solution (it is used for my mvc custom validator, but you can use it to have a better double/decimal/float.parse generic validator) go here https://stackoverflow.com/a/41916721/3930528

查看更多
家丑人穷心不美
4楼-- · 2019-01-17 11:47

You might be able to do this in a two-phase process. First you could verify the thousands separator using the information in the CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator and CultureInfo.CurrentCulture.NumberFormat.NumberGroupSizes throwing an exception if it doesn't pass and then pass the number into the Decimal.Parse();

查看更多
beautiful°
5楼-- · 2019-01-17 11:51

It's allowing thousands, because the default NumberStyles value used by Decimal.Parse (NumberStyles.Number) includes NumberStyles.AllowThousands.

If you want to disallow the thousands separators, you can just remove that flag, like this:

Decimal.Parse("1,2,3,4", NumberStyles.Number ^ NumberStyles.AllowThousands)

(the above code will throw an InvalidFormatException, which is what you want, right?)

查看更多
登录 后发表回答