简体   繁体   English

十进制的更快替代方法

[英]Faster alternative to decimal.Parse

I noticed decimal.Parse(number, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture) is about 100% slower than custom decimal parse method based on Jeffrey Sax's code from Faster alternative to Convert.ToDouble 我注意到decimal.Parse(number, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture)比基于Jeffrey Sax的自定义十进制解析方法慢大约100%,该方法来自于Faster替代Convert.ToDouble

public static decimal ParseDecimal(string input) {
    bool negative = false;
    long n = 0;

    int len = input.Length;
    int decimalPosition = len;

    if (len != 0) {
        int start = 0;
        if (input[0] == '-') {
            negative = true;
            start = 1;
        }

        for (int k = start; k < len; k++) {
            char c = input[k];

            if (c == '.') {
                decimalPosition = k +1;
            } else {
                n = (n *10) +(int)(c -'0');
            }
        }
    }

    return new decimal(((int)n), ((int)(n >> 32)), 0, negative, (byte)(len -decimalPosition));
}

I assume that is because native decimal.Parse is designed to struggle with number style and culture info. 我认为这是因为本地decimal.Parse设计用于处理数字样式和区域性信息。

However, above mentioned method doesn't use 3rd parameter hi byte in new decimal so it won't work with larger numbers. 但是,上述方法未在new decimal使用第3个参数hi字节,因此不适用于较大的数字。

Is there a faster alternative to decimal.Parse to convert string that consists only of numbers and decimal dot to decimal which would work with large numbers? 是否有一个更快的替代decimal.Parse转换字符串仅由数字组成的和十进制点到小数点这将有大量的工作?

EDIT: Benchmark: 编辑:基准:

var style = System.Globalization.NumberStyles.AllowDecimalPoint;
var culture = System.Globalization.CultureInfo.InvariantCulture;
System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();
s.Reset();
s.Start();
for (int i=0; i<10000000; i++)
{
    decimal.Parse("20000.0011223344556", style, culture);
}
s.Stop();
Console.WriteLine(s.Elapsed.ToString());

s.Reset();
s.Start();
for (int i=0; i<10000000; i++)
{
    ParseDecimal("20000.0011223344556");
}
s.Stop();
Console.WriteLine(s.Elapsed.ToString());

output: 输出:

00:00:04.2313728
00:00:01.4464048

Custom ParseDecimal is in this case significantly faster than decimal.Parse. 在这种情况下,自定义ParseDecimal的速度明显快于October.Parse。

Thanks for all your comments which gave me a little more insight. 感谢您的所有评论,这给了我更多的见解。 Finally I did it as follows. 最后我做了如下。 If input is too long then it separates input string and parses first part using long and the rest with int which is still faster than decimal.Parse. 如果输入太长,则它会分离输入字符串,并使用long解析第一部分,而使用int解析其余部分,而int仍比十进制快。

This is my final production code: 这是我的最终生产代码:

public static int[] powof10 = new int[10]
{
    1,
    10,
    100,
    1000,
    10000,
    100000,
    1000000,
    10000000,
    100000000,
    1000000000
};
public static decimal ParseDecimal(string input)
{
    int len = input.Length;
    if (len != 0)
    {
        bool negative = false;
        long n = 0;
        int start = 0;
        if (input[0] == '-')
        {
            negative = true;
            start = 1;
        }
        if (len <= 19)
        {
            int decpos = len;
            for (int k = start; k < len; k++)
            {
                char c = input[k];
                if (c == '.')
                {
                    decpos = k +1;
                }else{
                    n = (n *10) +(int)(c -'0');
                }
            }
            return new decimal((int)n, (int)(n >> 32), 0, negative, (byte)(len -decpos));
        }else{
            if (len > 28)
            {
                len = 28;
            }
            int decpos = len;
            for (int k = start; k < 19; k++)
            {
                char c = input[k];
                if (c == '.')
                {
                    decpos = k +1;
                }else{
                    n = (n *10) +(int)(c -'0');
                }
            }
            int n2 = 0;
            bool secondhalfdec = false; 
            for (int k = 19; k < len; k++)
            {
                char c = input[k];
                if (c == '.')
                {
                    decpos = k +1;
                    secondhalfdec = true;
                }else{
                    n2 = (n2 *10) +(int)(c -'0');
                }
            }
            byte decimalPosition = (byte)(len -decpos);
            return new decimal((int)n, (int)(n >> 32), 0, negative, decimalPosition) *powof10[len -(!secondhalfdec ? 19 : 20)] +new decimal(n2, 0, 0, negative, decimalPosition);
        }
    }
    return 0;
}

benchmark code: 基准代码:

const string input = "[inputs are below]";
var style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowLeadingSign;
var culture = System.Globalization.CultureInfo.InvariantCulture;
System.Diagnostics.Stopwatch s = new System.Diagnostics.Stopwatch();
s.Reset();
s.Start();
for (int i=0; i<10000000; i++)
{
    decimal.Parse(input, style, culture);
}
s.Stop();
Console.WriteLine(s.Elapsed.ToString());

s.Reset();
s.Start();
for (int i=0; i<10000000; i++)
{
    ParseDecimal(input);
}
s.Stop();
Console.WriteLine(s.Elapsed.ToString());

results on my i7 920: 我的i7 920上的结果:

input: 123.456789 输入:123.456789

00:00:02.7292447
00:00:00.6043730

input: 999999999999999123.456789 输入:999999999999999123.456789

00:00:05.3094786
00:00:01.9702198

input: 1.0 输入:1.0

00:00:01.4212123
00:00:00.2378833

input: 0 输入:0

00:00:01.1083770
00:00:00.1899732

input: -3.3333333333333333333333333333333 输入:-3.3333333333333333333333333333333333

00:00:06.2043707
00:00:02.0373628

If input consists only of 0-9, . 如果输入仅包含0-9,则。 and optionally - at the begining then this custom function is significantly faster for parsing string to decimal. 并且可以选择-在开始时,此自定义函数将字符串解析为十进制的速度明显更快。

Sax's method is fast for two reasons. 萨克斯的方法之所以快速,有两个原因。 The first, you already know. 首先,您已经知道。 The second, is because it is able to take advantage of the very efficient 8-byte long data type for n . 第二,是因为它是能够采取用于非常有效的8字节长的数据类型的优点n Understanding this method's use of the long, can also explain why (unfortunately) it is not currently possible to use a similar method for very large numbers. 了解此方法对long的用法,也可以解释为什么(不幸的)为什么目前无法对非常大的数使用类似的方法。

The first two parameters: lo and mid in the decimal constructor use 4 bytes each. 前两个参数:十进制构造函数中的lomid各自使用4个字节。 Together this is the same amount of memory as the long. 总的来说,这就是长内存量。 This means there is no space left to keep going once you hit the max value for a long. 这意味着一旦长时间达到最大值,就没有继续前进的空间了。

To utilize a similar method you would need a 12 byte data type in place of the long. 要使用类似的方法,您将需要一个12字节的数据类型来代替long类型。 This would provide you with the extra four bytes needed to utilize the hi parameter. 这将为您提供利用hi参数所需的额外四个字节。

Sax's method is very clever, but until someone writes a 12 byte data type, you are just going to have to rely on decimal.Parse. Sax的方法非常聪明,但是除非有人编写12字节的数据类型,否则您将只需要依赖小数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM