Login


Converting Between Integers and Strings Using Any Base

By Jonathan Wood on 4/11/2011
Language: C#
Technology: .NET
Platform: Windows
License: CPOL
Views: 14,810
General Programming » Text Handling » General » Converting Between Integers and Strings Using Any Base

Sample Project Screenshot

Download Source Code Download Source Code

Introduction

The .NET Framework provides the Convert class. This class converts between a wide array of different data types. One thing this class can do is convert between integers and strings using different bases. However, inexplicably, the supported bases are limited to 2 (binary), 8 (octal), 10 (decimal), and 16 (hexadecimal).

To be sure, these are the most common bases and will suffice in most cases. But it is possible to write these conversion routines so that they support any base, so this restriction seems somewhat arbitrary.

At any rate, it sounded like a nice little project to write my own conversion routine that would support any base.

The ConvertEx Class

Listing 1 shows my static ConvertEx class. This class performs a subset of the functionality provided by the Convert class. However, my class supports any base.

The foundation of this class is the ToString() and TryParse() methods. ToString() converts an integer to a string and allows you to specify both the base and the character set. Specifying the character set means you are not forced into using the digits, 0, 1, 2, 3, etc. for your numbering system. This becomes more important as your base becomes larger and larger. It also opens up a few interesting possibilities.

The ToString() method includes three overloads. Two variations wrap the core method. ToString(int value, Bases radix) allows you to specify the base as a value from the Bases enum. And ToString(int value, int radix) allows you to specify the base as an integer value. Both methods use the built-in Base64 character set, which is suitable for bases up to 64 and contains the characters commonly used for base-64 encoding.

If you want to override the character set, just call the core version, which has a signature of ToString(int value, int radix, string digits). Each character in the digits argument corresponds to the value of its position, starting from 0.

The TryParse() method also has several overloads. In addition, there are several overloads of the Parse() wrapper method, which returns the result directly and throws an exception if the input can't be converted. This mirrors the functionality of a number of conversion routines within the .NET Framework.

Like the ToString() variations, the TryParse() and Parse() methods support specifying the radix using the Bases enum or an integer. And you can also override the character set.

I should point out one item here. The TryParse() method (and methods that call it) will convert the input to upper case if the radix is less than or equal to 36. This makes the routine more flexible because it correctly handles both upper and lower case letters. Using the default character set (the one used for base-64 encoding), 36 covers all the digits and upper case letters. For bases greater than 36, both upper and lower case characters are included and converting the input to upper case would cause the conversion to fail. This should work fine with the default character set; however, if you supply your own character set, this could cause problems. When supplying your own character set, you may need to change the line that converts the input to upper case.

Listing 1: The ConvertEx Class

/// <summary>
/// Class that provides enhanced functionality for converting between
/// integers and strings using any base.
/// </summary>
static class ConvertEx
{
    /// <summary>
    /// Predefined conversion bases.
    /// </summary>
    public enum Bases
    {
        Binary = 2,
        Octal = 8,
        Decimal = 10,
        Hexadecimal = 16,
        Base36 = 36,
        Base64 = 64
    }

    // Characters for bases up to 64
    public const string Base64 =
        "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/";

    /// <summary>
    /// Converts the given valie to a string using the given radix.
    /// </summary>
    /// <param name="value">Value to convert</param>
    /// <param name="radix">Radix</param>
    /// <returns>The resulting string</returns>
    public static string ToString(int value, Bases radix)
    {
        return ToString(value, (int)radix, Base64);
    }

    /// <summary>
    /// Converts the given valie to a string using the given radix.
    /// </summary>
    /// <param name="value">Value to convert</param>
    /// <param name="radix">Radix</param>
    /// <returns>The resulting string</returns>
    public static string ToString(int value, int radix)
    {
        return ToString(value, radix, Base64);
    }

    /// <summary>
    /// Converts the given valie to a string using the given radix.
    /// </summary>
    /// <param name="value">Value to convert</param>
    /// <param name="radix">Radix</param>
    /// <param name="digits">String of legal digits</param>
    /// <returns>The resulting string</returns>
    public static string ToString(int value, int radix, string digits)
    {
        // Validate radix
        if (radix < 2 || radix > digits.Length)
            throw new ArgumentException();

        // Convert value to characters
        StringBuilder builder = new StringBuilder();
        do
        {
            builder.Insert(0, digits[value % radix]);
            value = value / radix;
        }
        while (value > 0);

        // Return result
        return builder.ToString();
    }

    /// <summary>
    /// Parses the specified string to an integer.
    /// </summary>
    /// <param name="s">String to parse</param>
    /// <param name="radix">Radix</param>
    /// <returns>The parsed integer value</returns>
    public static int Parse(string s, Bases radix)
    {
        return Parse(s, (int)radix, Base64);
    }

    /// <summary>
    /// Parses the specified string to an integer.
    /// </summary>
    /// <param name="s">String to parse</param>
    /// <param name="radix">Radix</param>
    /// <returns>The parsed integer value</returns>
    public static int Parse(string s, int radix)
    {
        return Parse(s, radix, Base64);
    }

    /// <summary>
    /// Parses the specified string to an integer.
    /// </summary>
    /// <param name="s">String to parse</param>
    /// <param name="radix">Radix</param>
    /// <param name="digits">String of legal digits</param>
    /// <returns>The parsed integer value</returns>
    public static int Parse(string s, int radix, string digits)
    {
        int result;
        if (!TryParse(s, radix, digits, out result))
            throw new ArgumentException();
        return result;
    }

    /// <summary>
    /// Attempts to parse the specified string to an integer.
    /// </summary>
    /// <param name="s">String to parse</param>
    /// <param name="radix">Radix</param>
    /// <param name="result">Returns the parsed integer value</param>
    /// <returns>True if string was successfully parsed</returns>
    public static bool TryParse(string s, Bases radix, out int result)
    {
        return TryParse(s, (int)radix, Base64, out result);
    }

    /// <summary>
    /// Attempts to parse the specified string to an integer.
    /// </summary>
    /// <param name="s">String to parse</param>
    /// <param name="radix">Radix</param>
    /// <param name="result">Returns the parsed integer value</param>
    /// <returns>True if string was successfully parsed</returns>
    public static bool TryParse(string s, int radix, out int result)
    {
        return TryParse(s, radix, Base64, out result);
    }

    /// <summary>
    /// Attempts to parse the specified string to an integer.
    /// </summary>
    /// <param name="s">String to parse</param>
    /// <param name="radix">Radix</param>
    /// <param name="digits">String of legal digits</param>
    /// <param name="result">Returns the parsed integer value</param>
    /// <returns>True if string was successfully parsed</returns>
    public static bool TryParse(string s, int radix, string digits, out int result)
    {
        // Validate radix
        if (radix < 2 || radix > digits.Length)
            throw new ArgumentException();

        // Normalize input
        s = s.Trim();

        // If selected radix does not utilize both upper and lower case letters,
        // then convert to upper case so that parsing is not case sensitive
        if (radix <= 36)
            s = s.ToUpper();

        int pos = 0;
        result = 0;

        // Use lookup table to parse string
        while (pos < s.Length && !Char.IsWhiteSpace(s[pos]))
        {
            string digit = s.Substring(pos, 1);
            int i = digits.IndexOf(digit);
            if (i >= 0 && i < radix)
            {
                result *= radix;
                result += i;
                pos++;
            }
            else
            {
                // Invalid character encountered
                return false;
            }
        }
        // Return true if any characters processed
        return (pos > 0);
    }
}

Conclusion

Since all these methods are static, using the code is a simple matter of calling the desired method. The attached download includes a project that prompts you to enter a base, and then lists some sequential values using that base.

As I stated previously, in the vast majority of cases the original Convert class will probably meet your needs. It is rare to need arbitrary bases and character sets. Nontheless, I find the existing limitations to be quite arbitrary and here's a ready class if you should need to work around them.

End-User License

Use of this article and any related source code or other files is governed by the terms and conditions of The Code Project Open License.

Author Information

Jonathan Wood

I'm a software/website developer working out of the greater Salt Lake City area in Utah. I've developed many websites including Black Belt Coder, Insider Articles, and others.