Cambio de base en Python

Más ejercicios resueltos

Si deseas revisar más ejercicios resueltos, haz click en el siguiente botón. 

A continuación presentamos 2 alternativas de solución al problema de cambio de base 10 a cualquier base en Python. Si deseas ver el enunciado del problema, haz click en el siguiente enlace

Presentaremos la resolución del problema de cambio de base en dos versiones: una iterativa y otra recursiva. Ambas soluciones han sido construidas usando el paradigma de programación modular y usando la técnica de diseño descendente.

Versión iterativa

Comenzaremos con la versión iterativa del cambio de base. Como ya se mencionó anteriormente, este ejercicio lo resolveremos usando la técnica del diseño descendente. Empezaremos desde lo más general hasta lo más particular. En cada paso asumiremos que ya existen ciertas funciones listas y las usaremos, luego nos preocuparemos de su implementación.

Función principal

Iniciamos con la función principal. Esta función básicamente realiza la lectura de datos, la validación de los datos de entrada y finalmente, invoca a la función \texttt{imprime\_en\_otra\_base} quien se encarga en realidad de hacer el cambio de base.

En este punto, no nos interesa cómo se implementa \texttt{imprime\_en\_otra\_base}, ese detalle se verá después, lo que importa en este punto es qué hace dicha función. Dicha función se encargará de imprimir el número natural n pero en otra base. El número natural se representa con la variable \texttt{numero} y la base con la variable \texttt{base}.

 Esta función tiene como precondición que numero sea mayor que 1 y que se cumpla que 2 \leq base \leq 36. Al momento de invocarla, debemos garantizar que esta precondición se cumpla. Esa responsabilidad es de la función principal y no del módulo \texttt{imprime\_en\_otra\_base}. Como estamos usando la técnica de diseño descendente, es importante tener claramente definidas las responsabilidades de cada función.

numero=int(input("Ingrese número en base 10: "))
base=int(input("Ingrese base: "))

if base >= 2 and base <= 36:
    imprime_en_otra_base(numero, base)
else:
    print("Debe ingresar una base entre 2 y 36") 

Impresión en la otra base

Ahora procedemos a implementar la función \texttt{imprime\_en\_otra\_base}. Esta función, tal como su nombre lo indica, imprime un número en otra base. Recibe como parámetro \texttt{numero} que representa el número que queremos imprimir en otra base y \texttt{base} que representa la base en la cual se imprimirá el número. La precondición es: numero>1 y 2 \leq base \leq 36. La función no retorna ningún valor. A continuación se explicará paso a paso cómo se ha construido esta función.

Los algoritmos que comúnmente se encuentran en internet para el cambio de base, se basan en el método de divisiones sucesivas. Si no sabes como funciona el método de divisiones sucesivas, te invitamos a leer el artículo representación de números de números naturales en el computador en donde explicamos al detalle este método.

El problema que ocurre con el método de divisiones sucesivas es que cuando se realiza la primera división del número entre la base se obtiene el último dígito en la nueva base, por lo que no lo podemos imprimir. Si lo hacemos así el número se imprimirá al revés. Algunas soluciones intentan formar el nuevo dígito en una variable entera de forma tal que el nuevo número exprese el número en la nueva base, pero esto falla cuando la base es mayor que 10. Otros optan por usar arreglos o cadenas de caracteres, pero por restricción del enunciado, no podremos usarlas.

Entonces, ¿qué debemos hacer para que en la primera división se obtenga el primer dígito del número transformado? Bueno, la respuesta a esta pregunta se encuentra en las matemáticas. En lugar que la primera división sea entre la base, dividiremos entre una potencia de base. Pero, ¿qué potencia? Pues la mayor potencia de la base pero que sea menor o igual al número que queremos convertir.  En otras palabras realizaremos el método de divisiones sucesivas pero al revés.

En la función que hemos implementado, esta máxima potencia la almacenamos en la variable \texttt{factor}. La inicializamos con el valor que retorna la función \texttt{encuentra\_mayor\_potencia\_menor\_o\_igual\_a\_numero} que justamente tiene como objetivo retornar la mayor potencia de la base pero que sea menor o igual al número pasado como parámetro. La base también la recibe como parámetro.  Como estamos usando la técnica de diseño descendente, no nos preocuparemos en este momento por cómo se implementa esta función. Luego lo veremos.

La variable \texttt{factor} es fundamental en este algoritmo por que es la variable que controla el flujo de la iteración. Para la iteración hemos optado por una estructura iterativa con entrada controlada pero en realidad simulando una salida controlada. El  \texttt{break}, que se encuentra en la salida en realidad termina el flujo de ejecución.  Es en la práctica quien hace el control de flujo. Simulamos una salida controlada dado que como asumimos que \texttt{numero} es mayor que 1, por lo menos deberemos hacer una división. Seguiremos iterando mientras \texttt{factor} \neq \texttt{0}

¿Cómo obtenemos los dígitos a imprimir? Pues básicamente se hace una división entera entre \texttt{numero}\texttt{factor}. Esta operación retornará el \texttt{digito} que se debe imprimir. Dependiendo de la base, este \texttt{digito} podría ser un número mayor que 10, por lo que mejor encargamos la impresión del dígito a la función \texttt{imprime\_digito\_en\_base}. No nos preocuparemos en este momento por cómo se implementa esta función. Luego lo veremos.

Luego de haber realizado la división, tanto el \texttt{numero} como la \texttt{el factor} se deben actualizar. En el caso de \texttt{número}, lo actualizamos con el resto de la división entre \texttt{numero}\texttt{factor}. En el caso de \texttt{factor} lo dividimos entre la \texttt{base}.

Recuerde que \texttt{numero\%=factor} equivale a \texttt{numero=numero\%factor}\texttt{factor//=base} equivale a \texttt{factor=factor//base}.

def imprime_en_otra_base(numero, base):
    factor = encuentra_mayor_potencia_menor_o_igual_a_numero(numero, base)
    while True:
        digito = numero // factor
        imprime_digito_en_base(digito)
        numero %= factor
        factor //= base;
        if factor == 0:
            break 

Encontrando la potencia adecuada

Ahora analizaremos la implementación de la función \texttt{encuentra\_mayor\_potencia\_menor\_o\_igual\_a\_numero}. Esta función, tal como su nombre lo indica, calcula y retorna la mayor potencia de una base pero menor o igual a un determinado número. Recibe como parámetro \texttt{numero} que representa el número que servirá para verificar la mayor potencia y \texttt{base} que representa la base para la cual se hallará la potencia. La precondición es: numero>1 y 2 \leq base \leq 36. La función retorna un valor entero. A continuación explicaremos paso a paso cómo se ha construido esta función.

El algoritmo es muy simple de entender. En realidad es un productoria. Sigue la misma lógica que el factorial o la descomposición de factores primos. Utilizamos la variable \texttt{factor} que será la variable que retornaremos pero además será la variable que usaremos para el control de flujo. Inicializamos a \texttt{factor} con el valor de 1 y usando un ciclo iterativo con entrada controlada, lo vamos multiplicando por la \texttt{base}. Haremos esto mientras \texttt{factor<=numero}. Dado que estamos usando un ciclo con entrada controlada, cuando finalice la iteración será por que \texttt{factor>numero}. Como hemos realizado una multiplicación de más, antes de retornar el valor, debemos de dividirlo entre la \texttt{base}. Usamos para ello la operación \texttt{//} que corresponde a la división entera en Python.

Este factor también se puede calcular con logaritmos. Pero hemos preferido usar, en esta publicación, la versión iterativa para que sea entendida por todos, inclusive aun por los que no tienen conocimientos de logaritmos. Pero si deseas ver  cómo se puede implementar esta función con logaritmos, te invitamos a que visites nuestro artículo Cantidad de dígitos de un número en donde explicamos este tema al detalle.

def encuentra_mayor_potencia_menor_o_igual_a_numero(numero, base):
    factor = 1
    while factor <= numero:
        factor *= base
    return factor//base 

Imprimiendo el dígito en otra base

Finalmente revisaremos la implementación de la función \texttt{imprime\_digito\_en\_base}. Esta función tiene por objetivo imprimir el dígito pasado como parámetro. Recibe como parámetro \texttt{digito} que contiene la magnitud del dígito que deseamos imprimir. La precondición es: 0 \leq digito \leq 35. La función no retorna ningún valor. A continuación se explicará paso a paso cómo se ha construido esta función.

Esta función es muy simple de entender. Existen dos situaciones. La primera de ellas se da cuando el \texttt{digito} que queremos imprimir es menor que 10. Esta situación se da cuando la base usada para la conversión del número se encuentra en el rango de [2..10]. En este caso, la función imprime el dígito como número.

La otra situación se da cuando el \texttt{digito} que queremos imprimir es mayor o igual a 10. Esta situación es normal cuando la base usada para la conversión del número se encuentra en el rango de [11..36]. Pero, ¿cómo representaremos a los dígitos entonces? Pues con un caracter del alfabeto inglés. Cuando \texttt{digito}  contenga el valor de 10 imprimiremos el caracter \texttt{A}. Cuando \texttt{digito}  contenga el valor de 11 imprimiremos el caracter \texttt{B}. Cuando \texttt{digito}  contenga el valor de 12 imprimiremos el caracter \texttt{C}. Cuando \texttt{digito}  contenga el valor de 13 imprimiremos el caracter \texttt{D}. Y así sucesivamente.

Entonces, ¿cómo hacemos la transformación del dígito en caracter? Pues para esto primero debemos obtener el orden del caracter en el alfabeto. De forma que el orden de la \texttt{A} será 0, el orden de la \texttt{B} será 1, el orden de la \texttt{C} será 2  y así sucesivamente. ¿Cómo obtenemos el orden que le corresponde al dígito que queremos imprimir? Pues basta restarle 10 al dígito, pues el valor de 10 corresponde con el caracter \texttt{A} y es el primero de todos. Una vez que tenemos el orden del caracter que deseamos imprimir, simplemente se lo sumamos al código ASCII de \texttt{A}. De esta forma por ejemplo, si queremos imprimir el caracter cuyo orden es 4, haríamos la suma \texttt{ord(‘A’)+4}, lo que da el código ASCCI de \texttt{‘E’}.

Recuerde que en Python, la función \texttt{ord} retorna el código ASCII del caracter pasado como parámetro. Por su lado, la función \texttt{chr} retorna el caracter del código ASCII pasado como parámetro.

def imprime_digito_en_base(digito):
    if digito < 10:
        print("{:d}".format(digito), end="")
    else:
        digito_transformado = chr(digito - 10 + ord('A'))
        print("{}".format(digito_transformado), end="") 

Versión recursiva

Procederemos ahora a explicar la versión recursiva de esta solución. También ha sido implementada usando la técnica de diseño descendente pero con un par de alteraciones. Primero la función \texttt{imprime\_en\_otra\_base} la hemos implementado totalmente recursiva. Y segundo, la función \texttt{encuentra\_mayor\_potencia\_menor\_o\_igual\_a\_numero} no es necesaria en esta versión, veamos el porqué.

La versión recursiva la hemos implementado usando el método de divisiones sucesivas. Este método básicamente divide el número en cuestión entre la base a la cual quiere convertir hasta que el cociente de este división se haga cero. Luego de cada división, el resto contendrá el dígito a imprimir y la división sucesiva se realiza con el cociente. Si no sabes como funciona el método de divisiones sucesivas, te invitamos a leer el artículo representación de números de números naturales en el computador en donde explicamos al detalle este método.

Habíamos comentado que no podíamos usar el método de divisiones sucesivas pues la primera división nos retornaba el último dígito a imprimir. Y esto es verdad en la solución iterativa, pero si usamos recursión, al poner la impresión luego de la llamada recursiva, hace que primero se imprima el siguiente el dígito de forma recursiva y luego se imprima el dígito actual. De esta forma, la recursión hace posible que dejemos de lado la función \texttt{encuentra\_mayor\_potencia\_menor\_o\_igual\_a\_numero}.

def imprime_en_otra_base(numero, base):
    if numero > 0:
        digito = numero % base
        numero //= base
        imprime_en_otra_base(numero, base)
        imprime_digito_en_base(digito) 

Conclusión

Hemos presentado en este artículo, 2 propuestas de solución al problema de cambio de base 10 a cualquier base usando Python. Se ha utilizado para el diseño algorítmico la técnica del diseño descendente y se ha controlado  el flujo en los módulos usando estructuras selectivas e iterativas. Podrá descargar la solución propuesta en el repositorio GitHub de iterando++ a través del siguiente enlace

Hemos preparado otros artículos adicionales en donde describimos al detalle la implementación de este problema PSeInt y en otros lenguajes de programación. Te invitamos a leer los siguientes artículos de iterando++

Si te interesa profundizar más en el desarrollo en Python, los dos mejores libros que se han escrito son Learning Python de Mark Lutz y Python Crash Course de Eric Matthes.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *