Алгоритмы НОД для арифметики произвольной точности

Я полностью застрял в этом вопросе, поэтому я ищу любую помощь.

Я думаю, что все знают об основных алгоритмах вычисления НОД, таких как двоичный или евклидов НОД. Не проблема реализовать такой метод для вычисления двух чисел с одинарной точностью. На самом деле, это всего лишь пара ударов.

Мне нужно реализовать этот метод (на языке C) для чисел с множественной точностью (более 10 ^ 5 бит). Доступно несколько библиотек GNU (GNU MP, MPFR, MPIR), и у них есть средства для определения чисел с множественной точностью и выполнения над ними действий. Это выглядит как одно число с многократной точностью, хранящееся в памяти как пара частей с одинарной точностью, также называемых «конечностями».

В них реализованы некоторые методы нахождения gcd(a, b), но их, на самом деле, сложно использовать для моих нужд. Двоичный метод вычисления НОД используется только в том случае, если a и b содержат ровно две конечности. Метод HGCD используется, когда min(a,b) содержит более (т.е. 630) конечностей и т. д. Мне трудно понять, как любой из этих методов можно расширить для использования с любой длиной a и b. Я также обнаружил, что разные версии библиотек GNU содержат разные версии и методы алгоритмов GCD.

Вопрос: я хочу выяснить, можно ли заставить двоичный алгоритм НОД работать с целыми числами многократной точности любой длины в терминах "конечностей", и если это возможно - получить какую-либо помощь или идеи как это реализовать на C. Есть ли у кого-нибудь идеи или части кода, как это реализовать?

Я хотел бы рассмотреть любые советы или любое другое решение для решения этой проблемы.

Вот часть бинарного GCD-метода GNU MP для (a = b = 2 конечностей), если кто-нибудь взглянет:

/* Use binary algorithm to compute G <-- GCD (U, V) for usize, vsize == 2.
   Both U and V must be odd. */
static inline mp_size_t
gcd_2 (mp_ptr gp, mp_srcptr up, mp_srcptr vp)
{
  printf("gcd_2 invoked\n");
  mp_limb_t u0, u1, v0, v1;
  mp_size_t gn;

  u0 = up[0];
  u1 = up[1];
  v0 = vp[0];
  v1 = vp[1];

  ASSERT (u0 & 1);
  ASSERT (v0 & 1);

  /* Check for u0 != v0 needed to ensure that argument to
   * count_trailing_zeros is non-zero. */
  while (u1 != v1 && u0 != v0)
    {
      unsigned long int r;
      if (u1 > v1)
  {
    sub_ddmmss (u1, u0, u1, u0, v1, v0);
    count_trailing_zeros (r, u0);
    u0 = ((u1 << (GMP_NUMB_BITS - r)) & GMP_NUMB_MASK) | (u0 >> r);
    u1 >>= r;
  }
      else  /* u1 < v1.  */
  {
    sub_ddmmss (v1, v0, v1, v0, u1, u0);
    count_trailing_zeros (r, v0);
    v0 = ((v1 << (GMP_NUMB_BITS - r)) & GMP_NUMB_MASK) | (v0 >> r);
    v1 >>= r;
  }
    }

  gp[0] = u0, gp[1] = u1, gn = 1 + (u1 != 0);

  /* If U == V == GCD, done.  Otherwise, compute GCD (V, |U - V|).  */
  if (u1 == v1 && u0 == v0)
    return gn;

  v0 = (u0 == v0) ? ((u1 > v1) ? u1-v1 : v1-u1) : ((u0 > v0) ? u0-v0 : v0-u0);
  gp[0] = mpn_gcd_1 (gp, gn, v0);

  return 1;
}

CodePaste вышеперечисленного.


person Mikhail Kalashnikov    schedule 23.02.2013    source источник
comment
Вы реализуете свои собственные? В противном случае void mpz_gcd (mpz_t ROP, mpz_t OP1, mpz_t OP2) кажется достаточно простым в использовании.   -  person Daniel Fischer    schedule 23.02.2013
comment
Поскольку сложность двоичного алгоритма GCD составляет O (N ^ 2), я хотел бы иметь код с такой же сложностью, если это возможно. Я знаю, что этот код не будет действительно эффективным по сравнению с любыми субквадратичными алгоритмами для больших целых чисел. Смысл моей работы просто в том, чтобы показать, насколько плохим будет чистый двоичный алгоритм с числами с многократной точностью по сравнению с любыми другими решениями.   -  person Mikhail Kalashnikov    schedule 23.02.2013
comment
void mpz_gcd (mpz_t ROP, mpz_t OP1, mpz_t OP2) Если я установлю OP1 = (10^5 бит) и OP2 = (10^5 бит), то этот метод будет вычислять gcd, используя улучшенный алгоритм Шонхаге. Но я хочу вычислить это число, используя чистый двоичный алгоритм, чтобы сравнить время выполнения процедуры, чтобы узнать, насколько плоха эффективность двоичного GCD. Надеюсь, что это возможно сделать.   -  person Mikhail Kalashnikov    schedule 23.02.2013
comment
Хорошо, вы, конечно, можете реализовать двоичный алгоритм НОД для чисел произвольной точности почти так же, как и для стандартных типов машин. Если вы используете GMP, mpz_tdiv_q_2exp — это ваш битовый сдвиг. В противном случае запись битового сдвига для массива проста, хотя и не должна быть очень эффективной.   -  person Daniel Fischer    schedule 23.02.2013
comment
@DanielFischer, наверное, мне следует пойти именно так (я использую GMP). Кажется, у меня есть идея, спасибо большое!   -  person Mikhail Kalashnikov    schedule 24.02.2013
comment
Я думаю, что вы все еще можете сделать это с gmp's gcd, но вам придется скомпилировать его с нуля, изменив некоторые значения из файла gmp-mparam.h. Есть пара, которые относятся к пороговым значениям алгоритма gcd.   -  person Noxbru    schedule 24.02.2013
comment
Я пытался копаться в исходном коде GMP, но, к сожалению, с порогами особо ничего не поделаешь. Я использую GMP 5.1, и нужно изменить только один ПОРОГ (GCD_DC_THRESHOLD), и это не очень помогает. В любом случае попробуйте использовать HGCD в любых случаях, когда длина min(a,b) > 2, потому что, насколько я понимаю, это своего рода метод по умолчанию для вычисления gcd.   -  person Mikhail Kalashnikov    schedule 24.02.2013
comment
Я думаю, что это может быть способом. Я попытаюсь имитировать обычные шаги алгоритма (Euclid и Binary) только для чисел с множественной точностью, используя разные функции GMP, думая, что они просто обычные с одинарной точностью. Это будет очень медленно для больших чисел, но это именно то, что я хочу. Я постараюсь сделать это, но любые комментарии приветствуются в любом случае.   -  person Mikhail Kalashnikov    schedule 24.02.2013


Ответы (1)


Просто сверните свой собственный код специально для этой проблемы, почему бы и нет? (10^9)^2 вписывается в 64-битное целое число, поэтому вы можете работать с цифрами по основанию (10^9), каждая из которых содержится в 64-битном целочисленном значении. Чтобы представить 2^(10^5)-битные значения, 2^(10^5) ~= 10^30103, то есть значения с ~ 30103 десятичными цифрами, вам понадобятся только 30103/9 ~= 3350 ints, которые представляют собой массив ~ 27 КБ в памяти для каждого из двух задействованных чисел.

Согласно WP, для двоичного алгоритма GCD вам нужны только minus и /2, что тривиально реализовать путем деления каждой цифры пополам со случайным переносом 5*10^8 в младшую цифру (94 / 2 = 47 = {4,5+2}). Окончательное умножение на 2k можно выполнить с помощью простого алгоритма, поскольку его нужно выполнить только один раз.

Печать в base-10 будет тривиальной. Если вам не нужно распечатывать окончательный результат, вам не понадобится окончательное умножение (или если вы сообщите свой результат как 2^k*x), и тогда вы можете работать с цифрами с основанием 10^18, уменьшая количество цифр вдвое до работать с.

Вам понадобится только обычная целочисленная арифметика C для работы с цифрами.

person Will Ness    schedule 23.02.2013
comment
Не могли бы вы поделиться несколькими короткими примерами или ссылками, как работать с числами (в C) с какой-либо системой, отличной от 10? Как должна выглядеть эта абстракция. Спасибо. - person Mikhail Kalashnikov; 24.02.2013
comment
{А,В} + {С,D}: N1=В+С; R1=N1 % основания; Carry=N1 / база; N2 = А+С+Перенос; R2=N2% основания; Carry=N2/база; если (Перенести › 0), то Результат = {Перенести,R2,R1} иначе Результат = {R2,R1} . Сделайте это с петлей; представлять списки цифр массивами + число_цифр. - person Will Ness; 24.02.2013
comment
Поправка @MikhailKalashnikov: N1 = B+D, конечно. Операции над каждой парой цифр выполняются с помощью обычных операций с целыми числами. Чтобы это не вызывало переполнение, max_digit^2 должно быть меньше диапазона int (если мы умножаем). Если мы только добавляем/вычитаем, max_digit*2 должен быть ‹ int range (MAXINT). - person Will Ness; 24.02.2013