He estado tratando de implementar el algoritmo de multiplicación de enteros de Schönhage-Strassen, pero me topé con un obstáculo en el paso recursivo.
Tengo un valor con bits y quiero calcular x ^ 2 \ pmod {2 ^ n + 1} . Originalmente pensé que la idea era elegir una k tal que 4 ^ k \ geq 2n , dividir x en 2 ^ k piezas cada una con 2 ^ {k-1} bits, aplicar la convolución de SSA mientras se trabaja el módulo 2 ^ {2 ^ k} +1 , un anillo con 2 ^ k bits de capacidad por valor, luego vuelve a juntar las piezas. Sin embargo, la salida de la convolución tiene un poco más de 2n bits (es decir, > 2 ^ kn4 k ≥ 2 n x 2 k 2 k - 1 2 2 k + 1 2 k 2 n > 2 kbits por valor de salida, que es más que la capacidad del anillo, debido a que cada valor de salida es una suma de varios productos), por lo que esto no funciona. Tuve que agregar un factor adicional de 2 de relleno.
Ese factor adicional de 2 en el relleno arruina la complejidad. Hace que mi paso recursivo sea demasiado caro. En lugar de un algoritmo , termino con un algoritmo .
Leí algunas referencias vinculadas desde wikipedia, pero todas parecen pasar por alto los detalles de cómo se resuelve este problema. Por ejemplo, podría evitar la sobrecarga de relleno adicional trabajando el módulo para un que no es una potencia de 2 ... pero luego las cosas se rompen más tarde, cuando solo tengo no poder- de-2 factores restantes y no se puede aplicar Cooley-Tukey sin duplicar el número de piezas. Además, puede no tener un módulo inverso multiplicativo . Entonces todavía hay factores forzados de 2 introducidos.
¿Cómo elijo el anillo para usar durante el paso recursivo, sin soplar la complejidad asintótica?
O, en forma de pseudocódigo:
multiply_in_ring(a, b, n):
...
// vvv vvv //
// vvv HOW DOES THIS PART WORK? vvv //
// vvv vvv //
let inner_ring = convolution_ring_for_values_of_size(n);
// ^^^ ^^^ //
// ^^^ HOW DOES THIS PART WORK? ^^^ //
// ^^^ ^^^ //
let input_bits_per_piece = ceil(n / inner_ring.order);
let piecesA = a.splitIntoNPiecesOfSize(inner_ring.order, input_bits_per_piece);
let piecesB = b.splitIntoNPiecesOfSize(inner_ring.order, input_bits_per_piece);
let piecesC = inner_ring.negacyclic_convolution(piecesA, piecesB);
...