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
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.
However, above mentioned method doesn't use 3rd parameter hi byte in new decimal
so it won't work with larger numbers.
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?
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.
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.
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:
input: 123.456789
00:00:02.7292447
00:00:00.6043730
input: 999999999999999123.456789
00:00:05.3094786
00:00:01.9702198
input: 1.0
00:00:01.4212123
00:00:00.2378833
input: 0
00:00:01.1083770
00:00:00.1899732
input: -3.3333333333333333333333333333333
00:00:06.2043707
00:00:02.0373628
If input consists only of 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
. 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.
The first two parameters: lo
and mid
in the decimal constructor use 4 bytes each. 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. This would provide you with the extra four bytes needed to utilize the hi
parameter.
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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.