Алгоритм Шенхаге

Тема в разделе "WASM.A&O", создана пользователем Perec, 13 дек 2007.

  1. maxdiver

    maxdiver Max

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    308
    Адрес:
    Саратов
    CreatorCray
    (тоже не бог весть какой источник, но всё же))
    В общем, на одном сайте олимпиадных задач есть задача "умножение длинных чисел".
    Длина там 10000 цифр.
    Вот, моё решение карацубой работает раз в 10 медленнее пары решений, и они как раз вроде бы писали БПФ.
    Так что видимо БПФ начинает обыгрывать алгоритм Карацубы уже при меньших размерах.
     
  2. CreatorCray

    CreatorCray Member

    Публикаций:
    0
    Регистрация:
    5 авг 2006
    Сообщения:
    201
    maxdiver
    ну, все зависит от имплементации.
    Может у тебя в карацубе неверно была подобрана минимальная длина множителей, при котором уже не происходит дальнейшая рекурсия. У меня в моей реализации такое уже было - я оставил эту константу с дебаговых времен и в результате дробил числа до размера в 32 бита. Сейчас до 768 бит. Разница в скорости весьма впечатляющая.

    10000 десятичных цифр это примерно 33219 бит. т.е. чуть больше чем 2^(2^15). Что в общем то как раз лежит в указанных на вики пределах.
     
  3. Perec

    Perec New Member

    Публикаций:
    0
    Регистрация:
    13 дек 2007
    Сообщения:
    7
    CreatorCray
    В нем все по немецки!!!:) Его вообще можно скачать?
     
  4. CreatorCray

    CreatorCray Member

    Публикаций:
    0
    Регистрация:
    5 авг 2006
    Сообщения:
    201
    Perec
    В КОМ все по немецки????
    Кого скачать?

    Описание алгоритма смотри во втором томе Кнута (английский или русский, какой найдешь) или тут http://en.wikipedia.org/wiki/Sch%C3%B6nhage-Strassen_algorithm - там все на англицком...
    Где взять имплементацию алгоритма - без понятия. Попробуй поискать на http://koders.com/ или http://www.google.com/codesearch?hl=ru
     
  5. Perec

    Perec New Member

    Публикаций:
    0
    Регистрация:
    13 дек 2007
    Сообщения:
    7
    CreatorCray
    У Кнута как такового нет описание Шенхаге. Там есть "Китайская теорема". Еще посмотрю. Может пропустил? Что врядли:dntknw: Но все равно спасибо что время на ответы потратили!!!! Если что появится сообщи пожалуйста!!!:)
     
  6. Perec

    Perec New Member

    Публикаций:
    0
    Регистрация:
    13 дек 2007
    Сообщения:
    7
    CreatorCray
    Э нет! Правда пропустил!!! Слепой!!! 334 страница 2-го тома!!!
     
  7. maxdiver

    maxdiver Max

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    308
    Адрес:
    Саратов
    Что-то у меня не получается написать аналог преобразования Фурье в поле целых чисел :dntknw:
    Не догоняю что-то, как именно там надо вводить модули и каким образом выбирать p, по которому и берется mod p (например, в Кормене предлагается выбирать p наименьшее среди простых вида K*N+1, где N - как я понял длина).
    Ни у кого нет исходника?
    В гугле пытался искать, что-то не получилось найти :dntknw:
    Заранее спасибо.
     
  8. persicum

    persicum New Member

    Публикаций:
    0
    Регистрация:
    2 фев 2007
    Сообщения:
    947
    http://rapidshare.de/files/38537380/fftmult.rar.html

    Забираем быстрое умножение на Дельфах с применением FFTW.
    Сама библиотека проводит только БПФ, а вот умножение - это уже целиком моя заслуга. Там же есть и умножение в столбик для проверки. Важное замечание - замо FFT работает с плавучкой, но сам результат умножения всегда точный, если ошибка округления не превышает 0.4 : В противном случае код отрубается.

    Код сто пудов рабочий, с помошью него я находил офигенные простые числа и некоторые даже запостил в соответствующие анналы.
     
  9. maxdiver

    maxdiver Max

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    308
    Адрес:
    Саратов
    Ну это и я могу выложить умножение, использующее double'ы. Пожалуй я так и сделаю :)
    Код (Text):
    1. typedef complex<double> base;
    2.  
    3. void multiply (const vector<int> & a, const vector<int> & b, vector<int> & res);
    4. void fft (vector<base> & a, bool back);
    5. int rev (int n, int bits);
    6.  
    7. void multiply (const vector<int> & a, const vector<int> & b, vector<int> & res)
    8. {
    9.     int n = 1;
    10.     while (n < (int) max (a.size(), b.size()) )  n <<= 1;
    11.     n <<= 1;
    12.  
    13.     vector<base> aa (n), bb (n);
    14.     copy (a.begin(), a.end(), aa.begin());
    15.     copy (b.begin(), b.end(), bb.begin());
    16.  
    17.     fft (aa, false);
    18.     fft (bb, false);
    19.  
    20.     vector<base> cc (n);
    21.     for (int i=0; i<n; ++i)
    22.         cc[i] = aa[i] * bb[i];
    23.     fft (cc, true);
    24.  
    25.     res.resize (n);
    26.     for (int i=0, carry=0; i<n || carry; ++i)
    27.     {
    28.         if (i == n)  {  res.push_back (carry);  break;  }
    29.         res[i] = carry + (int) floor (cc[i].real() + 0.0001);
    30.         carry = res[i] / 10;
    31.         res[i] %= 10;
    32.     }
    33.     while (res.size() > 1 && res.back() == 0)  res.pop_back();
    34. }
    35.  
    36. void fft (vector<base> & a, bool back)
    37. {
    38.     int n = (int) a.size();
    39.     int lg_n = 0;
    40.     for (int i=1; i<n; i<<=1, ++lg_n) ;
    41.  
    42.     vector<char> swapped (n);
    43.     for (int i=0; i<n; ++i)
    44.         if (!swapped[i])
    45.         {
    46.             int j = rev (i, lg_n);
    47.             swap (a[i], a[j]);
    48.             swapped[i] = swapped[j] = true;
    49.         }
    50.  
    51.     for (int s=1; s<=lg_n; ++s)
    52.     {
    53.         int m = 1 << s;
    54.         base wm (cos (M_PI*2/m), sin (M_PI*2/m));
    55.         if (back)  wm.imag (-wm.imag());
    56.         for (int k=0; k<n; k+=m)
    57.         {
    58.             base w (1);
    59.             for (int j=0; j<m/2; ++j)
    60.             {
    61.                 base t = w * a[k+j+m/2];
    62.                 base u = a[k+j];
    63.                 a[k+j] = u + t;
    64.                 a[k+j+m/2] = u - t;
    65.                 if (back)
    66.                     a[k+j] /= 2,  a[k+j+m/2] /= 2;
    67.                 w *= wm;
    68.             }
    69.         }
    70.     }
    71. }
    72.  
    73. int rev (int n, int bits)
    74. {
    75.     int res = 0;
    76.     for (int i=0; i<bits; ++i)
    77.         res |= ((n >> i) & 1) << (bits-i-1);
    78.     return res;
    79. }
    А вот с целочисленной арифметикой что-то никак не получается. Просто, если я правильно понимаю, с ней должно получиться заметно быстрее...
     
  10. persicum

    persicum New Member

    Публикаций:
    0
    Регистрация:
    2 фев 2007
    Сообщения:
    947
    Везде написано, что целочисленные Фурье всегда медленнее работают, их применяют чтобы уж совсем исключить возможность ошибки из-за округления. По моему опыту - это все фигня, ведь в double можно класть хоть 16 бит, хоть 8-бит. Если совсем припрет и числа уж очень огромные, можно в даблы класть хоть по одному биту и всегда получить желаемую погрешность. Я бы не стал вообще с целочисленным Фурье заморачиваться, все рекордные простые числа включая мерсена с mersenne.org находят именно плавучкой.
     
  11. halyavin

    halyavin New Member

    Публикаций:
    0
    Регистрация:
    13 май 2005
    Сообщения:
    252
    Адрес:
    Russia
    persicum
    Не скажете ли, где "везде" это написано?
     
  12. persicum

    persicum New Member

    Публикаций:
    0
    Регистрация:
    2 фев 2007
    Сообщения:
    947
    Штрассен работает быстрее плавучки только на числах в миллиарды и сотни миллиардов цифр, где пришлось бы набивать даблы по нескольку а то и по одному биту, или даже одного бита было бы слишком много, чтобы ошибки округления все испортили. Это относится только к сверх-сверх большим числам.
    А вот для обычных больших чисел, типа не открытого еще вожделенного числа Мерсена на 10 миллионов знаков плавучки более чем достаточно.
     
  13. halyavin

    halyavin New Member

    Публикаций:
    0
    Регистрация:
    13 май 2005
    Сообщения:
    252
    Адрес:
    Russia
    Вы не ответили на вопрос. Мне интересны более точные оценки количества цифр, при которых один метод превосходит другой.
     
  14. persicum

    persicum New Member

    Публикаций:
    0
    Регистрация:
    2 фев 2007
    Сообщения:
    947
    Ну блин вы как профессор на экзамене...
    Если сами все знаете, то так и напишите, а мы почитаем лекционный материал =)))
     
  15. halyavin

    halyavin New Member

    Публикаций:
    0
    Регистрация:
    13 май 2005
    Сообщения:
    252
    Адрес:
    Russia
    Факт тот, что целочисленное фурье было придумано не просто-так. Но вот выгодно ли его использовать конкретно для целочисленного умножения - я не знаю. Дело в том, что вещественное фурье может быть ускорено в 2 раза, если есть 2 чисто действительных набора входных данных (нужно просто прибавить к первому набору второй набор, домноженный на i, а затем можно будет разделить этот совместный образ на 2). Правда, при целочисленном умножений нужно 3 преобразования фурье, поэтому это дает ускорение лишь в 4/3 раза. В остальном же, целочисленное Фурье по-моему выигрывает за счет более простых операций. Особенно, если использовать ассемблерное деление int64 на int32. А авторы же программ для поиска чисел Мерсенна, скорее всего, никогда о целочисленном Фурье не слыхали и ассемблера не знают. Так что без проведения опыта нельзя сказать, какой алгоритм эффективнее.
     
  16. dead_body

    dead_body wasm.ru

    Публикаций:
    0
    Регистрация:
    3 сен 2004
    Сообщения:
    603
    Адрес:
    Украина;г.Харьков;г.Н.Каховка
    а примеры этих алгоритмов есть на ассемблере? или мир к ним еще не готов, и их не успели написать?
    а то алгоритмы самые быстрые, ассемблер самый быстрый тоже, а вот вместе что то у них не срастаеться :dntknw:
     
  17. t00x

    t00x New Member

    Публикаций:
    0
    Регистрация:
    15 фев 2007
    Сообщения:
    1.921
    примеры есть в математических формулах, иногда на языке Си, реже на Паскале.
     
  18. dead_body

    dead_body wasm.ru

    Публикаций:
    0
    Регистрация:
    3 сен 2004
    Сообщения:
    603
    Адрес:
    Украина;г.Харьков;г.Н.Каховка
    t00x
    написал же "на ассемблере", а не в формулах и других языках. :dntknw:
     
  19. RElf

    RElf New Member

    Публикаций:
    0
    Регистрация:
    25 дек 2004
    Сообщения:
    159
    посмотрите сорцы GIMPS - там как раз все на асме оптимизировано
     
  20. maxdiver

    maxdiver Max

    Публикаций:
    0
    Регистрация:
    18 июл 2006
    Сообщения:
    308
    Адрес:
    Саратов
    Прошло больше года, а я таки взялся снова за этот алгоритм и таки разобрался, что из себя представляет целочисленная версия :)
    И ведь действительно она медленней оказалась :) Причём в разы.
    Не, ну если её заоптимайзить по самое не могу, вполне возможно, всё-таки целочисленное окажется быстрее. Там очень много операций взятия по модулю, а вроде как есть метод (Монтгомери, если я не ошибаюсь), с помощью которого можно будет от них избавиться. Может, кто-нибудь займётся этим. Для себя я понял, что по сочетанию длина кода <=> скорость работы плавучное ДПФ рулит :)
    Код (Text):
    1. const int mod = 7340033;
    2. const int root = 5;
    3. const int root_1 = reverse (root);
    4. const int root_pw = 1<<20;
    5.  
    6. void fft (vector<int> & a, bool invert) {
    7.     int n = (int) a.size();
    8.  
    9.     for (int i=1, j=0; i<n; ++i) {
    10.         int bit = n >> 1;
    11.         for (; j>=bit; bit>>=1)
    12.             j -= bit;
    13.         j += bit;
    14.         if (i < j)
    15.             swap (a[i], a[j]);
    16.     }
    17.  
    18.     for (int len=2; len<=n; len<<=1) {
    19.         int wlen = invert ? root_1 : root;
    20.         for (int i=len; i<root_pw; i<<=1)
    21.             wlen = int (wlen * 1ll * wlen % mod);
    22.         for (int i=0; i<n; i+=len) {
    23.             int w = 1;
    24.             for (int j=0; j<len/2; ++j) {
    25.                 int u = a[i+j],  v = int (a[i+j+len/2] * 1ll * w % mod);
    26.                 a[i+j] = u+v < mod ? u+v : u+v-mod;
    27.                 a[i+j+len/2] = u-v >= 0 ? u-v : u-v+mod;
    28.                 w = int (w * 1ll * wlen % mod);
    29.             }
    30.         }
    31.     }
    32.     if (invert) {
    33.         int nrev = reverse (n);
    34.         for (int i=0; i<n; ++i)
    35.             a[i] = int (a[i] * 1ll * nrev % mod);
    36.     }
    37. }
    38.  
    39. int powmod (int a, int b) {
    40.     int res = 1;
    41.     while (b)
    42.         if (b & 1) {
    43.             res = int (res * 1ll * a % mod);
    44.             --b;
    45.         }
    46.         else {
    47.             a = int (a * 1ll * a % mod);
    48.             b >>= 1;
    49.         }
    50.     return res;
    51. }
    52.  
    53. int reverse (int n) {
    54.     return powmod (n, mod-2);
    55. }
    Здесь root - примитивное значение корня root_pw-ой степени из единицы по модулю mod. Искал подбором, на практике обычно этого значения root_pw=2^20 хватает (это ограничение на макс. длину вектора), mod=2^20 * 7 + 1 ограничивает значения, принимаемые каждым элементом. Как всегда, предполагается, что входной вектор имеет длину - степень двойки.

    На всякий случай даю реализацию ДПФ на комплексных числах, с которой я и сравнивал целочисленный вариант.
    Код (Text):
    1. const double PI = 3.1415926535897932384626433832795;
    2.  
    3. typedef complex<double> base;
    4.  
    5. void fft (vector<base> & a, bool invert) {
    6.     int n = (int) a.size();
    7.  
    8.     for (int i=1, j=0; i<n; ++i) {
    9.         int bit = n >> 1;
    10.         for (; j>=bit; bit>>=1)
    11.             j -= bit;
    12.         j += bit;
    13.         if (i < j)
    14.             swap (a[i], a[j]);
    15.     }
    16.  
    17.     for (int len=2; len<=n; len<<=1) {
    18.         double ang = 2*PI/len * (invert ? -1 : 1);
    19.         base wlen (cos(ang), sin(ang));
    20.         for (int i=0; i<n; i+=len) {
    21.             base w (1);
    22.             for (int j=0; j<len/2; ++j) {
    23.                 base u = a[i+j],  v = a[i+j+len/2] * w;
    24.                 a[i+j] = u + v;
    25.                 a[i+j+len/2] = u - v;
    26.                 w *= wlen;
    27.             }
    28.         }
    29.     }
    30.     if (invert)
    31.         for (int i=0; i<n; ++i)
    32.             a[i] /= n;
    33. }
    Итого: на числах длины 10000 целочисленное работало максимум 199 мс, комплексное - 73 мс (имеются в виду все затраты на чтение двух чисел, 3 ДПФ для вычисления умножения и вывод произведения).