Algoritmo de Garner
Una consecuencia del Teorema Chino del Resto es que podemos representar números grandes usando un arreglo de enteros pequeños. Por ejemplo, sea el producto de los primeros primos. tiene alrededor de dígitos.
Cualquier número menor que se puede representar como un arreglo , donde . Pero para hacer esto, obviamente, necesitamos saber cómo recuperar el número a partir de su representación. Una forma se discute en el artículo sobre el Teorema Chino del Resto.
En este artículo discutimos una alternativa, el algoritmo de Garner, que también se puede usar para este propósito.
Representación en base mixta
Podemos representar el número en la representación de base mixta (mixed radix):
Una representación en base mixta es un sistema de numeración posicional, que generaliza los sistemas numéricos típicos, como el sistema binario o el sistema decimal. Por ejemplo, el sistema decimal es un sistema de numeración posicional con radix (o base) 10. Todo número se representa como una cadena de dígitos entre y . Por ejemplo, la cadena representa el número . En general, la cadena de dígitos representa el número en el sistema de numeración posicional con radix .
En un sistema de base mixta ya no tenemos un único radix. La base varía de una posición a otra.
Algoritmo de Garner
El algoritmo de Garner calcula los dígitos . Nótese que los dígitos son relativamente pequeños. El dígito es un entero entre y .
Sea el inverso de módulo
que se puede encontrar usando el algoritmo descrito en Inverso modular.
Sustituyendo de la representación en base mixta en la primera ecuación de congruencia obtenemos
Sustituyendo en la segunda ecuación se obtiene
que se puede reescribir restando y dividiendo por para llegar a
De forma similar obtenemos que
Ahora se ve claramente un patrón emergente, que se puede expresar con el siguiente código:
for (int i = 0; i < k; ++i) {
x[i] = a[i];
for (int j = 0; j < i; ++j) {
x[i] = r[j][i] * (x[i] - x[j]);
x[i] = x[i] % p[i];
if (x[i] < 0)
x[i] += p[i];
}
}Así aprendimos a calcular los dígitos en tiempo . El número se puede calcular ahora usando la fórmula mencionada antes
Vale la pena señalar que, en la práctica, casi con seguridad necesitamos calcular la respuesta usando aritmética de precisión arbitraria, pero los dígitos (porque son pequeños) suelen poder calcularse con tipos nativos, y por eso el algoritmo de Garner es muy eficiente.
Implementación del algoritmo de Garner
Es conveniente implementar este algoritmo en Java, porque tiene soporte nativo para números grandes a través de la clase BigInteger.
Aquí mostramos una implementación que puede almacenar números grandes en forma de un conjunto de ecuaciones de congruencia. Soporta suma, resta y multiplicación. Y con el algoritmo de Garner podemos convertir el conjunto de ecuaciones en el entero único. En este código tomamos 100 números primos mayores que , lo que permite representar números tan grandes como .
final int SZ = 100;
int pr[] = new int[SZ];
int r[][] = new int[SZ][SZ];
void init() {
for (int x = 1000 * 1000 * 1000, i = 0; i < SZ; ++x)
if (BigInteger.valueOf(x).isProbablePrime(100))
pr[i++] = x;
for (int i = 0; i < SZ; ++i)
for (int j = i + 1; j < SZ; ++j)
r[i][j] =
BigInteger.valueOf(pr[i]).modInverse(BigInteger.valueOf(pr[j])).intValue();
}
class Number {
int a[] = new int[SZ];
public Number() {
}
public Number(int n) {
for (int i = 0; i < SZ; ++i)
a[i] = n % pr[i];
}
public Number(BigInteger n) {
for (int i = 0; i < SZ; ++i)
a[i] = n.mod(BigInteger.valueOf(pr[i])).intValue();
}
public Number add(Number n) {
Number result = new Number();
for (int i = 0; i < SZ; ++i)
result.a[i] = (a[i] + n.a[i]) % pr[i];
return result;
}
public Number subtract(Number n) {
Number result = new Number();
for (int i = 0; i < SZ; ++i)
result.a[i] = (a[i] - n.a[i] + pr[i]) % pr[i];
return result;
}
public Number multiply(Number n) {
Number result = new Number();
for (int i = 0; i < SZ; ++i)
result.a[i] = (int)((a[i] * 1l * n.a[i]) % pr[i]);
return result;
}
public BigInteger bigIntegerValue(boolean can_be_negative) {
BigInteger result = BigInteger.ZERO, mult = BigInteger.ONE;
int x[] = new int[SZ];
for (int i = 0; i < SZ; ++i) {
x[i] = a[i];
for (int j = 0; j < i; ++j) {
long cur = (x[i] - x[j]) * 1l * r[j][i];
x[i] = (int)((cur % pr[i] + pr[i]) % pr[i]);
}
result = result.add(mult.multiply(BigInteger.valueOf(x[i])));
mult = mult.multiply(BigInteger.valueOf(pr[i]));
}
if (can_be_negative)
if (result.compareTo(mult.shiftRight(1)) >= 0)
result = result.subtract(mult);
return result;
}
}