¿La forma más rápida de convertir un número de base 10 a cualquier base en .NET?

Resuelto joshcomley asked hace 15 años • 12 respuestas

Tengo un método antiguo (más o menos) de C# que escribí y que toma un número y lo convierte a cualquier base:

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

No es tan rápido y ordenado. ¿Existe una forma buena y conocida de lograr esto en .NET?

Estoy buscando algo que me permita usar cualquier base con una cadena arbitraria de caracteres.

Esto sólo permite las bases 16, 10, 8 y 2:

Convert.ToString(1, x);

Quiero usar esto para lograr una base enormemente alta aprovechando los números, todas las minúsculas y todas las letras mayúsculas. Como en este hilo , pero para C#, no para JavaScript.

¿Alguien conoce una forma buena y eficiente de hacer esto en C#?

joshcomley avatar May 29 '09 06:05 joshcomley
Aceptado

Convert.ToStringse puede utilizar para convertir un número a su representación de cadena equivalente en una base específica.

Ejemplo:

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

Sin embargo, como se señala en los comentarios, Convert.ToStringsolo admite el siguiente conjunto de bases limitado, pero generalmente suficiente: 2, 8, 10 o 16.

Actualización (para cumplir con el requisito de convertir a cualquier base):

No conozco ningún método en BCL que sea capaz de convertir números a cualquier base, por lo que tendría que escribir su propia pequeña función de utilidad. Un ejemplo simple se vería así (tenga en cuenta que esto seguramente se puede hacer más rápido reemplazando la concatenación de cadenas):

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

Actualización 2 (mejora del rendimiento)

El uso de un búfer de matriz en lugar de la concatenación de cadenas para generar la cadena de resultados proporciona una mejora del rendimiento, especialmente en números grandes (consulte el método IntToStringFast). En el mejor de los casos (es decir, la entrada más larga posible), este método es aproximadamente tres veces más rápido. Sin embargo, para números de 1 dígito (es decir, 1 dígito en la base objetivo), IntToStringserá más rápido.

Dirk Vollmar avatar May 29 '2009 00:05 Dirk Vollmar

Recientemente escribí un blog sobre esto . Mi implementación no utiliza ninguna operación de cadena durante los cálculos, lo que la hace muy rápida . Se admite la conversión a cualquier sistema numérico con base del 2 al 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;
}

También implementé una función inversa rápida en caso de que alguien también la necesite: Sistema de números arbitrarios a decimales .

Pavel Vladov avatar Jun 11 '2012 13:06 Pavel Vladov

MÉTODOS RÁPIDOS " DESDE " Y " HASTA "

Llegué tarde a la fiesta, pero completé las respuestas anteriores y las mejoré. Creo que estos dos métodos son más rápidos que cualquier otro publicado hasta ahora. Pude convertir 1.000.000 de números desde y hacia la base 36 en menos de 400 ms en una máquina de un solo núcleo.

El siguiente ejemplo es para base 62 . Cambie la BaseCharsmatriz para convertir desde y hacia cualquier otra 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;  
} 

EDITAR (2018-07-12)

Se corrigió para solucionar el caso de esquina encontrado por @AdrianBotor (ver comentarios) al convertir 46655 a base 36. Esto se debe a un pequeño error de punto flotante al calcular Math.Log(46656, 36)que es exactamente 3, pero .NET devuelve 3 + 4.44e-16, lo que provoca un carácter adicional en el búfer de salida. .

Diego avatar Jan 25 '2016 23:01 Diego

También se puede utilizar una versión ligeramente modificada de la aceptada y ajustar la cadena de caracteres base a sus necesidades:

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 avatar Sep 09 '2014 21:09 Kresimir