Quickest way to convert a base 10 number to any base in .NET?

后端 未结 12 1227
予麋鹿
予麋鹿 2020-11-22 04:07

I have and old(ish) C# method I wrote that takes a number and converts it to any base:

string ConvertToBase(int number, char[] baseChars);

12条回答
  •  一向
    一向 (楼主)
    2020-11-22 04:31

    Very late to the party on this one, but I wrote the following helper class recently for a project at work. It was designed to convert short strings into numbers and back again (a simplistic perfect hash function), however it will also perform number conversion between arbitrary bases. The Base10ToString method implementation answers the question that was originally posted.

    The shouldSupportRoundTripping flag passed to the class constructor is needed to prevent the loss of leading digits from the number string during conversion to base-10 and back again (crucial, given my requirements!). Most of the time the loss of leading 0s from the number string probably won't be an issue.

    Anyway, here's the code:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace StackOverflow
    {
        /// 
        /// Contains methods used to convert numbers between base-10 and another numbering system.
        /// 
        /// 
        /// 
        /// This conversion class makes use of a set of characters that represent the digits used by the target
        /// numbering system. For example, binary would use the digits 0 and 1, whereas hex would use the digits
        /// 0 through 9 plus A through F. The digits do not have to be numerals.
        /// 
        /// 
        /// The first digit in the sequence has special significance. If the number passed to the
        ///  method has leading digits that match the first digit, then those leading
        /// digits will effectively be 'lost' during conversion. Much of the time this won't matter. For example,
        /// "0F" hex will be converted to 15 decimal, but when converted back to hex it will become simply "F",
        /// losing the leading "0". However, if the set of digits was A through Z, and the number "ABC" was
        /// converted to base-10 and back again, then the leading "A" would be lost. The 
        /// flag passed to the constructor allows 'round-tripping' behaviour to be supported, which will prevent
        /// leading digits from being lost during conversion.
        /// 
        /// 
        /// Note that numeric overflow is probable when using longer strings and larger digit sets.
        /// 
        /// 
        public class Base10Converter
        {
            const char NullDigit = '\0';
    
            public Base10Converter(string digits, bool shouldSupportRoundTripping = false)
                : this(digits.ToCharArray(), shouldSupportRoundTripping)
            {
            }
    
            public Base10Converter(IEnumerable digits, bool shouldSupportRoundTripping = false)
            {
                if (digits == null)
                {
                    throw new ArgumentNullException("digits");
                }
    
                if (digits.Count() == 0)
                {
                    throw new ArgumentException(
                        message: "The sequence is empty.",
                        paramName: "digits"
                        );
                }
    
                if (!digits.Distinct().SequenceEqual(digits))
                {
                    throw new ArgumentException(
                        message: "There are duplicate characters in the sequence.",
                        paramName: "digits"
                        );
                }
    
                if (shouldSupportRoundTripping)
                {
                    digits = (new[] { NullDigit }).Concat(digits);
                }
    
                _digitToIndexMap =
                    digits
                    .Select((digit, index) => new { digit, index })
                    .ToDictionary(keySelector: x => x.digit, elementSelector: x => x.index);
    
                _radix = _digitToIndexMap.Count;
    
                _indexToDigitMap =
                    _digitToIndexMap
                    .ToDictionary(keySelector: x => x.Value, elementSelector: x => x.Key);
            }
    
            readonly Dictionary _digitToIndexMap;
            readonly Dictionary _indexToDigitMap;
            readonly int _radix;
    
            public long StringToBase10(string number)
            {
                Func selector =
                    (c, i) =>
                    {
                        int power = number.Length - i - 1;
    
                        int digitIndex;
                        if (!_digitToIndexMap.TryGetValue(c, out digitIndex))
                        {
                            throw new ArgumentException(
                                message: String.Format("Number contains an invalid digit '{0}' at position {1}.", c, i),
                                paramName: "number"
                                );
                        }
    
                        return Convert.ToInt64(digitIndex * Math.Pow(_radix, power));
                    };
    
                return number.Select(selector).Sum();
            }
    
            public string Base10ToString(long number)
            {
                if (number < 0)
                {
                    throw new ArgumentOutOfRangeException(
                        message: "Value cannot be negative.",
                        paramName: "number"
                        );
                }
    
                string text = string.Empty;
    
                long remainder;
                do
                {
                    number = Math.DivRem(number, _radix, out remainder);
    
                    char digit;
                    if (!_indexToDigitMap.TryGetValue((int) remainder, out digit) || digit == NullDigit)
                    {
                        throw new ArgumentException(
                            message: "Value cannot be converted given the set of digits used by this converter.",
                            paramName: "number"
                            );
                    }
    
                    text = digit + text;
                }
                while (number > 0);
    
                return text;
            }
        }
    }
    

    This can also be subclassed to derive custom number converters:

    namespace StackOverflow
    {
        public sealed class BinaryNumberConverter : Base10Converter
        {
            public BinaryNumberConverter()
                : base(digits: "01", shouldSupportRoundTripping: false)
            {
            }
        }
    
        public sealed class HexNumberConverter : Base10Converter
        {
            public HexNumberConverter()
                : base(digits: "0123456789ABCDEF", shouldSupportRoundTripping: false)
            {
            }
        }
    }
    

    And the code would be used like this:

    using System.Diagnostics;
    
    namespace StackOverflow
    {
        class Program
        {
            static void Main(string[] args)
            {
                {
                    var converter = new Base10Converter(
                        digits: "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz",
                        shouldSupportRoundTripping: true
                        );
    
                    long number = converter.StringToBase10("Atoz");
                    string text = converter.Base10ToString(number);
                    Debug.Assert(text == "Atoz");
                }
    
                {
                    var converter = new HexNumberConverter();
    
                    string text = converter.Base10ToString(255);
                    long number = converter.StringToBase10(text);
                    Debug.Assert(number == 255);
                }
            }
        }
    }
    

提交回复
热议问题