Click here to Skip to main content
15,891,204 members
Articles / Programming Languages / C#

Integer Base Converter

Rate me:
Please Sign up or sign in to vote.
4.83/5 (16 votes)
13 Dec 2013CPOL7 min read 36.4K   869   16  
Encode numbers in different positional numeral systems.
// -----------------------------------------------------------------------
// <copyright file="IntegerBaseConverter.cs" company="ProXoft L.L.C.">
// </copyright>
// Author: Adam A. Zgagacz
// Web: http://www.proxoft.com
// -----------------------------------------------------------------------
namespace ProXoft.WinForms
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Numerics;

    /// <summary>
    /// <para>
    /// Static class containing helper methods to convert numbers from and to different 
    /// positional numeral systems. It also provides methods to compare two numbers written 
    /// in the same numeral systems.<br></br>
    /// Class methods accept integer values greater than two as the numeral system base (radix).
    /// </para>
    /// Class has predefined default digit set used to represent numbers, but most method accept parameter where user can 
    /// provide own set of digits.<br></br>
    /// Default digit set is defined by string <i>"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"</i>. Using default digit set
    /// numbers encoded with small bases (for example base-2 (binary), base-8 (octal), base-10 (decimal) or base-16 (hexadecimal)) look exactly as
    /// you would expect because default digit set starts with character '0' followed with '1' etc. 
    /// This represents 'natural' digit order we conventionally 
    /// write numbers.<br></br>
    /// However, since user can supply in most method calls own string representing digits used for encoding, numbers might look quite unfamiliar.
    /// Let's look at the following example:
    ///<code>
    /// //The following line converts value 12345 to radix-10 string representation using 0,1,2,3,4,5,6,7,8,9 digits
    /// string Radix10Number = Convert(12345, 10, "0123456789");   
    /// Radix10Number contains string "12345".
    /// //However the following line of code produces result "BCD78" which represents the same number but is using different digits.
    /// string Radix10Number = Convert(12345, 10, "ABCD789012");   
    /// </code>
    /// </summary>
    public static class IntegerBaseConverter
    {
        const string m_DefaultDigitSet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/";
        /// <summary>
        /// Read-only property showing default character set representing digits used to write the number
        /// in numeral system. 
        /// <para>Characters are ordered in ascending order:
        /// for example 1 is greater than 0 and a is greater B.</para>
        /// To write number in Base-n numeral system only first n characters form the string are used.
        /// For example: octal numbers (base-8) are written using only 0,1,2,3,4,5,6,7 characters (digits).
        /// while base 64 numbers can use any of the following digits: 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,+,/"
        /// </summary>
        public static  string DefaultDigitsSet
        {
            get { return IntegerBaseConverter.m_DefaultDigitSet; }
        } 
     
        /// <summary>
        /// Converts <c>BigInteger</c> to the string representing 
        /// this number written in radix-x numeral system (x is provided as the <c>radix</c> method parameter)
        /// </summary>
        /// <param name="value">Number to be written in <c>radix</c> numeral system.</param>
        /// <param name="radix">Numeral system base (radix).</param>
        /// <param name="digits">Character set representing ascending sequence of digits used
        /// in destination numeral system.</param> 
        /// <returns>String representing number written in <c>radix</c> base numeral system
        /// (where <c>radix</c> is a number between 2 and length of <c>digits</c> string.)
        /// </returns>
        public static string Convert(BigInteger value, int radix, string digits=m_DefaultDigitSet)
        {
            if (value == null)
                value = new BigInteger(0);

            if ((radix > digits.Length) || (radix < 2))
                throw new ArgumentOutOfRangeException("radix", radix, string.Format("Radix has to be within range <2, {0}>;", digits.Length));

            StringBuilder sb = new StringBuilder();
            do
            {
                BigInteger remainder;
                value = BigInteger.DivRem(value, radix, out remainder);

                sb.Insert(0, digits[(int)remainder]);
            } while (value > 0);

            return sb.ToString();
        }


         /// <summary>
        /// Verifies if <c>Number</c> is a valid number, by checking all characters used to represent number against set of valid characters (digits).
        /// </summary>
        /// <param name="number">String representing base encoded number.</param>
        /// <param name="digits">String containing all valid characters (digits), that can be used to represent a number.</param>
        /// <returns><c>true</c> if <c>Number</c> contains only valid characters (digits). Otherwise method returns <c>false</c>.</returns>
        public static bool IsValidNumber(string number, string digits=m_DefaultDigitSet)
        {
            foreach (char ch in number)
            {
                if (digits.IndexOf(ch) < 0)
                    return false;
            }
            return true;
        }
        
        /// <summary>
        /// Check if parameter <c>digits</c> can be used for case insensitive parsing. In order to be valid 
        /// for case insensitive parsing, all characters in the string have to be unique regardless of their case.
        /// </summary>
        /// <param name="digits">Contains sequence of digits to check.</param>
        /// <returns><b>true</b> if <c>digits</c> does not contain duplicate characters, otherwise returns <b>false</b>.</returns>
        public static bool CanUseCaseInsensitiveParsingMode(string digits)
        {
            //Return true if number of unique uppercased digits in set is equal size of digits string
            return new HashSet<char>(digits.ToUpper()).Count == digits.Length;
        }

        /// <summary>
        /// Check if parameter <c>digits</c> is a valid digit set string. In order to be valid 
        /// digit set all characters in the string have to be unique.
        /// </summary>
        /// <param name="digits">Contains sequence of digits to check.</param>
        /// <returns><b>true</b> if <c>digits</c> does not contain duplicate characters, otherwise returns <b>false</b>.</returns>
        public static bool IsValidDigitSet(string digits)
        {
            HashSet<char> hashedCharacters = new HashSet<char>();

            //Try to insert all characters into HashSet. Return false after first duplicate character is detected.
            foreach (char c in digits)
                if (!hashedCharacters.Add(c)) return false;

            return true;
        }

        /// <summary>
        /// Converts string representation of a number (written as <c>radix</c> base string) to its BigInteger equivalent.
        /// </summary>
        /// <param name="value">A string containing a number to convert.</param>
        /// <param name="radix">Numeral base used to write <c>value</c>.</param>
        /// <param name="digits">String containing ordered set of valid digits (characters) for <c>radix</c> </param>
        /// <param name="result">When this method returns, contains BigInteger value equivalent to then number contained in <c>value</c>, if the conversion
        /// succeeded, or zero if the conversion failed. The conversion fails if <c>value</c> parameter is <b>null</b> or is not in correct 
        /// format.</param>
        /// <param name="CaseSensitiveParsing">Flag indicating if parsing is case sensitive. Default value is <b>true</b>.
        /// Setting it to false might be convenient in some applications. For example if we are dealing for example with
        /// radix-16 numbers, and numbers are entered by the user as typed text, user can assume strings contained 
        /// lower cased characters are equivalent to uppercased versions. For example both string "AFC3" and "afc4"
        /// should be consumed by Parse method and both should return valid number.<br></br>
        /// <i>Note:</i>
        /// Setting <c>CaseSensitiveParsing</c>=<b>false</b> should be considered as the hint. It will be used only if
        /// <c>digits</c> can be used for case insensitive parsing (see <c>CanUseCaseInsensitiveParsingMode</c> property)
        /// </param>
        /// <returns>
        /// Return value is 
        /// <b>true</b> if <c>value</c> was converted successfully; otherwise <b>false</b>.</returns>
        public static bool TryParse(string value, int radix, out BigInteger result, string digits = m_DefaultDigitSet, bool CaseSensitiveParsing = true)
        {
            try
            {
                result = Parse(value, radix, digits, CaseSensitiveParsing);
                return true;
            }
            catch
            {
                result = BigInteger.Zero;
                return false;
            }
       }

        /// <summary>
        /// Converts string representation of a number (written as <c>radix</c> base string) to its BigInteger equivalent.
        /// </summary>
        /// <param name="value">A string containing a number to convert.</param>
        /// <param name="radix">Numeral base used to write <c>value</c>.</param>
        /// <param name="digits">String containing ordered set of valid digits (characters) for <c>radix</c> </param>
        /// <param name="CaseSensitiveParsing">Flag indicating if parsing is case sensitive. Default value is <b>true</b>.
        /// Setting it to false might be convenient in some applications. For example if we are dealing for example with
        /// radix-16 numbers, and numbers are entered by the user as typed text, user can assume strings contained 
        /// lower cased characters are equivalent to uppercased versions. For example both string "AFC3" and "afc4"
        /// should be consumed by Parse method and both should return valid number.<br></br>
        /// <i>Note:</i>
        /// Setting <c>CaseSensitiveParsing</c>=<b>false</b> should be considered as the hint. It will be used only if
        /// <c>digits</c> can be used for case insensitive parsing (see <c>CanUseCaseInsensitiveParsingMode</c> property)
        /// </param>
        /// <exception cref="ArgumentOutOfRangeException">Radix is less than 2 or more than number of available digits
        /// (length of <c>digits</c> string).</exception>
        /// <exception cref="ArgumentOutOfRangeException"><c>value</c> contains invalid characters (not present in <c>digits</c> string).</exception>
        /// <returns><c>BigInteger</c> value equivalent to to the number contained in <c>value</c>parameter.</returns>
        public static BigInteger Parse(string value, int radix,  string digits=m_DefaultDigitSet, bool CaseSensitiveParsing=true)
        {

            if (string.IsNullOrEmpty(value))
                value = digits[0].ToString();
 
            if ((radix > digits.Length) || (radix < 2))
                throw new ArgumentOutOfRangeException("radix", radix, string.Format("Radix has to be within range <2, {0}>;", digits.Length));
            
            digits = digits.Substring(0, radix);

            if (!IsValidDigitSet(digits))
                throw new ArgumentException("Invalid digit set; digit set cannot contain duplicate characters.", "digits");

            if (!CaseSensitiveParsing && CanUseCaseInsensitiveParsingMode(digits))
            {
                value = value.ToUpper();
                digits = digits.ToUpper();
            }
            
            BigInteger uBase = 1;
            char[] ValueArray = value.ToCharArray();
            BigInteger RetValue = 0;
            for (int i = value.Length - 1; i >= 0; i--)
            {
                int CharIdx = digits.IndexOf(value[i]);
                if ((CharIdx >= radix) || (CharIdx < 0))
                    throw new ArgumentOutOfRangeException("Value", digits[CharIdx], "Invalid character in the input string.");

                RetValue += CharIdx * uBase;
                uBase *= radix;
            }
            return  RetValue;
        }
 
        /// <summary>
        /// Compares two numbers represented as strings encoded using the same radix value.
         /// </summary>
        /// <param name="numberA">The first number to compare</param>
        /// <param name="numberB">The second number to compare</param>
        /// <param name="Digits">String containing sequence of characters 
        /// used as digits in encoded numbers. Digits are ordered in ascending order
        /// starting from character representing zero</param>
        /// <returns>
        /// if numberA is less than numberB returns negative number; <br></br>
        /// if numberA == numberB returns zero;<br></br>
        /// if numberA is greater than numberB returns positive number.
        /// </returns>
        public static int Compare(string numberA, string numberB, string Digits=m_DefaultDigitSet)
        {
            //pad the shortest string wit equivalent of zeros
            int LengthDiff = numberA.Length - numberB.Length;
            if (LengthDiff > 0)
                numberB = new string(Digits[0], LengthDiff) + numberB;
            else if (LengthDiff < 0)
                numberA = new string(Digits[0], -LengthDiff) + numberA;

            //now strings have equal length so we can start comparing one character at the time
            for (int i = 0; i < numberA.Length; i++)
            {
                int charCompare = Compare(numberA[i], numberB[i], Digits);
                if (charCompare != 0)
                    return charCompare;
            }
            return 0;
        }


        /// <summary>
        /// Compares two digits (characters). Order of digits is determined by order of characters in <c>digits</c> parameter.
        /// </summary>
        /// <param name="digitA">The first digit to compare.</param>
        /// <param name="digitB">The second digit to compare.</param>
        /// <param name="digits">Ordered set of characters representing valid digits.</param>
        /// <returns>
        /// if digitA is less than digitB returns negative number;<br></br>
        /// if digitA == digitB returns zero;<br></br>
        /// if digitA is greater than digitB returns positive number.
        /// </returns>
        /// <exception cref="ArgumentOutOfRangeException">Invalid character</exception>
        public static int Compare(char digitA, char digitB, string digits= m_DefaultDigitSet)
        {
            //find index of charAInvalid character
            int idxA = digits.IndexOf(digitA);
            if (idxA < 0) throw new ArgumentOutOfRangeException("digitA", digitA, "Invalid character.");
            int idxB = digits.IndexOf(digitB);
            if (idxB < 0) throw new ArgumentOutOfRangeException("digitB", digitB, "Invalid character.");

            return (idxA - idxB);
        }

        /// <summary>
        /// Converts BigInteger to the string representing 
        /// this number written in radix-x numeral system (x is provided as the <c>radix</c> method parameter). If string length
        /// is less that maximum string length for given <c>radix</c> and <c>type</c> combination it is left-padded with
        /// characters representing zero digit up to maximum allowed length."/>
        /// </summary>
        /// <param name="value">Number to be written in different numeral system.</param>
        /// <param name="radix">Numeral system base.</param>
        /// <param name="type">Allowed values are: <b>System.Byte</b>, <b>System.UInt16</b>, <b>System.UInt32</b> <b>System.UInt64</b>or <b>System.BigInteger</b></param>
        /// <param name="digits">Character set representing ascending sequence of digits used
        /// in destination numeral system.</param>
        /// <returns>String representing number written in <c>radix</c> base numeral system
        /// (where 'Base is a number between 2 and length of <c>digits</c> string.)
        /// </returns>
        /// <returns></returns>
        /// <exception cref="ArgumentException"><c>value</c> is too big for given <c>type</c>.</exception>
        public static string GetPaddedValue(BigInteger value, int radix, Type type, string digits= m_DefaultDigitSet)
        {
            return GetPaddedValue(value, radix, MaxValue(type), digits);
        }

        public static string  GetPaddedValue(byte[] value,  int radix, BigInteger maxValue, string digits = m_DefaultDigitSet)
        {
            //Convert value to big integer /assuming value is stored in array less than 8
            //Bug Fix: Oct 12, 2013: longVal and Multiplier were Int64 this caused overflow
            UInt64 longVal = 0;
            UInt64 multipler = 1;
            for (int i= value.Length-1; i>=0; i--)
            {
                longVal += value[i]* multipler;
                multipler=multipler <<8;
            }
             return GetPaddedValue(longVal, radix,maxValue, digits);
        }

        /// <summary>
        /// Converts BigInteger to the string representing 
        /// this number written in radix-x numeral system (x is provided as the <c>radix</c> method parameter). If string length
        /// is less that maximum string length for given <c>radix</c> and <c>type</c> combination it is left-padded with
        /// characters representing zero digit up to maximum allowed length."/>
        /// </summary>
        /// <param name="value">Number to be written in different numeral system.</param>
        /// <param name="radix">Numeral system base.</param>
        /// <param name="type">Allowed values are: <b>System.Byte</b>, <b>System.UInt16</b>, <b>System.UInt32</b> <b>System.UInt64</b>or <b>System.BigInteger</b></param>
        /// <param name="digits">Character set representing ascending sequence of digits used
        /// in destination numeral system.</param>
        /// <returns>String representing number written in <c>radix</c> base numeral system
        /// (where 'Base is a number between 2 and length of <c>digits</c> string.)
        /// </returns>
        /// <returns></returns>
        /// <exception cref="ArgumentException"><c>value</c> is too big for given <c>type</c>.</exception>
        public static string GetPaddedValue(BigInteger value, int radix, BigInteger maxValue, string digits = m_DefaultDigitSet)
        {
            //Find string representation of the number encoded with base radix
            string RetValue = Convert(value, radix, digits);
            //Find maximum value allowed for given 'type'
            string Maximum = Convert(maxValue, radix, digits);

            //If encoded value is too big for given 'type' throw an exception.
            if (IntegerBaseConverter.Compare(RetValue, Maximum) > 0)
                throw new ArgumentException(string.Format("Value {0} is too big for {1}.", value, maxValue), "Value");

            //Find how many zero digits have to added in front
            int MaxSize = Maximum.Length;
            int DiffSize = MaxSize - RetValue.Length;

            //If needed, insert appropriate number of zero digits.
            if (DiffSize > 0)
                RetValue = new string(digits[0], DiffSize) + RetValue;
            else if (DiffSize < 0)
                throw new Exception("Unexpected error: Error getting padded value.");  //This should never happen
            return RetValue;
        }
 
    /// <summary>
    /// Returns string representation of maximum value of the number of type <c>type</c>  encoded with base <c>radix</c>.
    /// </summary>
        /// <param name="type">Allowed values are: <b>System.Byte</b>, <b>System.UInt16</b>, <b>System.UInt32</b> or <b>System.BigInteger</b></param>
        /// <param name="radix">Numeral system base (radix).</param>
        /// <param name="digits">Character set representing ascending sequence of digits used
        /// in destination numeral system.</param>
        /// <returns>Returns string representation of maximum value of the number of type <c>type</c>  encoded with base <c>radix</c>.</returns>
        public static string MaxValue(Type type, int radix, string digits= m_DefaultDigitSet)
        {
            return Convert(MaxValue(type), radix, digits);
        }

        /// <summary>
        /// Returns <c>BigInteger</c> value representing maximum value allowed for <b>System.Type</b> provided as method parameter.
        /// </summary>
        /// <param name="type"><b>System.Type</b> object. Only following Types are allowed:<br></br>
        /// <b>System.Byte</b>, <b>System.UInt16</b>, <b>System.UInt32</b>, <b>System.UInt64</b> or <b>Syste.Numerics.BigInteger.</b>
        /// </param>
        /// <exception cref="ArgumentException">Invalid Type. Only System.Byte, System.UInt16, System.UInt64, Syste.Numerics.BigInteger are allowed</exception>
        /// <returns>Maximum value for <c>type</c> provided as parameter.</returns>
        /// <remarks>Since <c>BigInteger</c> does not have maximum value defined, theoretically does not have value limit.
        /// For purpose of this class artificial maximum value of BigInteger was set for 1 googol (1 followed with 100 zeros)</remarks>
        public static BigInteger MaxValue(Type type)
        {
            if (type == typeof(Byte))
                return Byte.MaxValue;
            else if (type == typeof(UInt16))
                return UInt16.MaxValue;
            else if (type == typeof(UInt32))
                return UInt32.MaxValue;
            else if (type == typeof(UInt64))
                return UInt64.MaxValue;
            else if (type == typeof(BigInteger))
                 return BigInteger.Parse("1" + new string('0', 100));
            else
                throw new ArgumentException("Invalid argument.", "type");
        }
    }

    #region SandCastle help utility requires existence of this empty class
    /* The following empty class exists only in order to provide SandCastle help utility
     * extract namespace summary.   
     */

    /// <summary>
    /// Collection of WinForms components and controls.
    /// </summary>
    [System.Runtime.CompilerServices.CompilerGenerated]
    class NamespaceDoc
    {

    }
    #endregion

}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United States United States
Software Developer for many years. Still enjoying it a lot. I started with Assembly coding for PDP-11, then had phases of Fortran, Pascal, C, C++, VisualBasic (1 -6), some Web Development (ASP) and finally I landed in C# world (since very beginning of C# life span).

Comments and Discussions