132 votes

Moyen le plus rapide de convertir un numéro de base 10 en n’importe quelle base en .NET?

J'ai et les anciens(ish) méthode C# que j'ai écrit qui prend un nombre et le convertit à toute la base:

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

Il n'est pas super rapide et soigné. Est-il un bon moyen connu de la réalisation de ce dans .NET?

Je suis à la recherche de quelque chose qui me permet d'utiliser toute la base avec une chaîne quelconque de caractères à utiliser.

Cela ne vous permet de bases 16, 10, 8 et 2:

Convert.ToString(1, x);

Je veux utiliser cette fonction pour obtenir un massivement de base haute en prenant avantage des chiffres, des minuscules et des majuscules des lettres. Comme dans ce fil, mais pour C# pas de JavaScript.

Quelqu'un sait-il d'un moyen efficace de le faire en C#?

161voto

0xA3 Points 73439

Convert.ToString peut être utilisé pour convertir un nombre équivalent de la chaîne de la représentation dans une base spécifiée.

Exemple:

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

Cependant, comme souligné par les commentaires, Convert.ToString prend uniquement en charge les éléments suivants limitée mais généralement suffisante - ensemble de bases: 2, 8, 10 ou 16.

Mise à jour (pour répondre à l'exigence de convertir à toute la base):

Je ne suis pas au courant de n'importe quelle méthode de la BCL qui est capable de convertir des nombres à toute la base de sorte que vous devez écrire votre propre fonction d'utilité. Un exemple simple serait ressembler à ça (notez que cela ne peut certainement être fait plus rapidement en remplacement de la concaténation de chaîne):

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

Mise À Jour 2 (Amélioration Du Rendement)

À l'aide d'une matrice de mémoire tampon au lieu de concaténation de chaîne à construire la chaîne de résultat donne une amélioration de la performance en particulier sur le grand nombre (voir méthode IntToStringFast). Dans le meilleur des cas (c'est à dire la plus longue possible d'entrée) cette méthode est à peu près trois fois plus vite. Toutefois, pour les 1-chiffres (c'est à dire 1-chiffre dans la base cible), IntToString sera plus rapide.

95voto

Pavel Vladov Points 1074

J'ai récemment blogué à ce sujet . Mon implémentation n'utilise aucune opération de chaîne lors des calculs, ce qui la rend très rapide. La conversion en tout système numérique avec base de 2 à 36 est prise en charge:

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

3voto

Steve Rands Points 21

Très en retard à la fête sur ce point, mais j'ai écrit une classe helper récemment pour un projet au travail. Il a été conçu pour convertir des chaînes courtes en chiffres et en arrière de nouveau (simpliste de hachage parfait de la fonction), mais il permettra également d'effectuer le numéro de conversion entre des bases arbitraires. L' Base10ToString méthode de la mise en œuvre répond à la question qui a été posté.

L' shouldSupportRoundTripping drapeau passé au constructeur de la classe est nécessaire pour prévenir la perte des premiers chiffres du numéro de chaîne au cours de la conversion en base 10 et de retour à nouveau (crucial, compte tenu de mes besoins!). La plupart du temps la perte de leader de 0s à partir du numéro de chaîne ne sera probablement pas un problème.

De toute façon, voici le code:

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

Cela peut également être sous-classé pour tirer personnalisé nombre de convertisseurs:

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

Et le code pourra être utilisé comme ceci:

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

1voto

Svish Points 32303

Cette classe de ce post de forum pourrait-elle vous aider?

 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; 

} 

}
 

Totalement non testé ... laissez-moi savoir si cela fonctionne! (Copiez-le au cas où le message du forum disparaîtrait ou quelque chose du genre ...)

0voto

user1031307 Points 11

Moi aussi, je cherchais un moyen rapide de convertir le nombre décimal à une autre base dans la gamme de [2..36] j'ai donc développé le code suivant. Son simple à suivre et utilise un objet Stringbuilder comme un proxy pour un tampon de caractères que nous pouvons indice de caractère par caractère. Le code semble être très rapide par rapport aux alternatives et beaucoup plus vite que l'initialisation des caractères individuels dans un tableau de caractères.

Pour votre propre usage, vous préférerez peut-être: 1/ Retourne une chaîne vide plutôt que de jeter une exception. 2/ retirez le radix vérifiez la méthode de courir encore plus vite 3/ Initialiser l'objet Stringbuilder avec 32 '0' et de supprimer la ligne de résultat.Supprimer( 0, i );. Ce sera la cause de la chaîne de caractères à renvoyer avec les zéros non significatifs et d'augmenter la vitesse. 4/ Faire l'objet Stringbuilder un champ statique au sein de la classe, alors peu importe combien de fois la DecimalToBase méthode est appelée, l'objet Stringbuilder est seulement initialisées à la fois. Si vous faites ce changement 3 ci-dessus ne fonctionnerait plus.

J'espère que quelqu'un trouve cela utile :)

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X