views:

1192

answers:

11

I want to separate the digits of an integer, say 12345, into an array of bytes {1,2,3,4,5}, but I want the most performance effective way to do that, because my program does that millions of times.

Any suggestions? Thanks.

+6  A: 

'Will' vs 'Does'? I'm a huge fan of optimizing code after it's wrote, profiled, and it's been determined to be the bottleneck.

Obi
Why the downvote? +! premature optimisation is the root of all evil
Daniel Elliott
Your answer is a non-answer. How do you know he didn't write and profile the code, and that he does know this to be the the bottleneck.
cdiggins
@cdiggins: Yes, thanks, this is the bottleneck of my program's logic.
Moayad Mardini
He said my code will, not does. He inferred future tense, and yes as stated. Premature optimization is the root of all evil.
Obi
+19  A: 

How about:

public static int[] ConvertToArrayOfDigits(int value)
{
    int size = DetermineDigitCount(value);
    int[] digits = new int[size];
    for (int index = size - 1; index >= 0; index--)
    {
        digits[index] = value % 10;
        value = value / 10;
    }
    return digits;
}

private static int DetermineDigitCount(int x)
{
    // This bit could be optimised with a binary search
    return x < 10 ? 1
         : x < 100 ? 2
         : x < 1000 ? 3
         : x < 10000 ? 4
         : x < 100000 ? 5
         : x < 1000000 ? 6
         : x < 10000000 ? 7
         : x < 100000000 ? 8
         : x < 1000000000 ? 9
         : 10;
}

Note that this won't cope with negative numbers... do you need it to?

EDIT: Here's a version which memoizes the results for under 10000, as suggested by Eric. If you can absolutely guarantee that you won't change the contents of the returned array, you could remove the Clone call. It also has the handy property of reducing the number of checks to determine the length of "large" numbers - and small numbers will only go through that code once anyway :)

private static readonly int[][] memoizedResults = new int[10000][];

public static int[] ConvertToArrayOfDigits(int value)
{
    if (value < 10000)
    {
        int[] memoized = memoizedResults[value];
        if (memoized == null) {
            memoized = ConvertSmall(value);
            memoizedResults[value] = memoized;
        }
        return (int[]) memoized.Clone();
    }
    // We know that value >= 10000
    int size = value < 100000 ? 5
         : value < 1000000 ? 6
         : value < 10000000 ? 7
         : value < 100000000 ? 8
         : value < 1000000000 ? 9
         : 10;

    return ConvertWithSize(value, size);
}

private static int[] ConvertSmall(int value)
{
    // We know that value < 10000
    int size = value < 10 ? 1
             : value < 100 ? 2
             : value < 1000 ? 3 : 4;
    return ConvertWithSize(value, size);
}

private static int[] ConvertWithSize(int value, int size)
{
    int[] digits = new int[size];
    for (int index = size - 1; index >= 0; index--)
    {
        digits[index] = value % 10;
        value = value / 10;
    }
    return digits;
}

Note that this doesn't try to be thread-safe at the moment. You may need to add a memory barrier to make sure that the write to the memoized results isn't visible until the writes within the individual result are visible. I've given up trying to reason about these things unless I absolutely have to. I'm sure you can make it lock-free with effort, but you should really get someone very smart to do so if you really need to.

EDIT: I've just realised that the "large" case can make use of the "small" case - split the large number into two small ones and use the memoised results. I'll give that a go after dinner and write a benchmark...

EDIT: Okay, ready for a giant amount of code? I realised that for uniformly random numbers at least, you'll get "big" numbers much more often than small ones - so you need to optimise for that. Of course, that might not be the case for real data, but anyway... it means I now do my size tests in the opposite order, hoping for big numbers first.

I've got a benchmark for the original code, the simple memoization, and then the extremely-unrolled code.

Results (in ms):

Simple: 3168
SimpleMemo: 3061
UnrolledMemo: 1204

Code:

using System;
using System.Diagnostics;

class DigitSplitting
{
    static void Main()        
    {
        Test(Simple);
        Test(SimpleMemo);
        Test(UnrolledMemo);
    }

    const int Iterations = 10000000;

    static void Test(Func<int, int[]> candidate)
    {
        Random rng = new Random(0);
        Stopwatch sw = Stopwatch.StartNew();
        for (int i = 0; i < Iterations; i++)
        {
            candidate(rng.Next());
        }
        sw.Stop();
        Console.WriteLine("{0}: {1}",
            candidate.Method.Name, (int) sw.ElapsedMilliseconds);            
    }

    #region Simple
    static int[] Simple(int value)
    {
        int size = DetermineDigitCount(value);
        int[] digits = new int[size];
        for (int index = size - 1; index >= 0; index--)
        {
            digits[index] = value % 10;
            value = value / 10;
        }
        return digits;
    }

    private static int DetermineDigitCount(int x)
    {
        // This bit could be optimised with a binary search
        return x < 10 ? 1
             : x < 100 ? 2
             : x < 1000 ? 3
             : x < 10000 ? 4
             : x < 100000 ? 5
             : x < 1000000 ? 6
             : x < 10000000 ? 7
             : x < 100000000 ? 8
             : x < 1000000000 ? 9
             : 10;
    }
    #endregion Simple    

    #region SimpleMemo
    private static readonly int[][] memoizedResults = new int[10000][];

    public static int[] SimpleMemo(int value)
    {
        if (value < 10000)
        {
            int[] memoized = memoizedResults[value];
            if (memoized == null) {
                memoized = ConvertSmall(value);
                memoizedResults[value] = memoized;
            }
            return (int[]) memoized.Clone();
        }
        // We know that value >= 10000
        int size = value >= 1000000000 ? 10
                 : value >= 100000000 ? 9
                 : value >= 10000000 ? 8
                 : value >= 1000000 ? 7
                 : value >= 100000 ? 6
                 : 5;

        return ConvertWithSize(value, size);
    }

    private static int[] ConvertSmall(int value)
    {
        // We know that value < 10000
        return value >= 1000 ? new[] { value / 1000, (value / 100) % 10,
                                           (value / 10) % 10, value % 10 }
              : value >= 100 ? new[] { value / 100, (value / 10) % 10, 
                                         value % 10 }
              : value >= 10 ? new[] { value / 10, value % 10 }
              : new int[] { value };
    }

    private static int[] ConvertWithSize(int value, int size)
    {
        int[] digits = new int[size];
        for (int index = size - 1; index >= 0; index--)
        {
            digits[index] = value % 10;
            value = value / 10;
        }
        return digits;
    }
    #endregion

    #region UnrolledMemo
    private static readonly int[][] memoizedResults2 = new int[10000][];
    private static readonly int[][] memoizedResults3 = new int[10000][];
    static int[] UnrolledMemo(int value)
    {
        if (value < 10000)
        {
            return (int[]) UnclonedConvertSmall(value).Clone();
        }
        if (value >= 1000000000)
        {
            int[] ret = new int[10];
            int firstChunk = value / 100000000;
            ret[0] = firstChunk / 10;
            ret[1] = firstChunk % 10;
            value -= firstChunk * 100000000;
            int[] secondChunk = ConvertSize4(value / 10000);
            int[] thirdChunk = ConvertSize4(value % 10000);
            ret[2] = secondChunk[0];
            ret[3] = secondChunk[1];
            ret[4] = secondChunk[2];
            ret[5] = secondChunk[3];
            ret[6] = thirdChunk[0];
            ret[7] = thirdChunk[1];
            ret[8] = thirdChunk[2];
            ret[9] = thirdChunk[3];
            return ret;
        } 
        else if (value >= 100000000)
        {
            int[] ret = new int[9];
            int firstChunk = value / 100000000;
            ret[0] = firstChunk;
            value -= firstChunk * 100000000;
            int[] secondChunk = ConvertSize4(value / 10000);
            int[] thirdChunk = ConvertSize4(value % 10000);
            ret[1] = secondChunk[0];
            ret[2] = secondChunk[1];
            ret[3] = secondChunk[2];
            ret[4] = secondChunk[3];
            ret[5] = thirdChunk[0];
            ret[6] = thirdChunk[1];
            ret[7] = thirdChunk[2];
            ret[8] = thirdChunk[3];
            return ret;
        }
        else if (value >= 10000000)
        {
            int[] ret = new int[8];
            int[] firstChunk = ConvertSize4(value / 10000);
            int[] secondChunk = ConvertSize4(value % 10000);
            ret[0] = firstChunk[0];
            ret[1] = firstChunk[0];
            ret[2] = firstChunk[0];
            ret[3] = firstChunk[0];
            ret[4] = secondChunk[0];
            ret[5] = secondChunk[1];
            ret[6] = secondChunk[2];
            ret[7] = secondChunk[3];
            return ret;
        }
        else if (value >= 1000000)
        {
            int[] ret = new int[7];
            int[] firstChunk = ConvertSize4(value / 10000);
            int[] secondChunk = ConvertSize4(value % 10000);
            ret[0] = firstChunk[1];
            ret[1] = firstChunk[2];
            ret[2] = firstChunk[3];
            ret[3] = secondChunk[0];
            ret[4] = secondChunk[1];
            ret[5] = secondChunk[2];
            ret[6] = secondChunk[3];
            return ret;
        }
        else if (value >= 100000)
        {
            int[] ret = new int[6];
            int[] firstChunk = ConvertSize4(value / 10000);
            int[] secondChunk = ConvertSize4(value % 10000);
            ret[0] = firstChunk[2];
            ret[1] = firstChunk[3];
            ret[2] = secondChunk[0];
            ret[3] = secondChunk[1];
            ret[4] = secondChunk[2];
            ret[5] = secondChunk[3];
            return ret;
        }
        else
        {
            int[] ret = new int[5];
            int[] chunk = ConvertSize4(value % 10000);
            ret[0] = value / 10000;
            ret[1] = chunk[0];
            ret[2] = chunk[1];
            ret[3] = chunk[2];
            ret[4] = chunk[3];
            return ret;
        }
    }

    private static int[] UnclonedConvertSmall(int value)
    {
        int[] ret = memoizedResults2[value];
        if (ret == null)
        {
            ret = value >= 1000 ? new[] { value / 1000, (value / 100) % 10,
                                           (value / 10) % 10, value % 10 }
              : value >= 100 ? new[] { value / 100, (value / 10) % 10, 
                                         value % 10 }
              : value >= 10 ? new[] { value / 10, value % 10 }
              : new int[] { value };
            memoizedResults2[value] = ret;
        }
        return ret;
    }

    private static int[] ConvertSize4(int value)
    {
        int[] ret = memoizedResults3[value];
        if (ret == null)
        {
            ret = new[] { value / 1000, (value / 100) % 10,
                         (value / 10) % 10, value % 10 };
            memoizedResults3[value] = ret;
        }
        return ret;
    }
    #endregion UnrolledMemo
}
Jon Skeet
Why not write `for (int i=1; i <= size; i++)`? :)
Thorarin
@Thorarin: Good idea, but there's a better one :)
Jon Skeet
You could, on average, save one compare in DetermineDigitCount if you did a binary search on x, i.e. return x < 100000 ? (x < 1000 ? ...) : (x < 10000000 ? .. ). That might be a bit obfuscated though.
Skizz
@Skizz: Yeah, I wondered about that. Couldn't be bothered to get it right though :)
Jon Skeet
Actually, I'm not certain it's "better". In theory it should be, but the `for (int i = 0; i < size; i++)` is optimized by the JIT compiler to be faster than `for (int i = size - 1; i >= 0; i--)`. Kinda stupid, I know :( I was mostly going for readability.
Thorarin
I believe in using Math.Log10
Johan Kullbom
Jon's DetermineDigitCount is about three times as fast as Math.Log10 on my machine. (Including the cast from and to double.) Might be pretty relevant.
Joren
Oh, that was an unoptimized build even. Optimized it's closer to nine times.
Joren
Yeah, I did something similar to this myself once. Log10 was not an option :)
Thorarin
How is allocating an array using "new" each time fast?
cdiggins
Allocation in the CLR is only a pointer increment if it doesn't trigger a garbage collection.
Joren
You could potentially make this faster on average by memoizing the result.
Eric Lippert
@cdiggins: Within the constraint of the question, it's hard not to allocate a new array each time (due to mutation). @Eric: You can only memoize the result completely if you know that the caller won't mutate the array. I suppose you could memoize a copy of the array and then clone it... maybe for i < 1000 or something similar.
Jon Skeet
@Jon: I'm (perversely) fond of `for (index = size --index >= 0; )...`, but actually, do you think it would make sense to unroll the loop and thus maybe avoid some indexing?
Mike Dunlavey
@Mike: Possibly. Mind you, the optimisation I'm now considering would special case each "large" size anyway... Hmmm.
Jon Skeet
A small mistake, Jon: you use `x` where you meant `value` a few times.
Joren
I had thought of splitting the number in two halves and implemented it, and it is indeed faster than your current solution.
Joren
First solution had "hard to beat" performance (get rid of function call to improve it). 2nd solution... too many typos and Clone() will kill most of performance gain.
DK
I wonder how printing to a string and getting the character array performs?
Joel Coehoorn
Or a state machine that operates via bit shifting?
Joel Coehoorn
Going through string is about twice as slow as Jon's second approach.
Joren
Perfect answer! A huge 'thank you' :)
Moayad Mardini
+7  A: 

Millions of times isn't that much.

// input: int num >= 0
List<byte> digits = new List<byte>();
while (num > 0)
{
   byte digit = (byte) (num % 10);
   digits.Insert(0, digit);  // Insert to preserve order
   num = num / 10;
}

// if you really want it as an array
byte[] bytedata = digits.ToArray();

Note that this could be changed to cope with negative numbers if you change byte to sbyte and test for num != 0.

Henk Holterman
Several expensive operations here: allocate a list, insert into a list, convert to an array. It could be sped up by an order of at least a hundred.
cdiggins
It'd be way better just to start filling up an array of 10 items in reverse order and then copying the digits to a new array later.
Joren
@Commenters: quite right, I wasn't optimizing very much.
Henk Holterman
+5  A: 

convert integer to string and then use String.Chars[]

DmitryK
I don't believe this is the fastest way of doing that.
Moayad Mardini
A kitten dies every time someone uses strings for doing simple arithmetic.
Joren
"Hardware is Cheap, Programmers are Expensive."
iik
+2  A: 

If you can get by with leading zeros it is much easier.

    void Test()
    { 
        // Note: 10 is the maximum number of digits.
        int[] xs = new int[10];
        System.Random r = new System.Random();
        for (int i=0; i < 10000000; ++i)
            Convert(xs, r.Next(int.MaxValue));
    }

    // Notice, I don't allocate and return an array each time.
    public void Convert(int[] digits, int val)
    {
        for (int i = 0; i < 10; ++i)
        {
            digits[10 - i - 1] = val % 10;
            val /= 10;
        }
    }

EDIT: Here is a faster version. On my computer it tested faster than two of Jon Skeet's algorithms, except for his memoized version:

static void Convert(int[] digits, int val)
{
  digits[9] = val % 10; val /= 10;
  digits[8] = val % 10; val /= 10;
  digits[7] = val % 10; val /= 10;
  digits[6] = val % 10; val /= 10;
  digits[5] = val % 10; val /= 10;
  digits[4] = val % 10; val /= 10;
  digits[3] = val % 10; val /= 10;
  digits[2] = val % 10; val /= 10;
  digits[1] = val % 10; val /= 10;
  digits[0] = val % 10; val /= 10;     
}
cdiggins
It's of course easy to get rid of the zeroes by copying the relevant digits to a new array before returning. Okay, it iterates over the array again, but for an array that's 10 entries long I really don't see a problem with that.
Joren
The extra iteration is exactly corret. The only probably we would run into is millions of array allocations. Faster to pre-allocate 10 static arrays, and choose the correct one afterwards.
cdiggins
+6  A: 

1 + Math.Log10(num) will give the number of digits without any searching/looping:

public static byte[] Digits(int num)
{
    int nDigits = 1 + Convert.ToInt32(Math.Floor(Math.Log10(num)));
    byte[] digits = new byte[nDigits];
    int index = nDigits - 1;
    while (num > 0) {
        byte digit = (byte) (num % 10);
        digits[index] = digit;
        num = num / 10;
        index = index - 1;
    }
    return digits;
}

Edit: Possibly prettier:

public static byte[] Digits(int num)
{
    int nDigits = 1 + Convert.ToInt32(Math.Floor(Math.Log10(num)));
    byte[] digits = new byte[nDigits];

    for(int i = nDigits - 1; i != 0; i--)
    {
        digits[i] = (byte)(num % 10);
        num = num / 10;
    }
    return digits;
}
Johan Kullbom
+3  A: 

A little loop unrolling perhaps?

int num = 147483647;
int nDigits = 1 + Convert.ToInt32(Math.Floor(Math.Log10(num)));
byte[] array = new byte[10] {
            (byte)(num / 1000000000 % 10),
            (byte)(num / 100000000 % 10),
            (byte)(num / 10000000 % 10),
            (byte)(num / 1000000 % 10),
            (byte)(num / 100000 % 10),
            (byte)(num / 10000 % 10),
            (byte)(num / 1000 % 10),
            (byte)(num / 100 % 10),
            (byte)(num / 10 % 10),
            (byte)(num % 10)};
byte[] digits;// = new byte[nDigits];
digits = array.Skip(array.Length-nDigits).ToArray();

Thanks above for the Log10 thingy.. ;)

There's been some talk of benchmarking...

I've fully unrolled the loops, and compared with the accepted memoized variant of Jons, and I get a consistently quicker time with this:-

    static int[] ConvertToArrayOfDigits_unrolled(int num)
    {
        if (num < 10)
        {
            return new int[1] 
            {
                (num % 10) 
            };
        }
        else if (num < 100)
        {
            return new int[2] 
            {
                (num / 10 % 10),
                (num % 10)
            };
        }
        else if (num < 1000)
        {
            return new int[3] {
            (num / 100 % 10),
            (num / 10 % 10),
            (num % 10)};
        }
        else if (num < 10000)
        {
            return new int[4] {
            (num / 1000 % 10),
            (num / 100 % 10),
            (num / 10 % 10),
            (num % 10)};
        }
        else if (num < 100000)
        {
            return new int[5] {
            (num / 10000 % 10),
            (num / 1000 % 10),
            (num / 100 % 10),
            (num / 10 % 10),
            (num % 10)};
        }
        else if (num < 1000000)
        {
            return new int[6] {
            (num / 100000 % 10),
            (num / 10000 % 10),
            (num / 1000 % 10),
            (num / 100 % 10),
            (num / 10 % 10),
            (num % 10)};
        }
        else if (num < 10000000)
        {
            return new int[7] {
            (num / 1000000 % 10),
            (num / 100000 % 10),
            (num / 10000 % 10),
            (num / 1000 % 10),
            (num / 100 % 10),
            (num / 10 % 10),
            (num % 10)};
        }
        else if (num < 100000000)
        {
            return new int[8] {
            (num / 10000000 % 10),
            (num / 1000000 % 10),
            (num / 100000 % 10),
            (num / 10000 % 10),
            (num / 1000 % 10),
            (num / 100 % 10),
            (num / 10 % 10),
            (num % 10)};
        }
        else if (num < 1000000000)
        {
            return new int[9] {
            (num / 100000000 % 10),
            (num / 10000000 % 10),
            (num / 1000000 % 10),
            (num / 100000 % 10),
            (num / 10000 % 10),
            (num / 1000 % 10),
            (num / 100 % 10),
            (num / 10 % 10),
            (num % 10)};
        }
        else
        {
            return new int[10] {
            (num / 1000000000 % 10),
            (num / 100000000 % 10),
            (num / 10000000 % 10),
            (num / 1000000 % 10),
            (num / 100000 % 10),
            (num / 10000 % 10),
            (num / 1000 % 10),
            (num / 100 % 10),
            (num / 10 % 10),
            (num % 10)};
        }
    }

It may be I've messed up somewhere - I don't have much time for fun and games, but I was timing this as 20% quicker.

JonB
That does a lot of redundant work for small numbers though... it will still calculate the values, even if you're skipping them.
Jon Skeet
Indeed Jon, on average this isn't faster than your solution.
Joren
The redundant work is all maths though, it saves a loop condition and a set of assignments, the compiler would likely wind up with the same code though.@Joren Is that from a benchmark, or just estimation?
JonB
I benchmarked it for the first hundred million integers or so, giving about a 5:3 speed ratio in favor of Jon's code. I think your code has a good chance of being faster if you don't do any arithmetic for digits that don't exist. Hard to be sure without testing.
Joren
Fully unrolled I can save the array re-sizing and it gains quite a lot. I benchmark this as 20% faster than Jon Skeets 2 accepted answers.
JonB
+1  A: 

divide and mod tend to be slow operations. I wanted to find out if a solution using multiply and subtraction would be faster and it seems to be (on my computer):

    public static void ConvertToArrayOfDigits2(int value, int[] digits)
 {
  double v = value;
  double vby10 = v * .1;

  for (int index = digits.Length - 1; index >= 0; index--)
  {
   int ivby10 = (int)vby10;
   digits[index] = (int)(v)- ivby10* 10;
   v = ivby10;
   vby10 = ivby10 * .1;
  }   
 }

I am passing in an array instead of allocating it every time to take the memory allocator and length out of the equation. This version will produce leading zeros if the array is longer than the number. Compared to a similarly converted version of Jon's example:

    public static void ConvertToArrayOfDigits(int value, int[] digits){

  for (int index = digits.Length - 1; index >= 0; index--)    { 
   digits[index] = value % 10;    
   value = value / 10;  
  }   
 }

the no divide/mod version took about 50 more time to generate all the arrays up to a given number. I also tried using floats and it was only about 5-10% slower (the double version was quicker than the float version).

Just because it was bothering me, here is an unrolled version which is just slightly faster again:

     public static void ConvertToArrayOfDigits3(int value, int[] digits)
 {
  double v = value;
  double vby10 = v * .1;
  int ivby10;

  switch(digits.Length -1){
   default:
    throw new ArgumentOutOfRangeException();
   case 10:
    ivby10 = (int)vby10;
    digits[10] = (int)(v) - ivby10 * 10;
    v = ivby10;
    vby10 = ivby10 * .1;
    goto case 9;
   case 9:
    ivby10 = (int)vby10;
    digits[9] = (int)(v) - ivby10 * 10;
    v = ivby10;
    vby10 = ivby10 * .1;
    goto case 8;
   case 8:
    ivby10 = (int)vby10;
    digits[8] = (int)(v) - ivby10 * 10;
    v = ivby10;
    vby10 = ivby10 * .1;
    goto case 7;
   case 7:
    ivby10 = (int)vby10;
    digits[7] = (int)(v) - ivby10 * 10;
    v = ivby10;
    vby10 = ivby10 * .1;
    goto case 6;
   case 6:
    ivby10 = (int)vby10;
    digits[6] = (int)(v) - ivby10 * 10;
    v = ivby10;
    vby10 = ivby10 * .1;
    goto case 5;
   case 5:
    ivby10 = (int)vby10;
    digits[5] = (int)(v) - ivby10 * 10;
    v = ivby10;
    vby10 = ivby10 * .1;
    goto case 4;
   case 4:
    ivby10 = (int)vby10;
    digits[4] = (int)(v) - ivby10 * 10;
    v = ivby10;
    vby10 = ivby10 * .1;
    goto case 3;
   case 3:
    ivby10 = (int)vby10;
    digits[3] = (int)(v) - ivby10 * 10;
    v = ivby10;
    vby10 = ivby10 * .1;
    goto case 2;
   case 2:
    ivby10 = (int)vby10;
    digits[2] = (int)(v) - ivby10 * 10;
    v = ivby10;
    vby10 = ivby10 * .1;
    goto case 1;
   case 1:
    ivby10 = (int)vby10;
    digits[1] = (int)(v) - ivby10 * 10;
    v = ivby10;
    vby10 = ivby10 * .1;
    goto case 0;
   case 0:
    ivby10 = (int)vby10;
    digits[0] = (int)(v) - ivby10 * 10;
    break;
  }

 }
Dolphin
+3  A: 

Just for fun, here's a way to separate all the digits using just one C# statement. It works this way: the regular expression uses the string version of the number, splits apart its digits into a string array, and finally the outer ConvertAll method creates an int array from the string array.

    int num = 1234567890;

    int [] arrDigits = Array.ConvertAll<string, int>(
        System.Text.RegularExpressions.Regex.Split(num.ToString(), @"(?!^)(?!$)"),
        str => int.Parse(str)
        );

    // resulting array is [1,2,3,4,5,6,7,8,9,0]

Efficiency-wise?... I'm unsure compared to some of the other fast answers I see here. Somebody would have to test it.

John K
The nice thing about a regular expression being present is you can modify the pattern to exclude special numeric-related symbols like . $ E, etc if you wanted only the digits extracted before being split.
John K
Ewww... strings?
Charlie Somerville
A: 

I haven't benchmarked this or anything, but I think this would be the simplest answer. Correct me if I'm wrong.

    Dim num As Integer = 147483647
    Dim nDigits As Integer = 1 + Convert.ToInt32(Math.Floor(Math.Log10(num)))
    Dim result(nDigits - 1) As Integer

    For a As Integer = 1 To nDigits
        result(a - 1) = Int(num / (10 ^ (nDigits - a))) Mod 10
    Next

** EDIT **

Revised the function because exponents seem to be very expensive.

Private Function Calc(ByVal num As Integer) As Integer()
    Dim nDigits As Int64 = 1 + Convert.ToInt64(Math.Floor(Math.Log10(num)))
    Dim result(nDigits - 1) As Integer
    Dim place As Integer = 1

    For a As Integer = 1 To nDigits
        result(nDigits - a) = Int(num / place) Mod 10
        place = place * 10
    Next

    Return result
End Function

This benchmarks to around 775k/sec (for numbers 9 digits or less). Drop the maximum digits to 7 and it benches at 885k/s. 5 digits at 1.1m/s.

sentry07
I'm returning from 400k to 500k random integers (from 1 to 9 digits) a second.
sentry07
+1  A: 

The allocation of a new int[] every time takes up a significant amount of the time according to my testing. If you know these values will be used once and thrown away before the next call, you could instead reuse a static array for a significant speed improvement:

    private static readonly int[] _buffer = new int[10];
    public static int[] ConvertToArrayOfDigits(int value)
    {
        for (int index = 9; index >= 0; index--)
        {
            _buffer[index] = value % 10;
            value = value / 10;
        }
        return _buffer;
    }

to keep the code small, I am returning trailing zero's for smaller numbers, but this could easily be changed by using 9 different static arrays instead (or an array of arrays).

Alternatively, 2 seperate ConvertToArrayOfDigits methods could be provided, one taking a precreated int array as an extra parameter, and one without that which creates the resulting buffer prior to calling the first method.

    public static void ConvertToArrayOfDigits(int value, int[] digits) { ... }
    public static int[] ConvertToArrayOfDigits(int value)
    {
        int size = DetermineDigitCount(value);
        int[] digits = new int[size];
        ConvertToArrayOfDigits(value, digits);
        return digits;
    }

This way, it would be up to the caller to potentially create a static reusable buffer if their usecase allows for it.

Luke Hutteman