Exponenciación binaria por factorización
Consideremos el problema de calcular , dados enteros , , y , donde es impar.
El algoritmo de abajo permite resolver este problema con sumas y operaciones binarias y una sola multiplicación por .
Por la estructura del grupo multiplicativo módulo , cualquier número tal que se puede representar como
donde . Sin pérdida de generalidad asumimos que , ya que podemos reducir a sustituyendo y . En esta notación, se representa como
La idea central del algoritmo es simplificar el cálculo de y usando el hecho de que trabajamos módulo . Por razones que quedarán claras más adelante, trabajaremos con en lugar de , pero tomado módulo en vez de .
En este artículo cubriremos la implementación para enteros de bits. Sean
mbin_log_32(r, x)una función que calcula ;mbin_exp_32(r, x)una función que calcula ;mbin_power_odd_32(a, x, y)una función que calcula .
Entonces mbin_power_odd_32 se implementa así:
uint32_t mbin_power_odd_32(uint32_t rem, uint32_t base, uint32_t exp) {
if (base & 2) {
/* el divisor se considera negativo */
base = -base;
/* comprobar si el resultado debe ser negativo */
if (exp & 1) {
rem = -rem;
}
}
return (mbin_exp_32(rem, mbin_log_32(0, base) * exp));
}Cálculo de 4L(x) a partir de x
Sea un número impar tal que . Se puede representar como
donde . Aquí está bien definido para cada factor, porque son iguales a módulo . Por lo tanto,
Así, si precomputamos para todo , podremos calcular para cualquier número .
Para enteros de 32 bits podemos usar la siguiente tabla:
const uint32_t mbin_log_32_table[32] = {
0x00000000, 0x00000000, 0xd3cfd984, 0x9ee62e18,
0xe83d9070, 0xb59e81e0, 0xa17407c0, 0xce601f80,
0xf4807f00, 0xe701fe00, 0xbe07fc00, 0xfc1ff800,
0xf87ff000, 0xf1ffe000, 0xe7ffc000, 0xdfff8000,
0xffff0000, 0xfffe0000, 0xfffc0000, 0xfff80000,
0xfff00000, 0xffe00000, 0xffc00000, 0xff800000,
0xff000000, 0xfe000000, 0xfc000000, 0xf8000000,
0xf0000000, 0xe0000000, 0xc0000000, 0x80000000,
};En la práctica se usa un enfoque un poco distinto del descrito arriba. En lugar de encontrar la factorización de , multiplicaremos sucesivamente por hasta convertirlo en módulo . De este modo encontraremos la representación de , es decir
Para ello iteramos sobre tales que . Si el actual tiene el -ésimo bit activado, multiplicamos por , lo cual en C++ se hace convenientemente como x = x + (x << n). Esto no cambia los bits menores que nx$ es impar.
Con todo esto en mente, la función mbin_log_32(r, x) se implementa así:
uint32_t mbin_log_32(uint32_t r, uint32_t x) {
uint8_t n;
for (n = 2; n < 32; n++) {
if (x & (1 << n)) {
x = x + (x << n);
r -= mbin_log_32_table[n];
}
}
return r;
}Nótese que , así que en lugar de sumar , lo restamos de , que inicialmente vale .
Cálculo de x a partir de 4L(x)
Nótese que para se cumple
de donde (elevando al cuadrado repetidamente) se deduce que
Aplicando este resultado a y deducimos que el orden multiplicativo de es un divisor de .
Esto, a su vez, significa que debe ser divisible por , porque el orden de es y el orden de es , donde es la mayor potencia de que divide a , así que necesitamos
por lo tanto debe ser mayor o igual que . Esto es un poco feo y para mitigarlo dijimos al principio que multiplicamos por . Ahora, si conocemos , podemos descomponerlo de forma única como suma de revisando sucesivamente los bits de . Si el -ésimo bit está en , multiplicamos el resultado por y reducimos el actual en .
Así, mbin_exp_32 se implementa de la siguiente forma:
uint32_t mbin_exp_32(uint32_t r, uint32_t x) {
uint8_t n;
for (n = 2; n < 32; n++) {
if (x & (1 << n)) {
r = r + (r << n);
x -= mbin_log_32_table[n];
}
}
return r;
}Optimizaciones adicionales
Es posible reducir a la mitad el número de iteraciones si se observa que y que para se cumple
lo que permite deducir que para . Así, se puede simplificar el algoritmo recorriendo solo hasta y luego usar el hecho anterior para calcular la parte restante con operaciones de bits:
uint32_t mbin_log_32(uint32_t r, uint32_t x) {
uint8_t n;
for (n = 2; n != 16; n++) {
if (x & (1 << n)) {
x = x + (x << n);
r -= mbin_log_32_table[n];
}
}
r -= (x & 0xFFFF0000);
return r;
}
uint32_t mbin_exp_32(uint32_t r, uint32_t x) {
uint8_t n;
for (n = 2; n != 16; n++) {
if (x & (1 << n)) {
r = r + (r << n);
x -= mbin_log_32_table[n];
}
}
r *= 1 - (x & 0xFFFF0000);
return r;
}Cálculo de la tabla de logaritmos
Para calcular la tabla de logaritmos, se puede modificar el algoritmo de Pohlig–Hellman para el caso en que el módulo es una potencia de .
Nuestra tarea principal aquí es calcular tal que , donde e es un número de la forma .
Elevando ambos lados al cuadrado veces llegamos a
Nótese que el orden de no es mayor que (de hecho, que , pero nos quedaremos con por conveniencia); por lo tanto, usando tendremos o en el lado izquierdo, lo que nos permite determinar el bit más pequeño de comparando con . Ahora supongamos que , donde es una parte conocida y todavía no se conoce. Entonces
Multiplicando ambos lados por , obtenemos
Ahora, elevando ambos lados al cuadrado veces podemos obtener el siguiente bit de , y eventualmente recuperar todos sus bits.