[c#] .NET에서 10 진수를 임의의 염기로 변환하는 가장 빠른 방법은 무엇입니까?

나는 숫자를 취하여 어떤 염기로도 변환하는 이전 (ish) C # 메소드를 가지고 있습니다.

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

그다지 빠르고 깔끔한 것은 아닙니다. .NET에서 이것을 달성하는 잘 알려진 방법이 있습니까?

사용할 임의의 문자열로 모든 기본 을 사용할 수있는 것을 찾고 있습니다.

이것은 16, 10, 8 및 2 염기 만 허용합니다.

Convert.ToString(1, x);

나는 이것을 사용하여 숫자, 모두 소문자 및 모두 대문자를 활용하여 엄청나게 높은 기수를 얻고 싶습니다. 에서와 마찬가지로 이 스레드 ,하지만 C #을하지 자바 스크립트.

누구든지 C #에서 이것을 수행하는 훌륭하고 효율적인 방법을 알고 있습니까?



답변

Convert.ToString 숫자를 지정된 밑수의 해당 문자열 표현으로 변환하는 데 사용할 수 있습니다.

예:

string binary = Convert.ToString(5, 2); // convert 5 to its binary representation
Console.WriteLine(binary);              // prints 101

그러나 주석에서 지적한대로 Convert.ToString에서는 제한적이지만 일반적으로 충분한 기준 세트 인 2, 8, 10 또는 16 만 지원합니다.

업데이트 (기본으로 변환하기위한 요구 사항 충족) :

나는 숫자를 어떤 밑으로도 변환 할 수있는 BCL의 어떤 방법도 알지 못하기 때문에 자신 만의 작은 유틸리티 함수를 작성해야합니다. 간단한 샘플은 다음과 같습니다 (문자열 연결을 바꾸면 더 빨리 만들 수 있습니다).

class Program
{
    static void Main(string[] args)
    {
        // convert to binary
        string binary = IntToString(42, new char[] { '0', '1' });

        // convert to hexadecimal
        string hex = IntToString(42,
            new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                         'A', 'B', 'C', 'D', 'E', 'F'});

        // convert to hexavigesimal (base 26, A-Z)
        string hexavigesimal = IntToString(42,
            Enumerable.Range('A', 26).Select(x => (char)x).ToArray());

        // convert to sexagesimal
        string xx = IntToString(42,
            new char[] { '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'});
    }

    public static string IntToString(int value, char[] baseChars)
    {
        string result = string.Empty;
        int targetBase = baseChars.Length;

        do
        {
            result = baseChars[value % targetBase] + result;
            value = value / targetBase;
        }
        while (value > 0);

        return result;
    }

    /// <summary>
    /// An optimized method using an array as buffer instead of 
    /// string concatenation. This is faster for return values having 
    /// a length > 1.
    /// </summary>
    public static string IntToStringFast(int value, char[] baseChars)
    {
        // 32 is the worst cast buffer size for base 2 and int.MaxValue
        int i = 32;
        char[] buffer = new char[i];
        int targetBase= baseChars.Length;

        do
        {
            buffer[--i] = baseChars[value % targetBase];
            value = value / targetBase;
        }
        while (value > 0);

        char[] result = new char[32 - i];
        Array.Copy(buffer, i, result, 0, 32 - i);

        return new string(result);
    }
}

업데이트 2 (성능 개선)

문자열 연결 대신 배열 버퍼를 사용하여 결과 문자열을 작성하면 특히 많은 수에서 성능이 향상됩니다 (method 참조 IntToStringFast). 가장 좋은 경우 (즉, 가능한 가장 긴 입력)이 방법은 대략 3 배 더 빠릅니다. 그러나 1 자리 숫자 (즉, 대상 기준의 1 자리 숫자)의 IntToString경우 더 빠릅니다.


답변

나는 최근에 이것에 대해 블로그에 올렸다 . 내 구현은 계산 중에 문자열 연산을 사용하지 않으므로 매우 빠릅니다 . 2에서 36까지의 기수가있는 모든 숫자 체계로의 변환이 지원됩니다.

/// <summary>
/// Converts the given decimal number to the numeral system with the
/// specified radix (in the range [2, 36]).
/// </summary>
/// <param name="decimalNumber">The number to convert.</param>
/// <param name="radix">The radix of the destination numeral system (in the range [2, 36]).</param>
/// <returns></returns>
public static string DecimalToArbitrarySystem(long decimalNumber, int radix)
{
    const int BitsInLong = 64;
    const string Digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";

    if (radix < 2 || radix > Digits.Length)
        throw new ArgumentException("The radix must be >= 2 and <= " + Digits.Length.ToString());

    if (decimalNumber == 0)
        return "0";

    int index = BitsInLong - 1;
    long currentNumber = Math.Abs(decimalNumber);
    char[] charArray = new char[BitsInLong];

    while (currentNumber != 0)
    {
        int remainder = (int)(currentNumber % radix);
        charArray[index--] = Digits[remainder];
        currentNumber = currentNumber / radix;
    }

    string result = new String(charArray, index + 1, BitsInLong - index - 1);
    if (decimalNumber < 0)
    {
        result = "-" + result;
    }

    return result;
}

누구에게나 필요한 경우를 대비하여 빠른 역함수도 구현했습니다 :
Arbitrary to Decimal Numeral System .


답변

FAST ” FROM AND” ” TO “방법

나는 파티에 늦었지만 이전 답변을 합성하고 개선했습니다. 이 두 가지 방법이 지금까지 게시 된 다른 방법보다 빠르다고 생각합니다. 단일 코어 머신에서 400ms 이내에 1,000,000 개의 숫자를 기본 36으로 변환 할 수있었습니다.

아래의 예는 기본 62 입니다. BaseChars다른 염기로 변환 하려면 배열을 변경하십시오 .

private static readonly char[] BaseChars =
         "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".ToCharArray();
private static readonly Dictionary<char, int> CharValues = BaseChars
           .Select((c,i)=>new {Char=c, Index=i})
           .ToDictionary(c=>c.Char,c=>c.Index);

public static string LongToBase(long value)
{
   long targetBase = BaseChars.Length;
   // Determine exact number of characters to use.
   char[] buffer = new char[Math.Max(
              (int) Math.Ceiling(Math.Log(value + 1, targetBase)), 1)];

   var i = buffer.Length;
   do
   {
       buffer[--i] = BaseChars[value % targetBase];
       value = value / targetBase;
   }
   while (value > 0);

   return new string(buffer, i, buffer.Length - i);
}

public static long BaseToLong(string number)
{
    char[] chrs = number.ToCharArray();
    int m = chrs.Length - 1;
    int n = BaseChars.Length, x;
    long result = 0;
    for (int i = 0; i < chrs.Length; i++)
    {
        x = CharValues[ chrs[i] ];
        result += x * (long)Math.Pow(n, m--);
    }
    return result;
} 

수정 (2018-07-12)

46655를 기본 36으로 변환하는 @AdrianBotor (주석 참조)에서 발견 된 코너 케이스를 해결하도록 수정되었습니다. 이는 Math.Log(46656, 36)정확히 3 인 계산의 작은 부동 소수점 오류 3 + 4.44e-16로 인해 발생 하지만 .NET은을 반환 하여 출력 버퍼에 추가 문자가 발생합니다. .


답변

허용되는 버전의 약간 수정 된 버전을 사용하고 필요에 따라 기본 문자열을 조정할 수도 있습니다.

public static string Int32ToString(int value, int toBase)
{
    string result = string.Empty;
    do
    {
        result = "0123456789ABCDEF"[value % toBase] + result;
        value /= toBase;
    }
    while (value > 0);

    return result;
}


답변

이것에 대한 파티에 매우 늦었지만 최근에 직장에서 프로젝트를 위해 다음 도우미 클래스를 작성했습니다. 짧은 문자열을 숫자로 다시 변환하도록 설계 되었지만 (단순한 완벽한 해시 함수) 임의의 염기간에 숫자 변환도 수행합니다. Base10ToString메소드 구현은 원래 게시 된 질문에 대한 대답.

shouldSupportRoundTripping클래스 생성자에 전달 된 플래그는 10 진수로 변환하는 동안 숫자 문자열에서 선행 자릿수가 손실되는 것을 방지하기 위해 필요합니다 (내 요구 사항에 따라 중요합니다!). 대부분의 경우 숫자 문자열에서 선행 0이 손실되는 것은 문제가되지 않을 것입니다.

어쨌든 다음은 코드입니다.

using System;
using System.Collections.Generic;
using System.Linq;

namespace StackOverflow
{
    /// <summary>
    /// Contains methods used to convert numbers between base-10 and another numbering system.
    /// </summary>
    /// <remarks>
    /// <para>
    /// 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.
    /// </para>
    /// <para>
    /// The first digit in the sequence has special significance. If the number passed to the
    /// <see cref="StringToBase10"/> 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 <see cref="System.Boolean"/>
    /// flag passed to the constructor allows 'round-tripping' behaviour to be supported, which will prevent
    /// leading digits from being lost during conversion.
    /// </para>
    /// <para>
    /// Note that numeric overflow is probable when using longer strings and larger digit sets.
    /// </para>
    /// </remarks>
    public class Base10Converter
    {
        const char NullDigit = '\0';

        public Base10Converter(string digits, bool shouldSupportRoundTripping = false)
            : this(digits.ToCharArray(), shouldSupportRoundTripping)
        {
        }

        public Base10Converter(IEnumerable<char> 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<char, int> _digitToIndexMap;
        readonly Dictionary<int, char> _indexToDigitMap;
        readonly int _radix;

        public long StringToBase10(string number)
        {
            Func<char, int, long> 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;
        }
    }
}

이것은 또한 사용자 지정 숫자 변환기를 파생하기 위해 하위 분류 될 수 있습니다.

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)
        {
        }
    }
}

그리고 코드는 다음과 같이 사용됩니다.

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);
            }
        }
    }
}


답변

이 포럼 게시물 의이 수업 도움 될 수 있습니까?

public class BaseConverter {

public static string ToBase(string number, int start_base, int target_base) {

  int base10 = this.ToBase10(number, start_base);
  string rtn = this.FromBase10(base10, target_base);
  return rtn;

}

public static int ToBase10(string number, int start_base) {

  if (start_base < 2 || start_base > 36) return 0;
  if (start_base == 10) return Convert.ToInt32(number);

  char[] chrs = number.ToCharArray();
  int m = chrs.Length - 1;
  int n = start_base;
  int x;
  int rtn = 0;

  foreach(char c in chrs) {

    if (char.IsNumber(c))
      x = int.Parse(c.ToString());
    else
      x = Convert.ToInt32(c) - 55;

    rtn += x * (Convert.ToInt32(Math.Pow(n, m)));

    m--;

  }

  return rtn;

}

public static string FromBase10(int number, int target_base) {

  if (target_base < 2 || target_base > 36) return "";
  if (target_base == 10) return number.ToString();

  int n = target_base;
  int q = number;
  int r;
  string rtn = "";

  while (q >= n) {

    r = q % n;
    q = q / n;

    if (r < 10)
      rtn = r.ToString() + rtn;
    else
      rtn = Convert.ToChar(r + 55).ToString() + rtn;

  }

  if (q < 10)
    rtn = q.ToString() + rtn;
  else
    rtn = Convert.ToChar(q + 55).ToString() + rtn;

  return rtn;

}

}

완전히 테스트되지 않았습니다 … 작동하는지 알려주세요! (포럼 게시물이 사라지는 경우를 대비하여 복사-붙여 넣기 …)


답변

나도 십진수를 [2..36] 범위의 다른 밑으로 변환하는 빠른 방법을 찾고 있었기 때문에 다음 코드를 개발했습니다. 따라 가기 쉽고 Stringbuilder 객체를 문자별로 인덱싱 할 수있는 문자 버퍼의 프록시로 사용합니다. 코드는 대안에 비해 매우 빠르며 문자 배열에서 개별 문자를 초기화하는 것보다 훨씬 빠릅니다.

자신의 사용을 위해 다음을 선호 할 수 있습니다. 1 / 예외를 던지기보다는 빈 문자열을 반환합니다. 2 / 메서드가 더 빠르게 실행되도록 기수 검사를 제거합니다. 3 / 32 ‘0’으로 Stringbuilder 객체를 초기화하고 result.Remove (0, i); 줄을 제거합니다. 이렇게하면 문자열이 선행 0으로 반환되고 속도가 더욱 빨라집니다. 4 / Stringbuilder 객체를 클래스 내에서 정적 필드로 만들어 DecimalToBase 메서드가 몇 번 호출 되든 Stringbuilder 객체는 한 번만 초기화됩니다. 이 변경을 수행하면 위의 3이 더 이상 작동하지 않습니다.

누군가 이것이 유용하다고 생각하기를 바랍니다. 🙂

AtomicParadox

        static string DecimalToBase(int number, int radix)
    {
        // Check that the radix is between 2 and 36 inclusive
        if ( radix < 2 || radix > 36 )
            throw new ArgumentException("ConvertToBase(int number, int radix) - Radix must be between 2 and 36.");

        // Create a buffer large enough to hold the largest int value represented in binary digits 
        StringBuilder result = new StringBuilder("                                ");  // 32 spaces

        // The base conversion calculates the digits in reverse order so use
        // an index to point to the last unused space in our buffer
        int i = 32;

        // Convert the number to the new base
        do
        {
            int remainder = number % radix;
            number = number / radix;
            if(remainder <= 9)
                result[--i] = (char)(remainder + '0');  // Converts [0..9] to ASCII ['0'..'9']
            else
                result[--i] = (char)(remainder + '7');  // Converts [10..36] to ASCII ['A'..'Z']
        } while ( number > 0 );

        // Remove the unwanted padding from the front of our buffer and return the result
        // Note i points to the last unused character in our buffer
        result.Remove( 0, i );
        return (result.ToString());
    }