Le moyen le plus rapide de convertir un nombre de base 10 en n'importe quelle base dans .NET?

108

J'ai une ancienne méthode (ish) C # que j'ai écrite qui prend un nombre et le convertit en n'importe quelle base:

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

Ce n'est pas si rapide et soigné. Existe-t-il une bonne manière connue d'y parvenir dans .NET?

Je recherche quelque chose qui me permette d'utiliser n'importe quelle base avec une chaîne arbitraire de caractères à utiliser.

Cela n'autorise que les bases 16, 10, 8 et 2:

Convert.ToString(1, x);

Je veux utiliser cela pour obtenir une base massivement élevée en tirant parti des chiffres, toutes les minuscules et toutes les lettres majuscules. Comme dans ce fil , mais pour C # pas JavaScript.

Quelqu'un connaît-il un moyen efficace et efficace de le faire en C #?

Joshcomley
la source

Réponses:

136

Convert.ToString peut être utilisé pour convertir un nombre en sa représentation sous forme de chaîne équivalente 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 indiqué dans les commentaires, Convert.ToStringne prend en charge que l'ensemble limité - mais généralement suffisant - de bases suivantes: 2, 8, 10 ou 16.

Mise à jour (pour répondre à l'exigence de conversion vers n'importe quelle base):

Je ne connais aucune méthode de la BCL capable de convertir des nombres en n'importe quelle base, vous devrez donc écrire votre propre petite fonction utilitaire. Un exemple simple ressemblerait à cela (notez que cela peut sûrement être rendu plus rapide en remplaçant la concaténation de chaînes):

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

Mise à jour 2 (amélioration des performances)

L'utilisation d'un tampon de tableau au lieu de la concaténation de chaînes pour construire la chaîne de résultat améliore les performances, en particulier sur un grand nombre (voir méthode IntToStringFast). Dans le meilleur des cas (c'est-à-dire la saisie la plus longue possible), cette méthode est environ trois fois plus rapide. Cependant, pour les nombres à 1 chiffre (c'est-à-dire 1 chiffre dans la base cible), IntToStringsera plus rapide.

Dirk Vollmar
la source
5
Il convient de noter que cela ne prend en charge que les bases 2,8,10,16 - pas le «tout» dans la question. parce que vous ne savez jamais quand vous aurez besoin de sexagésimal ;-p
Marc Gravell
46
Sexagésimal semble amusant.
rein
Avec une targetBase de 60 et une valeur de 12345, cette ligne dans la méthode IntToString: value = value / targetBase; ferait valeur = 203,75. Est-ce correct? Ne devrait-il pas le garder sous forme de nombre entier?
Adam Harte
6
Impressionnant. Mais où est la fonction inverse? : /
ashes999
2
J'ai une fonction inverse de premier passage ici: stackoverflow.com/questions/3579970
...
78

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 vers n'importe quel système numérique avec une 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;
}

J'ai également implémenté une fonction inverse rapide au cas où quelqu'un en aurait besoin aussi: Arbitrary to Decimal Numeral System .

Pavel Vladov
la source
5
J'ai testé toutes les solutions de cette page, et c'est la plus rapide, environ deux fois plus rapide que la solution courte à la fin.
Justin R.
Qu'est-ce que c'est result = "-" + result? Est-ce une sorte de rembourrage? Comment pourrais-je modifier le code pour n'utiliser que AZ ou 0-9 pour un caractère de remplissage?
oscilatingcretin
Le "-"in result = "-" + resultreprésente le signe négatif des nombres négatifs. Ce n'est pas un caractère de remplissage.
Pavel Vladov
2
Pourquoi n'est-ce pas la réponse acceptée? C'est brilliant!
Avrohom Yisroel
Merci, cela m'a fait gagner beaucoup de temps.
NinjaLlama
15

MÉTHODES RAPIDES " DE " ET " À "

Je suis en retard à la fête, mais j'ai composé les réponses précédentes et les ai améliorées. Je pense que ces deux méthodes sont plus rapides que toutes les autres publiées jusqu'à présent. J'ai pu convertir 1 000 000 de numéros de et vers la base 36 en moins de 400 ms dans une machine à un seul cœur.

L'exemple ci-dessous concerne la base 62 . Modifiez le BaseCharstableau à convertir depuis et vers n'importe quelle autre base.

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

MODIFIER (2018-07-12)

Correction pour résoudre le cas d'angle trouvé par @AdrianBotor (voir les commentaires) convertissant 46655 en base 36. Cela est causé par une petite erreur de calcul en virgule flottante Math.Log(46656, 36)qui est exactement 3, mais .NET retourne 3 + 4.44e-16, ce qui provoque un caractère supplémentaire dans le tampon de sortie .

Diego
la source
@AdrianBotor Impossible de reproduire votre problème:BaseToLong(LongToBase(46655)) == 46655
Diego
2
@Diego, Désolé pour la réponse tardive. Initialisons le tableau avec 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZet convertissons la valeur 46655. Le résultat devrait être ZZZmais dans le débogueur je reçois \0ZZZ. Seule cette valeur est supplémentaire \0. Par exemple, la valeur se 46654convertit correctement en ZZY.
Adrian Botor
@AdrianBotor Bonne prise. Corrigé en modifiant la déclaration de retour LongToBaseàreturn new string(buffer, (int) i, buffer.Length - (int)i);
Diego
7

On peut également utiliser une version légèrement modifiée de celle acceptée et ajuster la chaîne de caractères de base en fonction de ses besoins:

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;
}
Kresimir
la source
4

Très tard à la fête sur celui-ci, mais j'ai récemment écrit la classe d'aide suivante pour un projet au travail. Il a été conçu pour convertir des chaînes courtes en nombres et inversement (une fonction de hachage parfaite simpliste ), mais il effectuera également une conversion de nombres entre des bases arbitraires. leBase10ToString implémentation de la méthode répond à la question qui a été initialement publiée.

le shouldSupportRoundTripping drapeau passé au constructeur de classe est nécessaire pour éviter la perte des chiffres de tête de la chaîne numérique lors de la conversion en base 10 et inversement (crucial, compte tenu de mes exigences!). La plupart du temps, la perte des 0 en tête de la chaîne numérique ne sera probablement pas un problème.

Bref, 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 dériver des convertisseurs de nombres personnalisés:

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 serait 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);
            }
        }
    }
}
Steve Rands
la source
2

Ce cours de ce post de forum pourrait-il 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é ... faites-moi savoir si cela fonctionne! (Copiez-le au cas où le message du forum disparaîtrait ou quelque chose du genre ...)

Svish
la source
Fermer .. J'aurai une pièce plus tard. Il faudra un peu de travail pour pouvoir prendre n'importe quel caractère, mais c'est un pas dans la bonne direction. Je vais comparer la vitesse avec ma propre méthode!
joshcomley
N'oubliez pas de le partager si ici si vous l'améliorez. Quelqu'un d'autre voudra peut-être ot aussi =)
Svish
@joshcomley Comment s'est passé le week-end? ;)
Mikkel R. Lund
3
C'était un long week-end: D
joshcomley
1

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

Pour votre propre usage, vous préférerez peut-être: 1 / Renvoyer une chaîne vide plutôt que de lever une exception. 2 / supprimer la vérification de base pour rendre la méthode encore plus rapide 3 / Initialiser l'objet Stringbuilder avec 32 '0 et supprimer le résultat de la ligne.Supprimer (0, i) ;. Cela entraînera le retour de la chaîne avec des zéros non significatifs et augmentera encore la vitesse. 4 / Faites de l'objet Stringbuilder un champ statique au sein de la classe afin que peu importe le nombre de fois que la méthode DecimalToBase est appelée l'objet Stringbuilder n'est initialisé qu'une seule fois. Si vous faites ce changement 3 ci-dessus ne fonctionnerait plus.

J'espère que quelqu'un trouvera 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());
    }
user1031307
la source
0

J'utilisais ceci pour stocker un Guid sous forme de chaîne plus courte (mais était limité à 106 caractères). Si quelqu'un est intéressé, voici mon code pour décoder la chaîne en valeur numérique (dans ce cas, j'ai utilisé 2 ulongs pour la valeur Guid, plutôt que de coder un Int128 (puisque je suis en 3.5 et non en 4.0). Pour plus de clarté, CODE est un chaîne const avec 106 caractères uniques. ConvertLongsToBytes est assez peu passionnant.

private static Guid B106ToGuid(string pStr)
    {
        try
        {
            ulong tMutl = 1, tL1 = 0, tL2 = 0, targetBase = (ulong)CODE.Length;
            for (int i = 0; i < pStr.Length / 2; i++)
            {
                tL1 += (ulong)CODE.IndexOf(pStr[i]) * tMutl;
                tL2 += (ulong)CODE.IndexOf(pStr[pStr.Length / 2 + i]) * tMutl;
                tMutl *= targetBase;
            }
            return new Guid(ConvertLongsToBytes(tL1, tL2));
        }
        catch (Exception ex)
        {
            throw new Exception("B106ToGuid failed to convert string to Guid", ex);
        }
    }
Gary
la source
0

J'avais un besoin similaire, sauf que j'avais aussi besoin de faire des maths sur les «nombres». J'ai pris certaines des suggestions ici et j'ai créé une classe qui fera tout ce truc amusant. Il permet d'utiliser n'importe quel caractère Unicode pour représenter un nombre et fonctionne également avec des décimales.

Cette classe est assez facile à utiliser. Créez simplement un nombre en tant que type de New BaseNumber, définissez quelques propriétés et désactivez. Les routines prennent soin de basculer automatiquement entre la base 10 et la base x et la valeur que vous définissez est conservée dans la base dans laquelle vous la définissez, donc aucune précision n'est perdue (jusqu'à la conversion, mais même dans ce cas, la perte de précision devrait être très minime car cela utilisations courantes Doubleet Longdans la mesure du possible).

Je ne peux pas commander sur la vitesse de cette routine. C'est probablement assez lent, donc je ne sais pas si cela répondra aux besoins de celui qui a posé la question, mais il est certain qu'il est flexible, alors j'espère que quelqu'un d'autre pourra l'utiliser.

Pour toute autre personne pouvant avoir besoin de ce code pour calculer la colonne suivante dans Excel, j'inclurai le code de boucle que j'ai utilisé et qui exploite cette classe.

Public Class BaseNumber

    Private _CharacterArray As List(Of Char)

    Private _BaseXNumber As String
    Private _Base10Number As Double?

    Private NumberBaseLow As Integer
    Private NumberBaseHigh As Integer

    Private DecimalSeparator As Char = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator
    Private GroupSeparator As Char = System.Globalization.CultureInfo.CurrentCulture.NumberFormat.NumberGroupSeparator

    Public Sub UseCapsLetters()
        'http://unicodelookup.com
        TrySetBaseSet(65, 90)
    End Sub

    Public Function GetCharacterArray() As List(Of Char)
        Return _CharacterArray
    End Function

    Public Sub SetCharacterArray(CharacterArray As String)
        _CharacterArray = New List(Of Char)
        _CharacterArray.AddRange(CharacterArray.ToList)

        TrySetBaseSet(_CharacterArray)
    End Sub

    Public Sub SetCharacterArray(CharacterArray As List(Of Char))
        _CharacterArray = CharacterArray
        TrySetBaseSet(_CharacterArray)
    End Sub

    Public Sub SetNumber(Value As String)
        _BaseXNumber = Value
        _Base10Number = Nothing
    End Sub

    Public Sub SetNumber(Value As Double)
        _Base10Number = Value
        _BaseXNumber = Nothing
    End Sub

    Public Function GetBaseXNumber() As String
        If _BaseXNumber IsNot Nothing Then
            Return _BaseXNumber
        Else
            Return ToBaseString()
        End If
    End Function

    Public Function GetBase10Number() As Double
        If _Base10Number IsNot Nothing Then
            Return _Base10Number
        Else
            Return ToBase10()
        End If
    End Function

    Private Sub TrySetBaseSet(Values As List(Of Char))
        For Each value As Char In _BaseXNumber
            If Not Values.Contains(value) Then
                Throw New ArgumentOutOfRangeException("The string has a value, " & value & ", not contained in the selected 'base' set.")
                _CharacterArray.Clear()
                DetermineNumberBase()
            End If
        Next

        _CharacterArray = Values

    End Sub

    Private Sub TrySetBaseSet(LowValue As Integer, HighValue As Integer)

        Dim HighLow As KeyValuePair(Of Integer, Integer) = GetHighLow()

        If HighLow.Key < LowValue OrElse HighLow.Value > HighValue Then
            Throw New ArgumentOutOfRangeException("The string has a value not contained in the selected 'base' set.")
            _CharacterArray.Clear()
            DetermineNumberBase()
        End If

        NumberBaseLow = LowValue
        NumberBaseHigh = HighValue

    End Sub

    Private Function GetHighLow(Optional Values As List(Of Char) = Nothing) As KeyValuePair(Of Integer, Integer)
        If Values Is Nothing Then
            Values = _BaseXNumber.ToList
        End If

        Dim lowestValue As Integer = Convert.ToInt32(Values(0))
        Dim highestValue As Integer = Convert.ToInt32(Values(0))

        Dim currentValue As Integer

        For Each value As Char In Values

            If value <> DecimalSeparator AndAlso value <> GroupSeparator Then
                currentValue = Convert.ToInt32(value)
                If currentValue > highestValue Then
                    highestValue = currentValue
                End If
                If currentValue < lowestValue Then
                    currentValue = lowestValue
                End If
            End If
        Next

        Return New KeyValuePair(Of Integer, Integer)(lowestValue, highestValue)

    End Function

    Public Sub New(BaseXNumber As String)
        _BaseXNumber = BaseXNumber
        DetermineNumberBase()
    End Sub

    Public Sub New(BaseXNumber As String, NumberBase As Integer)
        Me.New(BaseXNumber, Convert.ToInt32("0"c), NumberBase)
    End Sub

    Public Sub New(BaseXNumber As String, NumberBaseLow As Integer, NumberBaseHigh As Integer)
        _BaseXNumber = BaseXNumber
        Me.NumberBaseLow = NumberBaseLow
        Me.NumberBaseHigh = NumberBaseHigh
    End Sub

    Public Sub New(Base10Number As Double)
        _Base10Number = Base10Number
    End Sub

    Private Sub DetermineNumberBase()
        Dim highestValue As Integer

        Dim currentValue As Integer

        For Each value As Char In _BaseXNumber

            currentValue = Convert.ToInt32(value)
            If currentValue > highestValue Then
                highestValue = currentValue
            End If
        Next

        NumberBaseHigh = highestValue
        NumberBaseLow = Convert.ToInt32("0"c) 'assume 0 is the lowest

    End Sub

    Private Function ToBaseString() As String
        Dim Base10Number As Double = _Base10Number

        Dim intPart As Long = Math.Truncate(Base10Number)
        Dim fracPart As Long = (Base10Number - intPart).ToString.Replace(DecimalSeparator, "")

        Dim intPartString As String = ConvertIntToString(intPart)
        Dim fracPartString As String = If(fracPart <> 0, DecimalSeparator & ConvertIntToString(fracPart), "")

        Return intPartString & fracPartString

    End Function

    Private Function ToBase10() As Double
        Dim intPartString As String = _BaseXNumber.Split(DecimalSeparator)(0).Replace(GroupSeparator, "")
        Dim fracPartString As String = If(_BaseXNumber.Contains(DecimalSeparator), _BaseXNumber.Split(DecimalSeparator)(1), "")

        Dim intPart As Long = ConvertStringToInt(intPartString)
        Dim fracPartNumerator As Long = ConvertStringToInt(fracPartString)
        Dim fracPartDenominator As Long = ConvertStringToInt(GetEncodedChar(1) & String.Join("", Enumerable.Repeat(GetEncodedChar(0), fracPartString.ToString.Length)))

        Return Convert.ToDouble(intPart + fracPartNumerator / fracPartDenominator)

    End Function

    Private Function ConvertIntToString(ValueToConvert As Long) As String
        Dim result As String = String.Empty
        Dim targetBase As Long = GetEncodingCharsLength()

        Do
            result = GetEncodedChar(ValueToConvert Mod targetBase) & result
            ValueToConvert = ValueToConvert \ targetBase
        Loop While ValueToConvert > 0

        Return result
    End Function

    Private Function ConvertStringToInt(ValueToConvert As String) As Long
        Dim result As Long
        Dim targetBase As Integer = GetEncodingCharsLength()
        Dim startBase As Integer = GetEncodingCharsStartBase()

        Dim value As Char
        For x As Integer = 0 To ValueToConvert.Length - 1
            value = ValueToConvert(x)
            result += GetDecodedChar(value) * Convert.ToInt32(Math.Pow(GetEncodingCharsLength, ValueToConvert.Length - (x + 1)))
        Next

        Return result

    End Function

    Private Function GetEncodedChar(index As Integer) As Char
        If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
            Return _CharacterArray(index)
        Else
            Return Convert.ToChar(index + NumberBaseLow)
        End If
    End Function

    Private Function GetDecodedChar(character As Char) As Integer
        If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
            Return _CharacterArray.IndexOf(character)
        Else
            Return Convert.ToInt32(character) - NumberBaseLow
        End If
    End Function

    Private Function GetEncodingCharsLength() As Integer
        If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
            Return _CharacterArray.Count
        Else
            Return NumberBaseHigh - NumberBaseLow + 1
        End If
    End Function

    Private Function GetEncodingCharsStartBase() As Integer
        If _CharacterArray IsNot Nothing AndAlso _CharacterArray.Count > 0 Then
            Return GetHighLow.Key
        Else
            Return NumberBaseLow
        End If
    End Function
End Class

Et maintenant, pour que le code passe en boucle dans les colonnes Excel:

    Public Function GetColumnList(DataSheetID As String) As List(Of String)
        Dim workingColumn As New BaseNumber("A")
        workingColumn.SetCharacterArray("@ABCDEFGHIJKLMNOPQRSTUVWXYZ")

        Dim listOfPopulatedColumns As New List(Of String)
        Dim countOfEmptyColumns As Integer

        Dim colHasData As Boolean
        Dim cellHasData As Boolean

        Do
            colHasData = True
            cellHasData = False
            For r As Integer = 1 To GetMaxRow(DataSheetID)
                cellHasData = cellHasData Or XLGetCellValue(DataSheetID, workingColumn.GetBaseXNumber & r) <> ""
            Next
            colHasData = colHasData And cellHasData

            'keep trying until we get 4 empty columns in a row
            If colHasData Then
                listOfPopulatedColumns.Add(workingColumn.GetBaseXNumber)
                countOfEmptyColumns = 0
            Else
                countOfEmptyColumns += 1
            End If

            'we are already starting with column A, so increment after we check column A
            Do
                workingColumn.SetNumber(workingColumn.GetBase10Number + 1)
            Loop Until Not workingColumn.GetBaseXNumber.Contains("@")

        Loop Until countOfEmptyColumns > 3

        Return listOfPopulatedColumns

    End Function

Vous noterez que la partie importante de la partie Excel est que 0 est identifié par un @ dans le nombre re-basé. Donc, je filtre simplement tous les nombres qui ont un @ en eux et j'obtiens la séquence appropriée (A, B, C, ..., Z, AA, AB, AC, ...).

cjbarth
la source
0
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConvertToAnyBase
{
   class Program
    {
        static void Main(string[] args)
        {
            var baseNumber = int.Parse(Console.ReadLine());
            var number = int.Parse(Console.ReadLine());
            string conversion = "";


            while(number!=0)
            {

                conversion += Convert.ToString(number % baseNumber);
                number = number / baseNumber;
            }
            var conversion2 = conversion.ToArray().Reverse();
            Console.WriteLine(string.Join("", conversion2));


       }
    }
}
Martin Dimitrov
la source
C'est pour les nombres de base de 1 à 10.
Martin Dimitrov
0

Si quelqu'un cherche une option VB, cela était basé sur la réponse de Pavel:

Public Shared Function ToBase(base10 As Long, Optional baseChars As String = "0123456789ABCDEFGHIJKLMNOPQRTSUVWXYZ") As String

    If baseChars.Length < 2 Then Throw New ArgumentException("baseChars must be at least 2 chars long")

    If base10 = 0 Then Return baseChars(0)

    Dim isNegative = base10 < 0
    Dim radix = baseChars.Length
    Dim index As Integer = 64 'because it's how long a string will be if the basechars are 2 long (binary)
    Dim chars(index) As Char '65 chars, 64 from above plus one for sign if it's negative

    base10 = Math.Abs(base10)


    While base10 > 0
        chars(index) = baseChars(base10 Mod radix)
        base10 \= radix

        index -= 1
    End While

    If isNegative Then
        chars(index) = "-"c
        index -= 1
    End If

    Return New String(chars, index + 1, UBound(chars) - index)

End Function
Caius Jard
la source
0

C'est une façon assez simple de le faire, mais ce n'est peut-être pas la plus rapide. Il est assez puissant car il est composable.

public static IEnumerable<int> ToBase(this int x, int b)
{
    IEnumerable<int> ToBaseReverse()
    {
        if (x == 0)
        {
            yield return 0;
            yield break;
        }
        int z = x;
        while (z > 0)
        {
            yield return z % b;
            z = z / b;
        }
    }

    return ToBaseReverse().Reverse();
}

Combinez cela avec cette méthode d'extension simple et tout obtenir une base est maintenant possible:

public static string ToBase(this int number, string digits) =>
    String.Concat(number.ToBase(digits.Length).Select(x => digits[x]));

Il peut être utilisé comme ceci:

var result = 23.ToBase("01");
var result2 = 23.ToBase("012X");

Console.WriteLine(result);
Console.WriteLine(result2);

La sortie est:

10111
11X
Énigmativité
la source