Самая быстрая полупримесовая факторизация

28

Напишите программу для факторизации полу-простого числа в кратчайшие сроки.

Для тестирования используйте это: 38! +1 (523022617466601111760007224100074291200000001)

Это равно: 14029308060317546154181 × 37280713718589679646221

Сохам Чоудхури
источник
2
Хотя мне нравится «самый быстрый» бит, поскольку он дает таким языкам, как C, преимущество перед обычными языками программирования, мне интересно, как вы будете тестировать результаты?
Мистер Листер
1
Если вы имеете в виду, что 12259243это будет использоваться для проверки того, насколько быстры программы, результаты будут настолько малы, что вы не получите никаких статистически значимых различий.
Питер Тейлор
Я добавил большее число, спасибо за головы.
Сохам Чоудхури
@ Мистер Листер, я проверю это на своем собственном ПК.
Сохам Чоудхури
5
inb4 кто-то использует злоупотребление препроцессором, чтобы написать 400-кратную таблицу поиска
Wug

Ответы:

59

Python (с PyPy JIT v1.9) ~ 1,9 с

Использование многочленного квадратичного сита . Я решил, что это вызов кода, поэтому я решил не использовать никаких внешних библиотек (кроме стандартной logфункции, я полагаю). При синхронизации следует использовать PyPy JIT , так как это приводит к увеличению времени в 4-5 раз по сравнению с cPython .

Обновление (2013-07-29):
С момента публикации я внес несколько небольших, но значительных изменений, которые увеличивают общую скорость примерно в 2,5 раза.

Обновление (2014-08-27):
Поскольку этот пост все еще привлекает внимание, я обновил my_math.pyисправление двух ошибок для тех, кто может его использовать:

  • isqrtбыл ошибочным, иногда производил неправильный вывод для значений, очень близких к идеальному квадрату. Это было исправлено, и производительность увеличилась за счет использования гораздо лучшего семени.
  • is_primeбыл обновлен Моя предыдущая попытка удалить идеальные квадратные 2-sprps была в лучшем случае нерешительной. Я добавил проверку 3-sprp - метод, используемый Mathmatica - чтобы убедиться, что тестируемое значение не содержит квадратов.

Обновление (2014-11-24):
если в конце расчета нетривиальные сравнения не найдены, программа теперь просеивает дополнительные полиномы. Это было ранее отмечено в коде как TODO.


mpqs.py

from my_math import *
from math import log
from time import clock
from argparse import ArgumentParser

# Multiple Polynomial Quadratic Sieve
def mpqs(n, verbose=False):
  if verbose:
    time1 = clock()

  root_n = isqrt(n)
  root_2n = isqrt(n+n)

  # formula chosen by experimentation
  # seems to be close to optimal for n < 10^50
  bound = int(5 * log(n, 10)**2)

  prime = []
  mod_root = []
  log_p = []
  num_prime = 0

  # find a number of small primes for which n is a quadratic residue
  p = 2
  while p < bound or num_prime < 3:

    # legendre (n|p) is only defined for odd p
    if p > 2:
      leg = legendre(n, p)
    else:
      leg = n & 1

    if leg == 1:
      prime += [p]
      mod_root += [int(mod_sqrt(n, p))]
      log_p += [log(p, 10)]
      num_prime += 1
    elif leg == 0:
      if verbose:
        print 'trial division found factors:'
        print p, 'x', n/p
      return p

    p = next_prime(p)

  # size of the sieve
  x_max = len(prime)*60

  # maximum value on the sieved range
  m_val = (x_max * root_2n) >> 1

  # fudging the threshold down a bit makes it easier to find powers of primes as factors
  # as well as partial-partial relationships, but it also makes the smoothness check slower.
  # there's a happy medium somewhere, depending on how efficient the smoothness check is
  thresh = log(m_val, 10) * 0.735

  # skip small primes. they contribute very little to the log sum
  # and add a lot of unnecessary entries to the table
  # instead, fudge the threshold down a bit, assuming ~1/4 of them pass
  min_prime = int(thresh*3)
  fudge = sum(log_p[i] for i,p in enumerate(prime) if p < min_prime)/4
  thresh -= fudge

  if verbose:
    print 'smoothness bound:', bound
    print 'sieve size:', x_max
    print 'log threshold:', thresh
    print 'skipping primes less than:', min_prime

  smooth = []
  used_prime = set()
  partial = {}
  num_smooth = 0
  num_used_prime = 0
  num_partial = 0
  num_poly = 0
  root_A = isqrt(root_2n / x_max)

  if verbose:
    print 'sieving for smooths...'
  while True:
    # find an integer value A such that:
    # A is =~ sqrt(2*n) / x_max
    # A is a perfect square
    # sqrt(A) is prime, and n is a quadratic residue mod sqrt(A)
    while True:
      root_A = next_prime(root_A)
      leg = legendre(n, root_A)
      if leg == 1:
        break
      elif leg == 0:
        if verbose:
          print 'dumb luck found factors:'
          print root_A, 'x', n/root_A
        return root_A

    A = root_A * root_A

    # solve for an adequate B
    # B*B is a quadratic residue mod n, such that B*B-A*C = n
    # this is unsolvable if n is not a quadratic residue mod sqrt(A)
    b = mod_sqrt(n, root_A)
    B = (b + (n - b*b) * mod_inv(b + b, root_A))%A

    # B*B-A*C = n <=> C = (B*B-n)/A
    C = (B*B - n) / A

    num_poly += 1

    # sieve for prime factors
    sums = [0.0]*(2*x_max)
    i = 0
    for p in prime:
      if p < min_prime:
        i += 1
        continue
      logp = log_p[i]

      inv_A = mod_inv(A, p)
      # modular root of the quadratic
      a = int(((mod_root[i] - B) * inv_A)%p)
      b = int(((p - mod_root[i] - B) * inv_A)%p)

      k = 0
      while k < x_max:
        if k+a < x_max:
          sums[k+a] += logp
        if k+b < x_max:
          sums[k+b] += logp
        if k:
          sums[k-a+x_max] += logp
          sums[k-b+x_max] += logp

        k += p
      i += 1

    # check for smooths
    i = 0
    for v in sums:
      if v > thresh:
        x = x_max-i if i > x_max else i
        vec = set()
        sqr = []
        # because B*B-n = A*C
        # (A*x+B)^2 - n = A*A*x*x+2*A*B*x + B*B - n
        #               = A*(A*x*x+2*B*x+C)
        # gives the congruency
        # (A*x+B)^2 = A*(A*x*x+2*B*x+C) (mod n)
        # because A is chosen to be square, it doesn't need to be sieved
        val = sieve_val = A*x*x + 2*B*x + C

        if sieve_val < 0:
          vec = set([-1])
          sieve_val = -sieve_val

        for p in prime:
          while sieve_val%p == 0:
            if p in vec:
              # keep track of perfect square factors
              # to avoid taking the sqrt of a gigantic number at the end
              sqr += [p]
            vec ^= set([p])
            sieve_val = int(sieve_val / p)

        if sieve_val == 1:
          # smooth
          smooth += [(vec, (sqr, (A*x+B), root_A))]
          used_prime |= vec
        elif sieve_val in partial:
          # combine two partials to make a (xor) smooth
          # that is, every prime factor with an odd power is in our factor base
          pair_vec, pair_vals = partial[sieve_val]
          sqr += list(vec & pair_vec) + [sieve_val]
          vec ^= pair_vec
          smooth += [(vec, (sqr + pair_vals[0], (A*x+B)*pair_vals[1], root_A*pair_vals[2]))]
          used_prime |= vec
          num_partial += 1
        else:
          # save partial for later pairing
          partial[sieve_val] = (vec, (sqr, A*x+B, root_A))
      i += 1

    num_smooth = len(smooth)
    num_used_prime = len(used_prime)

    if verbose:
      print 100 * num_smooth / num_prime, 'percent complete\r',

    if num_smooth > num_used_prime:
      if verbose:
        print '%d polynomials sieved (%d values)'%(num_poly, num_poly*x_max*2)
        print 'found %d smooths (%d from partials) in %f seconds'%(num_smooth, num_partial, clock()-time1)
        print 'solving for non-trivial congruencies...'

      used_prime_list = sorted(list(used_prime))

      # set up bit fields for gaussian elimination
      masks = []
      mask = 1
      bit_fields = [0]*num_used_prime
      for vec, vals in smooth:
        masks += [mask]
        i = 0
        for p in used_prime_list:
          if p in vec: bit_fields[i] |= mask
          i += 1
        mask <<= 1

      # row echelon form
      col_offset = 0
      null_cols = []
      for col in xrange(num_smooth):
        pivot = col-col_offset == num_used_prime or bit_fields[col-col_offset] & masks[col] == 0
        for row in xrange(col+1-col_offset, num_used_prime):
          if bit_fields[row] & masks[col]:
            if pivot:
              bit_fields[col-col_offset], bit_fields[row] = bit_fields[row], bit_fields[col-col_offset]
              pivot = False
            else:
              bit_fields[row] ^= bit_fields[col-col_offset]
        if pivot:
          null_cols += [col]
          col_offset += 1

      # reduced row echelon form
      for row in xrange(num_used_prime):
        # lowest set bit
        mask = bit_fields[row] & -bit_fields[row]
        for up_row in xrange(row):
          if bit_fields[up_row] & mask:
            bit_fields[up_row] ^= bit_fields[row]

      # check for non-trivial congruencies
      for col in null_cols:
        all_vec, (lh, rh, rA) = smooth[col]
        lhs = lh   # sieved values (left hand side)
        rhs = [rh] # sieved values - n (right hand side)
        rAs = [rA] # root_As (cofactor of lhs)
        i = 0
        for field in bit_fields:
          if field & masks[col]:
            vec, (lh, rh, rA) = smooth[i]
            lhs += list(all_vec & vec) + lh
            all_vec ^= vec
            rhs += [rh]
            rAs += [rA]
          i += 1

        factor = gcd(list_prod(rAs)*list_prod(lhs) - list_prod(rhs), n)
        if factor != 1 and factor != n:
          break
      else:
        if verbose:
          print 'none found.'
        continue
      break

  if verbose:
    print 'factors found:'
    print factor, 'x', n/factor
    print 'time elapsed: %f seconds'%(clock()-time1)
  return factor

if __name__ == "__main__":
  parser =ArgumentParser(description='Uses a MPQS to factor a composite number')
  parser.add_argument('composite', metavar='number_to_factor', type=long,
      help='the composite number to factor')
  parser.add_argument('--verbose', dest='verbose', action='store_true',
      help="enable verbose output")
  args = parser.parse_args()

  if args.verbose:
    mpqs(args.composite, args.verbose)
  else:
    time1 = clock()
    print mpqs(args.composite)
    print 'time elapsed: %f seconds'%(clock()-time1)

my_math.py

# divide and conquer list product
def list_prod(a):
  size = len(a)
  if size == 1:
    return a[0]
  return list_prod(a[:size>>1]) * list_prod(a[size>>1:])

# greatest common divisor of a and b
def gcd(a, b):
  while b:
    a, b = b, a%b
  return a

# modular inverse of a mod m
def mod_inv(a, m):
  a = int(a%m)
  x, u = 0, 1
  while a:
    x, u = u, x - (m/a)*u
    m, a = a, m%a
  return x

# legendre symbol (a|m)
# note: returns m-1 if a is a non-residue, instead of -1
def legendre(a, m):
  return pow(a, (m-1) >> 1, m)

# modular sqrt(n) mod p
# p must be prime
def mod_sqrt(n, p):
  a = n%p
  if p%4 == 3:
    return pow(a, (p+1) >> 2, p)
  elif p%8 == 5:
    v = pow(a << 1, (p-5) >> 3, p)
    i = ((a*v*v << 1) % p) - 1
    return (a*v*i)%p
  elif p%8 == 1:
    # Shank's method
    q = p-1
    e = 0
    while q&1 == 0:
      e += 1
      q >>= 1

    n = 2
    while legendre(n, p) != p-1:
      n += 1

    w = pow(a, q, p)
    x = pow(a, (q+1) >> 1, p)
    y = pow(n, q, p)
    r = e
    while True:
      if w == 1:
        return x

      v = w
      k = 0
      while v != 1 and k+1 < r:
        v = (v*v)%p
        k += 1

      if k == 0:
        return x

      d = pow(y, 1 << (r-k-1), p)
      x = (x*d)%p
      y = (d*d)%p
      w = (w*y)%p
      r = k
  else: # p == 2
    return a

#integer sqrt of n
def isqrt(n):
  c = n*4/3
  d = c.bit_length()

  a = d>>1
  if d&1:
    x = 1 << a
    y = (x + (n >> a)) >> 1
  else:
    x = (3 << a) >> 2
    y = (x + (c >> a)) >> 1

  if x != y:
    x = y
    y = (x + n/x) >> 1
    while y < x:
      x = y
      y = (x + n/x) >> 1
  return x

# strong probable prime
def is_sprp(n, b=2):
  if n < 2: return False
  d = n-1
  s = 0
  while d&1 == 0:
    s += 1
    d >>= 1

  x = pow(b, d, n)
  if x == 1 or x == n-1:
    return True

  for r in xrange(1, s):
    x = (x * x)%n
    if x == 1:
      return False
    elif x == n-1:
      return True

  return False

# lucas probable prime
# assumes D = 1 (mod 4), (D|n) = -1
def is_lucas_prp(n, D):
  P = 1
  Q = (1-D) >> 2

  # n+1 = 2**r*s where s is odd
  s = n+1
  r = 0
  while s&1 == 0:
    r += 1
    s >>= 1

  # calculate the bit reversal of (odd) s
  # e.g. 19 (10011) <=> 25 (11001)
  t = 0
  while s:
    if s&1:
      t += 1
      s -= 1
    else:
      t <<= 1
      s >>= 1

  # use the same bit reversal process to calculate the sth Lucas number
  # keep track of q = Q**n as we go
  U = 0
  V = 2
  q = 1
  # mod_inv(2, n)
  inv_2 = (n+1) >> 1
  while t:
    if t&1:
      # U, V of n+1
      U, V = ((U + V) * inv_2)%n, ((D*U + V) * inv_2)%n
      q = (q * Q)%n
      t -= 1
    else:
      # U, V of n*2
      U, V = (U * V)%n, (V * V - 2 * q)%n
      q = (q * q)%n
      t >>= 1

  # double s until we have the 2**r*sth Lucas number
  while r:
    U, V = (U * V)%n, (V * V - 2 * q)%n
    q = (q * q)%n
    r -= 1

  # primality check
  # if n is prime, n divides the n+1st Lucas number, given the assumptions
  return U == 0

# primes less than 212
small_primes = set([
    2,  3,  5,  7, 11, 13, 17, 19, 23, 29,
   31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
   73, 79, 83, 89, 97,101,103,107,109,113,
  127,131,137,139,149,151,157,163,167,173,
  179,181,191,193,197,199,211])

# pre-calced sieve of eratosthenes for n = 2, 3, 5, 7
indices = [
    1, 11, 13, 17, 19, 23, 29, 31, 37, 41,
   43, 47, 53, 59, 61, 67, 71, 73, 79, 83,
   89, 97,101,103,107,109,113,121,127,131,
  137,139,143,149,151,157,163,167,169,173,
  179,181,187,191,193,197,199,209]

# distances between sieve values
offsets = [
  10, 2, 4, 2, 4, 6, 2, 6, 4, 2, 4, 6,
   6, 2, 6, 4, 2, 6, 4, 6, 8, 4, 2, 4,
   2, 4, 8, 6, 4, 6, 2, 4, 6, 2, 6, 6,
   4, 2, 4, 6, 2, 6, 4, 2, 4, 2,10, 2]

max_int = 2147483647

# an 'almost certain' primality check
def is_prime(n):
  if n < 212:
    return n in small_primes

  for p in small_primes:
    if n%p == 0:
      return False

  # if n is a 32-bit integer, perform full trial division
  if n <= max_int:
    i = 211
    while i*i < n:
      for o in offsets:
        i += o
        if n%i == 0:
          return False
    return True

  # Baillie-PSW
  # this is technically a probabalistic test, but there are no known pseudoprimes
  if not is_sprp(n, 2): return False

  # idea shamelessly stolen from Mathmatica
  # if n is a 2-sprp and a 3-sprp, n is necessarily square-free
  if not is_sprp(n, 3): return False

  a = 5
  s = 2
  # if n is a perfect square, this will never terminate
  while legendre(a, n) != n-1:
    s = -s
    a = s-a
  return is_lucas_prp(n, a)

# next prime strictly larger than n
def next_prime(n):
  if n < 2:
    return 2
  # first odd larger than n
  n = (n + 1) | 1
  if n < 212:
    while True:
      if n in small_primes:
        return n
      n += 2

  # find our position in the sieve rotation via binary search
  x = int(n%210)
  s = 0
  e = 47
  m = 24
  while m != e:
    if indices[m] < x:
      s = m
      m = (s + e + 1) >> 1
    else:
      e = m
      m = (s + e) >> 1

  i = int(n + (indices[m] - x))
  # adjust offsets
  offs = offsets[m:] + offsets[:m]
  while True:
    for o in offs:
      if is_prime(i):
        return i
      i += o

Пример ввода / вывода:

$ pypy mpqs.py --verbose 94968915845307373740134800567566911
smoothness bound: 6117
sieve size: 24360
log threshold: 14.3081031579
skipping primes less than: 47
sieving for smooths...
144 polynomials sieved (7015680 values)
found 405 smooths (168 from partials) in 0.513794 seconds
solving for non-trivial congruencies...
factors found:
216366620575959221 x 438925910071081891
time elapsed: 0.685765 seconds

$ pypy mpqs.py --verbose 523022617466601111760007224100074291200000001
smoothness bound: 9998
sieve size: 37440
log threshold: 15.2376302725
skipping primes less than: 59
sieving for smooths...
428 polynomials sieved (32048640 values)
found 617 smooths (272 from partials) in 1.912131 seconds
solving for non-trivial congruencies...
factors found:
14029308060317546154181 x 37280713718589679646221
time elapsed: 2.064387 seconds

Примечание: если вы не используете эту --verboseопцию, время будет немного лучше:

$ pypy mpqs.py 94968915845307373740134800567566911
216366620575959221
time elapsed: 0.630235 seconds

$ pypy mpqs.py 523022617466601111760007224100074291200000001
14029308060317546154181
time elapsed: 1.886068 seconds

Основные понятия

В целом, квадратичное сито основано на следующем наблюдении: любой нечетный составной n может быть представлен как:

Это не очень сложно подтвердить. Поскольку n нечетно, расстояние между любыми двумя кофакторами n должно быть четным 2d , где x - средняя точка между ними. Более того, такое же соотношение справедливо для любого кратного n

Обратите внимание, что если какие-либо такие x и d могут быть найдены, это немедленно приведет к (не обязательно простому) коэффициенту n , поскольку x + d и x - d оба делят n по определению. Это отношение может быть дополнительно ослаблено - вследствие возможного тривиального сравнения - до следующей формы:

Таким образом, в общем, если мы можем найти два идеальных квадрата, которые эквивалентны mod n , то весьма вероятно, что мы можем напрямую получить множитель n a la gcd (x ± d, n) . Кажется довольно просто, верно?

За исключением того, что это не так. Если бы мы намеревались провести исчерпывающий поиск по всем возможным x , нам нужно было бы искать весь диапазон от [ n , √ ( 2n ) ], который немного меньше, чем полное пробное деление, но также требует дорогогоis_square операции для каждой итерации, чтобы подтвердить значение d . Если это не заранее известно , что п есть факторы очень близко п , суд разделение, вероятно, будет быстрее.

Возможно, мы сможем ослабить это отношение еще больше. Предположим, мы выбрали x , такой, что для

полная простая факторизация у легко известна. Если бы у нас было достаточно таких отношений, мы могли бы построить адекватное d , если мы выберем число y, такое, что их произведение будет идеальным квадратом; то есть все простые факторы используются четное число раз. На самом деле, если у нас больше таких у, чем общее число уникальных простых факторов, которые они содержат, решение гарантированно существует; Это становится системой линейных уравнений. Теперь возникает вопрос, как мы выбрали такой х ? Вот где в игру вступает сито.

Сито

Рассмотрим полином:

Тогда для любого простого p и целого числа k верно следующее:

Это означает, что после решения для корней полинома mod p - то есть вы нашли x такой, что y (x) ≡ 0 (mod p) , ergo y делится на p - тогда вы нашли бесконечное число такого х . Таким образом, вы можете просеивать в диапазоне х , определяя небольшие простые факторы у , надеясь найти такие, для которых все простые факторы малы. Такие числа известны как k-гладкие , где k - самый большой используемый простой фактор.

Однако у этого подхода есть несколько проблем. Не все значения x являются адекватными, на самом деле их очень мало, сосредоточенных вокруг n . Меньшие значения станут в значительной степени отрицательными (из-за члена -n ), а большие значения станут слишком большими, так что маловероятно, что их первичная факторизация состоит только из небольших простых чисел. Там будет ряд таких х , но если композит вы будете факторинг не очень маленький, это очень маловероятно , что вы найдете достаточно разглаживает , чтобы привести к факторизации. И поэтому при большем n становится необходимым просеивать несколько полиномов заданной формы.

Несколько полиномов

Значит, нам нужно больше полиномов для сита? Как насчет этого:

Это сработает. Обратите внимание , что A и B могут быть буквально любым целочисленным значением, и математика все еще сохраняется. Все, что нам нужно сделать, это выбрать несколько случайных значений, найти корень полинома и просеять значения, близкие к нулю. В этот момент мы могли бы просто назвать это достаточно хорошо: если вы бросаете достаточно камней в случайных направлениях, вы рано или поздно должны разбить окно.

Кроме того, есть проблема с этим тоже. Если наклон полинома велик в точке пересечения х, что будет, если он не будет относительно плоским, будет только несколько подходящих значений для просеивания за полином. Это сработает, но в итоге вы просеете много полиномов, прежде чем получите то, что вам нужно. Можем ли мы сделать лучше?

Мы можем сделать лучше. В результате Монтгомери наблюдает следующее: если A и B выбраны так, что существует некоторое C, удовлетворяющее

тогда весь полином может быть переписан как

Кроме того, если A выбран в качестве идеального квадрата, то во время просеивания лидирующим членом A можно пренебречь, что приведет к гораздо меньшим значениям и гораздо более плоской кривой. Чтобы такое решение существовало, n должно быть квадратичным вычетом modA , который можно сразу узнать, вычислив символ Лежандра :
( n | √A ) = 1 . Обратите внимание, что для решения для B необходимо знать полную простую факторизацию √A (чтобы взять модульный квадратный корень √n (mod √A) ), поэтому √A обычно выбирается как простое число.

Тогда можно показать, что если , то для всех значений x ∈ [ -M, M ] :

И вот, наконец, у нас есть все компоненты, необходимые для реализации нашего сита. Или мы?

Полномочия простых чисел как факторов

Наше сито, как описано выше, имеет один существенный недостаток. Он может определить , какие значения х приведет к у делится на р , но он не может определить , является ли или нет у делится на мощности от р . Чтобы определить это, нам нужно будет выполнить пробное деление по значению, которое необходимо просеять, пока оно не перестанет делиться на p . Казалось, мы достигли тупика: весь смысл решетки был в том, что нам не нужно было этого делать. Время проверить книгу.

Это выглядит довольно полезным. Если сумма ln всех малых простых множителей y близка к ожидаемому значению ln (y) , то почти у данного фактора у y нет других факторов. Кроме того, если мы немного снизим ожидаемое значение, мы также можем определить значения как гладкие, которые имеют несколько степеней простых чисел как факторы. Таким образом, мы можем использовать сито в качестве процесса «предварительного скрининга» и учитывать только те значения, которые могут быть гладкими.

Это также имеет несколько других преимуществ. Обратите внимание , что небольшие штрихи способствуют очень мало для ¯Ln суммы, но все же они требуют самого решета времени. Для просеивания значения 3 требуется больше времени, чем 11, 13, 17, 19 и 23 вместе взятых . Вместо этого мы можем просто пропустить первые несколько простых чисел и соответствующим образом скорректировать порог, предполагая, что определенный процент из них прошел бы.

Другой результат заключается в том, что ряду значений будет разрешено «проскальзывать», которые в основном являются гладкими, но содержат один большой кофактор. Мы могли бы просто отбросить эти значения, но предположим, что мы нашли другое в основном гладкое значение с точно таким же кофактором. Затем мы можем использовать эти два значения для построения полезного y ; поскольку их продукт будет содержать этот большой кофактор в квадрате, его больше не нужно рассматривать.

Собираем все вместе

Последнее, что нам нужно сделать, это использовать эти значения y для построения адекватных x и d . Предположим, что мы рассматриваем только неквадратные множители у , то есть простые множители нечетной степени. Тогда каждый y может быть выражен следующим образом:

который можно выразить в виде матрицы:

Тогда возникает проблема найти вектор v такой, что vM =(mod 2) , где - нулевой вектор. То есть, чтобы решить для левого нулевого пространства М . Это можно сделать несколькими способами, самым простым из которых является выполнение исключения Гаусса для M T , заменив операцию добавления строки на строку xor . Это приведет к ряду базисных векторов с нулевым пространством, любая комбинация которых даст правильное решение.

Конструкция x довольно проста. Это просто произведение Ax + B для каждого из используемых y . Построение d немного сложнее. Если бы мы взяли произведение всех y , мы получим значение с десятками тысяч, если не сотнями тысяч цифр, для которых нам нужно найти квадратный корень. Этот расчет нецелесообразно дорогой. Вместо этого мы можем отслеживать четные степени простых чисел во время процесса просеивания, а затем использовать операции and и xor для векторов неквадратных факторов для восстановления квадратного корня.

Кажется, я достиг предела в 30000 символов. Ах, хорошо, я полагаю, этого достаточно.

Примо
источник
5
Ну, я никогда не сдавал алгебру в старших классах (фактически бросил учебу в течение первого семестра первого года обучения), но вы упрощаете понимание с точки зрения программиста. Я не буду притворяться, что полностью понимаю это, не применяя это на практике, но я приветствую вас. Вам следует подумать о расширении поста за пределами сайта и его публикации, серьезно!
jdstankosky
2
Я согласен. Отличный ответ с отличным объяснением. +1
Сохам Чоудхури
1
@primo Ваши ответы на несколько вопросов здесь были невероятно тщательными и интересными. Очень признателен!
Пол Уоллс
4
В качестве последнего замечания я хотел бы выразить свою признательность Уиллу Нессу за награду +100 по этому вопросу. Это была буквально вся его репутация.
Примо
2
@ StepHen это делает. К сожалению, он использует оригинальную версию 2012 года, без повышения скорости и с ошибкой в ​​устранении по Гауссу (ошибки, когда последний столбец является сводным столбцом). Я пытался связаться с автором некоторое время назад, но не получил ответа.
прима
2

Ну, ваш 38! +1 сломал мой php скрипт, не знаю почему. Фактически, любое простое число длиной более 16 цифр нарушает мой сценарий.

Однако, используя 8980935344490257 (86028157 * 104395301), мой сценарий управлял временем на моем домашнем компьютере 25,963 секунды (AMD Phenom 9950 с частотой 2,61 ГГц). Гораздо быстрее, чем мой рабочий компьютер, который работал почти на 31 секунду при 2,93 ГГц Core 2 Duo.

php - 757 символов вкл. новые линии

<?php
function getTime() {
    $t = explode( ' ', microtime() );
    $t = $t[1] + $t[0];
    return $t;
}
function isDecimal($val){ return is_numeric($val) && floor($val) != $val;}
$start = getTime();
$semi_prime = 8980935344490257;
$slice      = round(strlen($semi_prime)/2);
$max        = (pow(10, ($slice))-1);
$i          = 3;
echo "\nFactoring the semi-prime:\n$semi_prime\n\n";

while ($i < $max) {
    $sec_factor = ($semi_prime/$i);
    if (isDecimal($sec_factor) != 1) {
        $mod_f = bcmod($i, 1);
        $mod_s = bcmod($sec_factor, 1);
        if ($mod_f == 0 && $mod_s == 0) {
            echo "First factor = $i\n";
            echo "Second factor = $sec_factor\n";
            $end=getTime();
            $xtime=round($end-$start,4).' seconds';
            echo "\n$xtime\n";
            exit();
        }
    }
    $i += 2;
}
?>

Мне было бы интересно увидеть этот же алгоритм на c или другом компилируемом языке.

jdstankosky
источник
Числа PHP имеют точность только 53 бита, примерно 16 десятичных цифр
скопируйте
3
Реализация того же алгоритма в C ++ с использованием 64-битных целых чисел заняла всего около 1,8 секунд для запуска на моем компьютере. Однако у этого подхода есть несколько проблем: 1. Он не может обрабатывать достаточно большие числа. 2. Даже если бы оно могло (и предполагало, что все числа, независимо от длины) использовать одинаковое количество времени для пробного деления, каждое увеличение на порядок приводило бы к эквивалентному увеличению времени. Поскольку ваш первый множитель примерно на 14 порядков меньше заданного первого фактора, этот алгоритм потребует более 9 миллионов лет, чтобы вычислить заданный полупростой размер.
CasaDeRobison
Я не лучший в математике, по общему признанию, но для очень больших чисел стандартные методы факторинга полу-простых чисел просто не будут работать (используя elipse и т. Д.), Насколько я знаю. Имея это в виду, как можно улучшить сам алгоритм?
jdstankosky
2
Решето Эратосфена начинается со списком номера, а затем удаляет все кратные 2, а затем 3, а затем 5, а затем 7 и т.д. То , что остается после того, как решето завершено только простые числа. Это сито может быть «предварительно рассчитано» для определенного количества факторов. Потому lcm(2, 3, 5, 7) == 210что шаблон чисел, устраняемый этими факторами, будет повторяться каждые 210 чисел, и остаются только 48. Таким образом, вы можете исключить 77% всех чисел из пробного деления вместо 50%, беря только шансы.
Примо
1
@primo Из любопытства, сколько времени ты посвятил этому? Мне потребовались бы годы, чтобы подумать об этом. В то время, когда я писал это, я думал только о том, как простые числа всегда были нечетными. Я не пытался выходить за рамки этого и исключать непростые шансы. Это кажется таким простым в ретроспективе.
jdstankosky