Para poder trabajar con números grandes, lo primero que debemos definir es como los vamos a guardar. La forma más práctica es guardarlos en un arreglo, ya que además de facilitar su uso, hace que los podamos acomodar de una manera más intuitiva. Este arreglo puede ser estático o dinámico según se necesite. En cada casilla del arreglo vamos anotando partes del número, a las cuales en lo sucesivo les llamaremos bloque. También vamos guardando el tamaño en bloques del número en una variable. Tomando esto en cuenta, el número queda descompuesto de la siguiente forma:
N = (n0×b0) + (n1×b1) + … + (nk-1×bk-1)
Aquí N es el número grande original, b es la base en la que estamos trabajando, ni es el i-ésimo bloque y k es el tamaño en bloques. Para hacer esto, debemos escoger la base con la cual vamos a trabajar y el tipo de variable en la cual vamos a guardar los datos. En este capítulo vamos a trabajar con una variable tipo longint la cual maneja números de hasta 9 dígitos (32 bits) y una base 109 de tal forma que podamos aprovechar la mayor parte del número, quede suficiente espacio para detectar un desbordamiento y además podemos leer el número como si estuviera en base 10.
En aplicaciones prácticas generalmente es más eficiente convertir todos los números decimales a binarios, hacer todas las operaciones en binario y después convertirlos otra vez a decimal para presentar el resultado. No vamos a hacer esto por la siguiente razón: necesitamos contar con una muy buena implementación de un algoritmo convertidor de bases, y dicho algoritmo es mucho más complejo que los algoritmos que se van a presentar aquí. Además de que dentro de un concurso no se necesita tal grado de eficiencia.
Lo siguiente que debemos decidir es escoger entre si el arreglo que vamos a utilizar es estático o dinámico. Dentro del capítulo todas las implementaciones las haremos con arreglos estáticos, ya que presenta algunas ventajas como lo son: mayor facilidad para crear el código además de tener una mayor velocidad, ya que todo el número es puesto en memoria de forma contigua. La principal desventaja es el hecho de que debemos conocer la magnitud máxima del los números a trabajar antes de hacer los cálculos.
En el caso del costo en memoria, ambas implementaciones tienen sus desventajas: el arreglo estático siempre va a ocupar la misma cantidad de memoria por lo que no resulta práctico cuando la mayoría de los datos son pequeños; el arreglo dinámico necesita además de los números, un puntero al siguiente bloque por lo que en números muy grandes se ocupa más memoria.
Por último, en ambos casos, la magnitud máxima de los números a trabajar está limitada por las capacidades de la computadora y por la capacidad de la variable donde guardemos el tamaño del número. Nuestra implementación utiliza un longint, cuya capacidad es de 231 bits (4 bytes, contando el bit de signo), y en cada bloque estamos metiendo 9 dígitos. Por lo tanto, podemos utilizar números que tengan más de 1.9×1010 dígitos, dado que tengamos 8 Gigabytes de memoria.
Veamos como quedaría la estructura de un número grande en código:
En las primeras cuatro líneas definimos las constantes que vamos a utilizar para el manejo de números grandes. Aquí lon es la longitud en dígitos de cada bloque, max es el número máximo de dígitos que vamos a utilizar y carry es la base. Es importante que la base (carry) contenga exactamente lon ceros para que los algoritmos funcionen correctamente. Una alternativa para no tener complicaciones con esto, es definir cualquiera de las dos como variable global y calcularla al principio del programa de acuerdo al otro valor. Carry es el equivalente en inglés del “llevo”.
Las constantes lon y carry las escogemos nosotros de acuerdo al tipo de datos que vamos a utilizar, y dado que estamos utilizando variables longint lo más conveniente es que sean 9 dígitos. La variable max la tenemos que precalcular de acuerdo a las especificaciones del problema (el valor que tiene en este momento es meramente demostrativo).
Después definimos la estructura que van a utilizar los números grandes (líneas 6 a 10). En tam guardamos el tamaño variable (dinámico) del número, mientras que cada bloque lo almacenamos dentro de bloq.
Código en C