9

Is there a faster way to convert a string to double than Convert.ToDouble?

I have monitored System.Convert.ToDouble(string) calls and its degrading my app performance.

Convert.ToDouble("1.34515");

Perfomance screenshot

WORKING ANSWER FROM Jeffrey Sax :

static decimal[] decimalPowersOf10 = { 1m, 10m, 100m, 1000m, 10000m, 100000m, 1000000m }; 
static decimal CustomParseDecimal(string input) { 
    long n = 0; 
    int decimalPosition = input.Length; 
    for (int k = 0; k < input.Length; k++) { 
        char c = input[k]; 
        if (c == '.') 
            decimalPosition = k + 1; 
        else 
            n = (n * 10) + (int)(c - '0'); 
    } 
    return n / decimalPowersOf10[input.Length - decimalPosition]; 

}

After Jeffrey Sax CustomParser

Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
Steven Muhr
  • 3,339
  • 28
  • 46
  • 3
    well, the obvious thing to do would be to *avoid* the conversion being required in the first place (e.g. keep the data as a double in the first place). That being said, I'm also concerned that something is dealing with "Price", but using doubles (i.e. Decimal should be preferred for such numbers) – Damien_The_Unbeliever Dec 10 '11 at 16:25
  • 1
    Does the conversion sometimes fail and throw an exception? Exceptions are expensive(But you still can throw a few thousand of them per second). And how often are you calling `ToDouble`? There is no way that 2 calls to `ToDouble` take 5ms. Can you give us the code? And to measure performance execute it *often* and calculate the average. – CodesInChaos Dec 10 '11 at 16:41
  • I agree with @Damien , that monetary values should be stored in `decimal`, not double, but that doesn't increase performance. – CodesInChaos Dec 10 '11 at 16:43
  • A quick test showed that my comp can do about 5 million parsings of double per second on a single core. So how much data do you parse for that to be a problem? – CodesInChaos Dec 10 '11 at 16:50
  • @CodeInChaos: No exception is throw. Debug->Exceptions:All checked.I am simply converting a String to Double and it cost me 50% of my Method execution time in which I am parsing a string of 1200 characters – Steven Muhr Dec 10 '11 at 16:55
  • 2
    Please post a relevant subset of the code. Also 50% of how much total time is relevant too. If your code doesn't do anything expensive, 50% isn't much. – CodesInChaos Dec 10 '11 at 16:56
  • That's true ;) The only point I could optimise was the Convert.ToDouble. So if there is no alternative, I stay with Double.Parse which seems to be a little bit faster. Thx – Steven Muhr Dec 10 '11 at 17:09
  • I'm still trying to understand why `Convert.ToDouble` is taking 5 ms on your system, when on mine it takes 0.0003 ms. I'm fairly certain that my 2.0 GHz machine isn't 10,000 times faster than yours. – Jim Mischel Dec 10 '11 at 18:27
  • @JimMischel: This delay should come from the profiler. – Steven Muhr Dec 10 '11 at 19:04

7 Answers7

9

You can save about 10% by calling Double.TryParse with specific cached instances of NumberStyles and IFormatProvider (i.e. CultureInfo):

var style = System.Globalization.NumberStyles.AllowDecimalPoint;
var culture = System.Globalization.CultureInfo.InvariantCulture;
double.TryParse("1.34515", style, culture, out x);

Both Convert.ToDouble and Double.Parse or Double.TryParse have to assume the input can be in any format. If you know for certain that your input has a specific format, you can write a custom parser that performs much better.

Here's one that converts to decimal. Conversion to double is similar.

static decimal CustomParseDecimal(string input) {
    long n = 0;
    int decimalPosition = input.Length;
    for (int k = 0; k < input.Length; 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, false, (byte)(input.Length - decimalPosition));
}

My benchmarks show this to be about 5 times faster than the original for decimal, and up to 12 times if you use ints.

Jeffrey Sax
  • 10,253
  • 3
  • 29
  • 40
  • Awesome ! Thanks, it really improve the parsing performance ! – Steven Muhr Dec 10 '11 at 18:09
  • @StevenMuhr: Just curious, what improved parsing performance? Saving the culture and style? Or writing your own custom parser? – Jim Mischel Dec 10 '11 at 18:25
  • @JimMischel: I am using the CustomParseDecimal. It really improve my perf! – Steven Muhr Dec 10 '11 at 18:33
  • 6
    Note that this custom parser has a few limitations: 1) It allows fewer significant digits, but 64 bit should still be enough for most cases 2) It doesn't support a sign 3) It has no error handling whatsoever. – CodesInChaos Dec 10 '11 at 20:49
  • Your function is perfect but if you cache input.Length into a variable and use it instead then it significantly sppeds up. – LukAss741 May 30 '16 at 18:54
3

I'm unable to reproduce this. This code tests the speed of Convert.ToDouble.

        int numTests = 10000;
        double sum = 0;
        var sw = Stopwatch.StartNew();
        for (int i = 0; i < numTests; ++i)
        {
            var d = Convert.ToDouble("1.23456");
            sum += d;
        }
        sw.Stop();
        Console.WriteLine("{0} tests @ {1} ms. Avg of {2:N4} ms each", numTests,
           sw.ElapsedMilliseconds, (double)sw.ElapsedMilliseconds/numTests);
        Console.WriteLine("sum = {0}", sum);

With 10,000 calls, I get

10000 tests @ 3 ms. Avg of 0.0003 ms each
sum = 12345.6000000021

That's in release mode, running without the debugger attached.

It's highly unlikely that the problem is with Convert.ToDouble.

Jim Mischel
  • 131,090
  • 20
  • 188
  • 351
  • 1
    I've also checked that the call is not optimized away by the JITter. I've created an array of random numeric strings and used them as arguments. I've achieved a similar time. – Pavel Gatilov Dec 10 '11 at 18:02
2

You can call double.Parse("1.34515"); which is what Convert.ToDouble wraps.

It may be quicker to call double.TryParse which will avoid the exception overhead.

Aaron McIver
  • 24,527
  • 5
  • 59
  • 88
1

Function in this post Faster alternative to decimal.Parse is based on Jeffrey Sax's code. It adds support for negative numbers, optimizes performance by caching input.Length into a variable and works also for larger numbers.

Community
  • 1
  • 1
LukAss741
  • 771
  • 1
  • 7
  • 24
1

You could try decreasing the number of calls to Thread.CurrentCulture by using the Double.Parse(String, NumberStyles, IFormatProvider) overload. Although I doubt it would make a significant difference.

It may occur that parsing to another type: float or decimal may win a couple percent.

Kind of a mad idea, but... You could cache the NumberFormatInfo instance and use reflection to call the internal System.Number.ParseDouble directly. This would decrease the number of calls to NumberFormatInfo.GetInstance(), but to be honest, I'd expect reflection to be much slower.

The only option left (except for avoiding parsing) is using some custom parsing method. For example, if you define a strict format for the numbers (e.g. #.####), you could probably end up with a faster, yet less flexible and/or safe implementation. But taking into account that built-in parsing is half-native, I doubt you'll win.

UPDATE

I've analyzed the .NET code a bit more and found out that NumberFormatInfo is a IFormatProvider. So it seems like the fastest code should be:

IFormatProvider _CachedProvider = NumberFormatInfo.CurrentInfo;
var value1 = double.Parse(str1, NumberStyles.Number, _CachedProvider);
var value2 = double.Parse(str2, NumberStyles.Number, _CachedProvider);

This code should decrease the time spent for parsing preparation as much as it's possible. If you parse a lot of string pairse, you could also extract the IFormatProvider caching to an external code that (possibly) runs a loop and win another couple of milliseconds.

Pavel Gatilov
  • 7,579
  • 1
  • 27
  • 42
0

double.Parse() should be a little bit faster.

Albin Sunnanbo
  • 46,430
  • 8
  • 69
  • 108
  • 1
    @StevenMuhr, I find that really strange, since Convert.ToDouble uses double.Parse internally. – Albin Sunnanbo Dec 10 '11 at 16:39
  • I have rerunned some tests with Parse, I have 5ms. The difference between Convert.ToDouble & Double.Parse is really small unfortunately – Steven Muhr Dec 10 '11 at 16:44
  • double.Parse() is faster. See http://stackoverflow.com/questions/586436/double-tryparse-or-convert-todouble-which-is-faster-and-safer?lq=1 . – Max Beikirch Jul 04 '13 at 07:32
0

If you are 100% sure about your source data format and range, you can use:

string num = "1.34515";
int len = num.Length - num.IndexOf('.') - 1;
int intval = Int32.Parse(num.Replace(".", ""));
double d = (double)intval / PowersOf10[len]; // PowersOf10 is pre-computed or inlined

It worked ~50% faster than Double.Parse for me, but I wouldn't use it in any serious applications - it's extremely limited compared to proper parsing and I can't think of process where you need to parse millions of doubles and a few milliseconds would make the difference.

MagnatLU
  • 5,967
  • 1
  • 22
  • 17