Introducción:
Pues eso... lo dicho, creo que ya poco más puedo contar que no haya contado
ahí arriba... pero
bueno, para que no sea eso solo no vayas a pensar que he puesto la explicación
donde la he puesto
para que hagas otra cosa, je je.
La clase que te muestro (Conversor) define varios métodos, una serie de ellos
te sirven para
convertir un número decimal (base 10) en otro número de otra base distinta, por
ejemplo, base 2
(binario), base 8 (octal), base 16 (hexadecimal) o cualquier otra base desde 2 a
36.
Además, hay otra serie de funciones que sirven para lo contrario, es decir,
para convertir
números de cualquier base numérica (de 2 a 36) a un número decimal (base 10).
El problema de las bases grandes, por ejemplo 36, es que .NET no tiene
capacidad para almacenar
un valor de ese tipo, al menos no para almacenarlo sin que sea de forma
exponencial, así que... como
verás en el siguiente ejemplo, pues no se comporta como debiera.
De otras bases a decimal
========================
101011011 en base 2 -> 347
10765478 en base 8 -> 294247
FF00125E0 en base 16 -> 68451116512
GUILLEMOLA en base 36 -> 1711050165663742
ELGUILLEMOLA en base 36 -> 1921194231198726144
De decimal a otras bases
========================
101011011 en base 2 -> 347
1076547 en base 8 -> 294247
FF00125E0 en base 16 -> 68451116512
GUILLEMOLA en base 36 -> 1711050165663742
ELGUILLEMOR0 en base 36 -> 1921194231198726144
Si te fijas en la última línea de cada bloque, al usar la base 36 con el
"número"
ELGUILLEMOLA, (recuerda que una base 36 te permitirá usar números cuyas cifras
serán del 0 al 9 y de
la A a la Z), da como resultado 1921194231198726144, que no es totalmente
correcto, ya que el valor
debería ser 1921194231198726142 (a lo mejor es que mi código está mal, pero creo
que es problema de
los redondeos, la cosa es que quiero que veas lo que ocurre al convertir el
número nuevamente a la
base 36... je, je, seguramente es que mi ordenador sabe que soy medio moro, je,
je... en fin...
Y no es un truco para hacer un chiste ni nada de eso, si ejecutas el código
que te muestro más
abajo, verás que es ese el resultado que muestra.
Nota:
En realidad, el código lo he depurado y mejorado y el que te muestro ahora hace
los cálculos (en
realidad los redondeos) mejor, y ya no se equivoca con el valor ese de
ELGUILLEMOLA, pero en el
código
de Visual Basic te dejo comentadas las líneas del código anterior que si que me
convertía en moro,
je, je.
Por cierto, en el código de C# no está solucionado ese problemilla de redondeos,
pero no creo que te
resulte difícil solucionarlo, sobre todo si te fijas en el de VB y ves los
cambios que he hecho.
Es que como se supone que los programadores de Visual Basic somos más torpes,
pues hay que darle más
ayuda que a los de C# ;-)))
Pero esto tiene solución, aunque para ello hay que usar otros tipos de datos.
En mi caso, como
hace un rato anuncié, estoy finalizando la librería para trabajar con tipos
numéricos de gran
precisión (librería de la que puedes ver el código completo y te la puedes bajar
desde
mi
nuevo
sitio Programar por programar), aunque todavía no está publicado el
código que hace esto de
las
conversiones, pero para que veas el resultado "real" de esas conversiones de ahí
arriba:
De otras bases a decimal
========================
101011011 en base 2 -> 347
10765478 en base 8 -> 294247
FF00125E0 en base 16 -> 68451116512
GUILLEMOLA en base 36 -> 1711050165663742
ELGUILLEMOLA en base 36 -> 1921194231198726142
De decimal a otras bases
========================
101011011 en base 2 -> 347
1076547 en base 8 -> 294247
FF00125E0 en base 16 -> 68451116512
GUILLEMOLA en base 36 -> 1711050165663742
ELGUILLEMOLA en base 36 -> 1921194231198726142
En este segundo ejemplo, el tipo de datos que estoy usando es BigInt,
mientras que en el
anterior es UInt64 (ulong).
Espero que te sea de utilidad.
Nos vemos.
Guillermo
Abajo tienes el código completo de la clase, tanto para Visual
Basic como para
C#, y más abajo todavía, tienes el link para
bajar los proyectos
para Visual Basic 2005 y Visual C# 2005.
Decirte que no lo he probado en otras versiones anteriores, pero debería
funcionar, al menos el
código de C#,
ya que en las versiones anteriores de Visual Basic no disponíamos del tipo
ULong, pero de
querer convertirlo, puedes cambiarlo por Decimal y casi seguro que te
funciona, pero no lo he
probado, así que... solo garantizo que funciona en las versiones 2005 o
superiores.
El programa de ejemplo que usa la clase Conversor:
Para Visual Basic 2005:
Dim nums() As String = {"101011011", "10765478", "FF00125E0", _
"GUILLEMOLA", "ELGUILLEMOLA"}
Dim bases() As Integer = {2, 8, 16, 36, 36}
Dim n(0 To bases.Length - 1) As ULong
Console.WriteLine("De otras bases a decimal")
Console.WriteLine("========================")
For i As Integer = 0 To bases.Length - 1
n(i) = Conversor.FromNumBase(nums(i), bases(i))
Console.WriteLine("{0,16} en base {1,2} -> {2}", nums(i), bases(i), n(i))
Next
Console.WriteLine()
Console.WriteLine("De decimal a otras bases")
Console.WriteLine("========================")
For i As Integer = 0 To bases.Length - 1
nums(i) = Conversor.ToNumBase(n(i).ToString, bases(i))
Console.WriteLine("{0,16} en base {1,2} -> {2}", nums(i), bases(i), n(i))
Next
Console.WriteLine()
Para Visual C# 2005:
string[] nums = { "101011011", "10765478", "FF00125E0",
"GUILLEMOLA", "ELGUILLEMOLA" };
int[] bases = { 2, 8, 16, 36, 36 };
ulong[] n = new ulong[(bases.Length)];
Console.WriteLine("De otras bases a decimal");
Console.WriteLine("========================");
for(int i = 0; i < bases.Length; i++)
{
n[i] = Conversor.FromNumBase(nums[i], bases[i]);
Console.WriteLine("{0,16} en base {1,2} -> {2}",
nums[i], bases[i], n[i]);
}
Console.WriteLine();
Console.WriteLine("De decimal a otras bases");
Console.WriteLine("========================");
for(int i = 0; i < bases.Length; i++)
{
nums[i] = Conversor.ToNumBase(n[i].ToString(), bases[i]);
Console.WriteLine("{0,16} en base {1,2} -> {2}",
nums[i], bases[i], n[i]);
}
Console.WriteLine();
El código para Visual Basic 2005 o superior
'------------------------------------------------------------------------------
' Clase Conversor (31/Oct/07)
' Para convertir valores decimales a cualquier otra base numérica
'
' ©Guillermo 'guille' Som, 2007
'------------------------------------------------------------------------------
Option Strict On
Imports Microsoft.VisualBasic
Imports System
Imports System.Collections.Generic
Imports System.Text
'Imports System.Diagnostics
Namespace elGuille.Developer
''' <summary>
''' Clase para convertir bases numéricas
''' </summary>
''' <remarks></remarks>
Public Class Conversor
'
' Funciones de conversión a otras bases
'
''' <summary>
''' Convierte un número decimal en una base distinta
''' Las bases permitadas son de 2 a 36
''' </summary>
''' <param name="num"></param>
''' <param name="nBase">
''' Base a la que se convertirá (de 2 a 36)
''' (con los tipos de .NET ñla base 36 no es fiable)
''' </param>
''' <param name="conSeparador">
''' Si se añade un separador cada 4 cifras
''' </param>
''' <param name="trimStart">
''' Si se quitan los ceros a la izquierda
''' </param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function ToNumBase(ByVal num As String, _
ByVal nBase As Integer, _
ByVal conSeparador As Boolean, _
ByVal trimStart As Boolean) As String
Dim s As New StringBuilder
Dim n As ULong = CULng(num)
If n = 0 AndAlso conSeparador = False AndAlso trimStart = False Then
Return "0"
End If
' La base debe ser como máximo 36
' (por las letras del alfabeto (26) + 10 dígitos)
' F = 70 - 55 + 1 = 16
' Z = 90 - 55 + 1 = 36
If nBase < 2 OrElse nBase > 36 Then
Throw New ArgumentOutOfRangeException( _
"La base debe ser como máximo 36 y como mínimo 2")
End If
Dim j As Integer = 0
'Dim nu As Double = n
Dim nu As Decimal = n
While nu > 0
'Dim k As Double = (nu / nBase)
Dim k As Decimal = CDec(nu) / CDec(nBase)
nu = Fix(k)
Dim f As Integer = CInt((k - nu) * nBase)
Select Case f
Case Is > 9 ' letras
s.Append(ChrW(f + 55))
Case Else ' números
s.Append(ChrW(f + 48))
End Select
If conSeparador Then
j = j + 1
If j = 4 Then
j = 0
s.Append(" ")
End If
End If
End While
' Hay que darle la vuelta a la cadena
Dim ac() As Char = s.ToString.ToCharArray
Array.Reverse(ac)
s = New StringBuilder(ac)
If trimStart Then
Return s.ToString.TrimStart(" 0".ToCharArray).TrimEnd
Else
Return s.ToString.TrimEnd
End If
End Function
''' <summary>
''' Convierte de cualquier base a Double
''' </summary>
''' <param name="num">
''' El número en formato de la base
''' </param>
''' <param name="base">
''' La base del número a convertir
''' </param>
''' <returns></returns>
''' <remarks>
''' </remarks>
Public Shared Function FromNumBase(ByVal num As String, _
ByVal base As Integer) As ULong
Const aMay As Integer = AscW("A") - 10
Const aMin As Integer = AscW("a") - 10
Dim i As Integer = 0
Dim n As ULong = 0
num = num.TrimStart("0".ToCharArray)
For j As Integer = num.Length - 1 To 0 Step -1
Select Case num(j).ToString
Case "0"
i += 1
Case " "
' nada
Case "1" To "9"
Dim k As Integer = CInt(num(j).ToString)
If k - base >= 0 Then Continue For
'n = CULng(n + k * System.Math.Pow(base, i))
n = n + CULng(k * System.Math.Pow(base, i))
i += 1
Case "A" To "Z"
Dim k As Integer = AscW(num(j)) - aMay
If k - base >= 0 Then Continue For
'n = CULng(n + k * System.Math.Pow(base, i))
n = n + CULng(k * System.Math.Pow(base, i))
i += 1
Case "a" To "z"
Dim k As Integer = AscW(num(j)) - aMin
If k - base >= 0 Then Continue For
'n = CULng(n + k * System.Math.Pow(base, i))
n = n + CULng(k * System.Math.Pow(base, i))
i += 1
End Select
Next
Return n
End Function
'
' Sobrecargas
'
''' <summary>
''' Convierte un número decimal en una base distinta
''' Las bases permitadas son de 2 a 36
''' No se muestran los ceros a la izquierda y
''' no se separan los dígitos en grupos de 4
''' </summary>
''' <param name="num"></param>
''' <param name="nBase"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function ToNumBase(ByVal num As String, _
ByVal nBase As Integer) As String
Return ToNumBase(num, nBase, False, True)
End Function
''' <summary>
''' Convierte un número decimal en una base distinta
''' Las bases permitadas son de 2 a 36
''' no se separan los dígitos en grupos de 4
''' </summary>
''' <param name="num"></param>
''' <param name="nBase"></param>
''' <param name="trimStart"></param>
''' <returns></returns>
''' <remarks></remarks>
Public Shared Function ToNumBase(ByVal num As String, _
ByVal nBase As Integer, _
ByVal trimStart As Boolean) As String
Return ToNumBase(num, nBase, False, trimStart)
End Function
'
' Funciones específicas
'
''' <summary>
''' Convierte de Decimal a binario (base 2)
''' </summary>
''' <param name="num">
''' El número a convertir en binario
''' se convierte internamente a ULong como máximo
''' </param>
''' <param name="conSeparador"></param>
''' <param name="trimStart"></param>
''' <returns></returns>
''' <remarks>
''' </remarks>
Public Shared Function DecToBin(ByVal num As String, _
Optional ByVal conSeparador As Boolean = True, _
Optional ByVal trimStart As Boolean = True) As String
Return ToNumBase(num, 2, conSeparador, trimStart)
End Function
''' <summary>
''' Convierte de Decimal a hexadecimal (base 16)
''' </summary>
''' <param name="num">
''' El número a convertir en hexadecimal
''' se convierte internamente a ULong como máximo
''' </param>
''' <param name="conSeparador"></param>
''' <param name="trimStart"></param>
''' <returns></returns>
''' <remarks>
''' </remarks>
Public Shared Function DecToHex(ByVal num As String, _
Optional ByVal conSeparador As Boolean = True, _
Optional ByVal trimStart As Boolean = True) As String
Return ToNumBase(num, 16, conSeparador, trimStart)
End Function
''' <summary>
''' Convierte de Decimal a octal (base 8)
''' </summary>
''' <param name="num">
''' El número a convertir en binario
''' se convierte internamente a ULong como máximo
''' </param>
''' <param name="conSeparador"></param>
''' <param name="trimStart"></param>
''' <returns></returns>
''' <remarks>
''' </remarks>
Public Shared Function DecToOctal(ByVal num As String, _
Optional ByVal conSeparador As Boolean = True, _
Optional ByVal trimStart As Boolean = True) As String
Return ToNumBase(num, 8, conSeparador, trimStart)
End Function
''' <summary>
''' Convierte de Hexadecimal a Double
''' </summary>
''' <param name="num"></param>
''' <returns></returns>
''' <remarks>
''' </remarks>
Public Shared Function FromHex(ByVal num As String) As Double
Return FromNumBase(num, 16)
End Function
''' <summary>
''' Convierte de Octal a Double
''' </summary>
''' <param name="num"></param>
''' <returns></returns>
''' <remarks>
''' </remarks>
Public Shared Function FromOct(ByVal num As String) As Double
Return FromNumBase(num, 8)
End Function
''' <summary>
''' Convierte de Binario a Double
''' </summary>
''' <param name="num"></param>
''' <returns></returns>
''' <remarks>
''' </remarks>
Public Shared Function FromBin(ByVal num As String) As Double
Return FromNumBase(num, 2)
End Function
End Class
End Namespace
El código para C# (cualquier versión)
//-----------------------------------------------------------------------------
// Clase Conversor (31/Oct/07)
// Para convertir valores decimales a cualquier otra base numérica
//
// ©Guillermo 'guille' Som, 2007
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Text;
namespace elGuille.Developer
{
/// <summary>
/// Clase para convertir bases numéricas
/// </summary>
/// <remarks></remarks>
public class Conversor
{
//
// Funciones de conversión a otras bases
//
/// <summary>
/// Convierte un número decimal en una base distinta
/// Las bases permitadas son de 2 a 36
/// </summary>
/// <param name="num"></param>
/// <param name="nBase">
/// Base a la que se convertirá (de 2 a 36)
/// (con los tipos de .NET ñla base 36 no es fiable)
/// </param>
/// <param name="conSeparador">
/// Si se añade un separador cada 4 cifras
/// </param>
/// <param name="trimStart">
/// Si se quitan los ceros a la izquierda
/// </param>
/// <returns></returns>
/// <remarks></remarks>
public static string ToNumBase(string num,
int nBase,
bool conSeparador,
bool trimStart)
{
StringBuilder s = new StringBuilder();
ulong n = Convert.ToUInt64(num);
if(n == 0 && conSeparador == false && trimStart == false)
{
return "0";
}
// La base debe ser como máximo 36
// (por las letras del alfabeto (26) + 10 dígitos)
// F = 70 - 55 + 1 = 16
// Z = 90 - 55 + 1 = 36
if(nBase < 2 || nBase > 36)
{
throw new ArgumentOutOfRangeException(
"La base debe ser como máximo 36 y como mínimo 2");
}
int j = 0;
double nu = n;
while(nu > 0)
{
double k = (nu / nBase);
nu = System.Math.Floor(k);
int f = Convert.ToInt32((k - nu) * nBase);
if(f > 9)// letras
{
s.Append((char)(f + 55));
}
else // números
{
s.Append((char)(f + 48));
}
if(conSeparador)
{
j = j + 1;
if(j == 4)
{
j = 0;
s.Append(" ");
}
}
}
// Hay que darle la vuelta a la cadena
char[] ac = s.ToString().ToCharArray();
Array.Reverse(ac);
s = new StringBuilder();
foreach(char c in ac)
s.Append(c);
if(trimStart)
{
return s.ToString().TrimStart(" 0".ToCharArray()).TrimEnd();
}
else
{
return s.ToString().TrimEnd();
}
}
//
// Sobrecargas
//
/// <summary>
/// Convierte un número decimal en una base distinta
/// Las bases permitadas son de 2 a 36
/// No se muestran los ceros a la izquierda y
/// no se separan los dígitos en grupos de 4
/// </summary>
/// <param name="num"></param>
/// <param name="nBase"></param>
/// <returns></returns>
/// <remarks></remarks>
public static string ToNumBase(string num, int nBase)
{
return ToNumBase(num, nBase, false, true);
}
/// <summary>
/// Convierte un número decimal en una base distinta
/// Las bases permitadas son de 2 a 36
/// no se separan los dígitos en grupos de 4
/// </summary>
/// <param name="num"></param>
/// <param name="nBase"></param>
/// <param name="trimStart"></param>
/// <returns></returns>
/// <remarks></remarks>
public static string ToNumBase(string num, int nBase, bool trimStart)
{
return ToNumBase(num, nBase, false, trimStart);
}
//
// Funciones específicas
//
/// <summary>
/// Convierte de Decimal a binario (base 2)
/// </summary>
/// <param name="num">
/// El número a convertir en binario
/// se convierte internamente a ULong como máximo
/// </param>
/// <param name="conSeparador"></param>
/// <param name="trimStart"></param>
/// <returns></returns>
/// <remarks>
/// v.0.11 29/Oct/07
/// </remarks>
public static string DecToBin(string num, bool conSeparador, bool trimStart)
{
return ToNumBase(num, 2, conSeparador, trimStart);
}
/// <summary>
/// Convierte de Decimal a hexadecimal (base 16)
/// </summary>
/// <param name="num">
/// El número a convertir en hexadecimal
/// se convierte internamente a ULong como máximo
/// </param>
/// <param name="conSeparador"></param>
/// <param name="trimStart"></param>
/// <returns></returns>
/// <remarks>
/// </remarks>
public static string DecToHex(string num, bool conSeparador, bool trimStart)
{
return ToNumBase(num, 16, conSeparador, trimStart);
}
/// <summary>
/// Convierte de Decimal a octal (base 8)
/// </summary>
/// <param name="num">
/// El número a convertir en binario
/// se convierte internamente a ULong como máximo
/// </param>
/// <param name="conSeparador"></param>
/// <param name="trimStart"></param>
/// <returns></returns>
/// <remarks>
/// </remarks>
public static string DecToOctal(string num, bool conSeparador, bool trimStart)
{
return ToNumBase(num, 8, conSeparador, trimStart);
}
/// <summary>
/// Convierte de cualquier base a Double
/// </summary>
/// <param name="num">
/// El número en formato de la base
/// </param>
/// <param name="base">
/// La base del número a convertir
/// </param>
/// <returns></returns>
/// <remarks>
/// </remarks>
public static ulong FromNumBase(string num, int nbase)
{
const int aMay = 'A' - 10;
const int aMin = 'a' - 10;
int i = 0;
ulong n = 0;
num = num.TrimStart("0".ToCharArray());
for(int j = num.Length - 1; j >= 0; j--)
{
if(num[j] == '0')
{
i += 1;
}
else if(num[j] == ' ')
{
// nada
}
else if(num[j] >= '1' && num[j] <= '9')
{
int k = num[j] - 48;
if(k - nbase >= 0)
continue;
n = (ulong)(n + k * System.Math.Pow(nbase, i));
i += 1;
}
else if(num[j] >= 'A' && num[j] <= 'Z')
{
int k = num[j] - aMay;
if(k - nbase >= 0)
continue;
n = (ulong)(n + k * System.Math.Pow(nbase, i));
i += 1;
}
else if(num[j] >= 'a' && num[j] <= 'z')
{
int k = num[j] - aMin;
if(k - nbase >= 0)
continue;
n = (ulong)(n + k * System.Math.Pow(nbase, i));
i += 1;
}
}
return n;
}
/// <summary>
/// Convierte de Hexadecimal a Double
/// </summary>
/// <param name="num"></param>
/// <returns></returns>
/// <remarks>
/// </remarks>
public static double FromHex(string num)
{
return FromNumBase(num, 16);
}
/// <summary>
/// Convierte de Octal a Double
/// </summary>
/// <param name="num"></param>
/// <returns></returns>
/// <remarks>
/// </remarks>
public static double FromOct(string num)
{
return FromNumBase(num, 8);
}
/// <summary>
/// Convierte de Binario a Double
/// </summary>
/// <param name="num"></param>
/// <returns></returns>
/// <remarks>
/// </remarks>
public static double FromBin(string num)
{
return FromNumBase(num, 2);
}
}
}
Espacios de nombres usados en el código de este artículo:
System.Text