Как выбирается внутреннее кольцо в алгоритме Шёнхаге-Штрассена?


9

Я пытался реализовать алгоритм умножения целых чисел Шёнхаге-Штрассена, но натолкнулся на камень преткновения на рекурсивном этапе.

У меня есть значение с битами, и я хочу вычислить . Первоначально я думал, что идея состоит в том, чтобы выбрать , чтобы 4 ^ k \ geq 2n , разбить x на 2 ^ k частей, каждый с 2 ^ {k-1} битами, применить свертку SSA во время работы по модулю 2 ^ {2 ^ k} +1 , кольцо с 2 ^ k битами емкости на значение, затем соедините части вместе. Однако вывод свертки имеет чуть более 2n бит (т.е. > 2 ^ kxnx2(mod2n+1)k4k≥2nx2k2k−122k+12k2n>2kбит на выходное значение, которое больше емкости кольца, поскольку каждое выходное значение является суммой нескольких продуктов), поэтому это не работает. Я должен был добавить дополнительный коэффициент 2 дополнения.

Этот дополнительный фактор 2 в заполнении разрушает сложность. Это делает мой рекурсивный шаг слишком дорогим. Вместо алгоритма F(n)=nlg⁡n+nF(2n)=Θ(nlg⁡nlg⁡lg⁡n) я получаю с алгоритмом F(n)=nlg⁡n+nF(4n)=Θ(nlg2⁡n) .

Я прочитал несколько ссылок, связанных с Википедией, но все они, кажется, затушевывают детали того, как эта проблема решается. Например, я мог бы избежать дополнительных накладных расходов, работая по модулю 2p2k+1 для p который не является степенью 2 ... но тогда все просто сломается позже, когда у меня только не-power- из 2 факторов осталось и не может применить Кули-Тьюки без удвоения количества фигур. Кроме того, p может не иметь мультипликативного обратного по модулю 2p+1 . Таким образом, все еще существуют вынужденные факторы 2.

Как выбрать кольцо для использования во время рекурсивного шага, не увеличивая асимптотическую сложность?

Или в форме псевдокода:

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);
  ...

Пожалуйста , не размещайте один и тот же вопрос на нескольких сайтах . Каждое сообщество должно иметь честный ответ, не теряя никого времени. Я предлагаю вам удалить одну из двух копий.
— DW

@DW Готово. Я кросс-постил после того, как cs не давал никаких ответов в течение недели, полагая, что это было слишком сложно для этого сайта. Собирался ссылаться на любые ответы, очевидно.
— Крейг Гидни

Я понимаю. Если это произойдет в будущем, вы всегда можете пометить свое сообщение для модератора и попросить перенести его, и мы можем переместить его для вас в CSTheory. Спасибо за понимание!
— DW

3
Существует версия алгоритма, которая работает по модулю чисел вида : А. Шенхаге. Асимптотически быстрые алгоритмы численного умножения и деления полиномов с комплексными коэффициентами. На EUROCAM '82: Европейская конференция по компьютерной алгебре, лекция Примечания Comp. Sci. 144, 3-15. iai.uni-bonn.de/~schoe/publi39.dvi2ν2n
— Маркус Блезер

IIRC у вас был частичный ответ на удаленный вопрос CS. Кажется, стыдно терять это. Не могли бы вы включить его здесь (в вопросе, чтобы вопрос не был помечен как уже отвеченный)?
— Питер Тейлор

Ответы:


4

Этот ответ взят из статьи «Асимптотически быстрые алгоритмы числового умножения и деления многочленов с комплексными коэффициентами», которую Маркус связал в комментариях.


Вы хотите возвести в квадрат разрядное число по модулю . Вот что вы делаете:n2n+1

  • Найдите и которые удовлетворяют и .psn=(p−1)2ss≤p≤2s

  • Выберите количество частей чтобы разделить бит, и соответствующие параметры для размеров частей:2mn

    m=⌊s/2⌋+1s2=⌈s/2⌉+1p2=⌈p/2⌉+1

    Обратите внимание, что и продолжают удовлетворять инварианту . Также обратите внимание, что удовлетворяется, поэтому вход соответствует месту для переносов.s2p2s2≤p2≤2s22m2s2p2≥2n+m+1

  • Выполните основанную на БПФ отрицательную циклическую свертку на кусках, а остальное, как обычно.

Так что это всеобъемлющая идея: логарифмический коэффициент заполнения . Теперь для анализа сложности. Для выполнения БПФ потребуется работа, и мы вернемся к кусочкам , так что теперь мы можем сделать очень грубую математику с рекуррентным соотношением относительно :pnm2m(p2−1)2s2s

F(s)(≤)(p−1)2sm+2mF(⌈s/2⌉+1)(≤)2s2s(⌊s/2⌋+1)+2⌊s/2⌋+1F(⌈s/2⌉+1)(≤)s22s+2⋅2s/2F(s/2+1)(≤)s22s+4(s/2)22s+16(s/4)22s+...(≤)2ss2lg⁡(s)(≤)nlg⁡n(lg⁡nlg⁡n)2lg⁡lg⁡nlg⁡n(≤)nlg⁡n(lg2⁡n)lg⁡lg⁡n(≤)n(lg⁡n)lg⁡lg⁡n

Что кажется правильным, хотя я довольно много обманывал на этих шагах.

«Уловка», кажется, в том, что мы получаем вместо в базовой стоимости. По-прежнему есть два умножения на два на рекурсивный уровень, как я жаловался в этом вопросе, но теперь, когда половина в платит двойные дивиденды, все работает. Затем, в конце, мы отменяем дополнительный коэффициент (который на самом деле является коэффициентом ) благодаря тому, что изначально мы делали логарифмически большим относительно .s2ssslog⁡nps

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.