Running with Code Like with scissors, only more dangerous

17Jan/120

A very fast, random text string generator in C#, Part One

Posted by Rob Paveza

This is about half of a type I designed a few years ago in response to my boss's assertion that I couldn't improve on the speed of random string generators. This is a pretty handy type to have around when you want to generate a character string of a fixed length. It so happens that 6 characters can fit a "base 36" number very well into just over 231 bits (it's just a little bit bigger than int.MaxValue and so it stores the value as a uint).

What is included below is a first step. I want to show how to refactor it for improved performance and where certain design scenarios might be better; for example, repeatedly calling the CreateNew method results in repeatedly creating new RNGCryptoServiceProviders. Although I believe this class is a bit better about seeds as it incorporates hardware-dependent data, it isn't necessarily cryptographically strong. You can add some strength by passing in a single RNG to each subsequent call, for example.

This sample also only goes one way: from number to string. Next time, we'll show the reverse operation.

Note: an important inclusion:

using System.Security.Cryptography;
    /// <summary>
    /// Represents a number that may be transitioned to a six-digit alphanumeric (base-36) representation.  This type is not CLS-compliant.
    /// </summary>
    [CLSCompliant(false)]
    public struct AlphaNumericNumber
    {
        private const uint MAX_VALUE = 2176782336; // = RADIX ^ 6
        private const int RADIX = 36; // 10 digits + 26 alphabetics
        private uint m_val;

        /// <summary>
        /// Creates a new <see>AlphaNumericNumber</see> with the specified value.
        /// </summary>
        /// <param name="value">The value with which to assign the number.</param>
        public AlphaNumericNumber(uint value)
        {
            if (value > MAX_VALUE)
                value %= MAX_VALUE;
            m_val = value;
        }

        /// <summary>
        /// Creates a new, random <see>AlphaNumericNumber</see>.
        /// </summary>
        /// <returns>A randomly-chosen <see>AlphaNumericNumber</see>.</returns>
        public static AlphaNumericNumber CreateNew()
        {
            byte[] container = new byte[4];
            using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
            {
                rng.GetNonZeroBytes(container);
            }

            return new AlphaNumericNumber(BitConverter.ToUInt32(container, 0));
        }

        /// <inheritdoc />
        public override string ToString()
        {
            char[] result = new char[6];
            uint msd = RADIX * RADIX * RADIX * RADIX * RADIX;

            uint currentDigit = msd;
            uint accumulator = m_val;
            unchecked
            {
                for (int charIndex = 0; charIndex < result.Length; charIndex++)
                {
                    uint currentDigitValue = accumulator / currentDigit;
                    accumulator -= (currentDigitValue * currentDigit);
                    if (currentDigitValue <= 9)
                    {
                        result[charIndex] = (char)('0' + currentDigitValue);
                    }
                    else
                    {
                        currentDigitValue -= 10; // adjust for 'A' equaling 10.
                        result[charIndex] = (char)('A' + currentDigitValue);
                    }

                    currentDigit /= RADIX;
                }
            }

            return new string(result);
        }

        /// <summary>
        /// Gets the value contained by this <see>AlphaNumericNumber</see>.
        /// </summary>
        public uint Value
        {
            get { return m_val; }
        }
    }