Найшвидша напівпровідна факторизація


28

Напишіть програму, щоб розподілити напівпросте число за найкоротший час.

Для тестування використовуйте це: 38! +1 (523022617466601111760007224100074291200000001)

Він дорівнює: 14029308060317546154181 × 37280713718589679646221


2
Хоча мені подобається "найшвидший" біт, оскільки він дає таким мовам, як C перевагу перед типовими мовами кодового гольфу, мені цікаво, як ви перевірите результати?
Містер Лістер

1
Якщо ви маєте на увазі, що 12259243буде використано для перевірки наскільки швидкі програми, результати будуть настільки малі, що статистичних значущих відмінностей ви не отримаєте.
Пітер Тейлор

Я додав більшу кількість, thx за голови вгору.
Soham Chowdhury

@Mr Lister, я перевірю його на власному ПК.
Soham Chowdhury

5
inb4 хтось використовує зловживання препроцесором, щоб написати таблицю пошуку 400 екзабайтів.
Вуг

Відповіді:


59

Python (w / PyPy JIT v1.9) ~ 1.9s

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

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

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

  • isqrtбув несправним, іноді даючи неправильний вихід для значень, близьких до ідеального квадрата. Це було виправлено, а продуктивність збільшилась за допомогою набагато кращого насіння.
  • is_primeбуло оновлено. Моя попередня спроба видалити ідеальні квадратні 2-спритні в кращому випадку була напівсерця. Я додав перевірку 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 має бути парним 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 . Якщо заздалегідь невідомо, що n має чинників, що знаходяться дуже близько n , пробний поділ може бути швидшим.

Можливо, ми можемо ще більше послабити це відношення. Припустимо, ми обрали х , такий, що для

повна основна факторизація y легко відома. Якби у нас було достатньо таких відносин, ми повинні бути в змозі побудувати адекватний d , якщо ми виберемо кількість y таких, щоб їх добуток був ідеальним квадратом; тобто всі прості фактори використовуються парною кількістю разів. Насправді, якщо у нас більше таких y, ніж загальна кількість унікальних простих факторів, які вони містять, рішення гарантовано існує; Це стає системою лінійних рівнянь. Тепер стає питання, як ми обрали такий х ? Ось тут і грає сито.

Сито

Розглянемо многочлен:

Тоді для будь-яких простих p і цілих k справедливо таке:

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

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

Кілька поліномів

Тож нам потрібно більше поліномів для просіювання? Як щодо цього:

Це спрацює. Зауважте, що A і B можуть бути буквально будь-якими цілими значеннями, а математика все-таки дотримується. Все, що нам потрібно зробити, - це вибрати кілька випадкових значень, вирішити для кореня многочлена і просіяти значення, близькі до нуля. На даний момент ми можемо просто назвати це досить добре: якщо ви кинете достатньо каміння у випадкових напрямках, ви рано чи пізно зламаєте вікно.

За винятком, що з цим теж є проблема. Якщо нахил полінома великий при переході x, який він буде, якщо він не відносно рівний, буде лише кілька підходящих значень для просіювання на поліном. Це спрацює, але ви в кінцевому підсумку просіюєте цілу кількість поліномів, перш ніж отримаєте те, що вам потрібно. Чи можемо ми зробити краще?

Ми можемо зробити краще. Спостереження в результаті Монтгомері таке: якщо A і B обрані такими, що існує деяка C, що задовольняє

тоді весь многочлен можна переписати як

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

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

І ось, нарешті, у нас є всі компоненти, необхідні для реалізації нашого сита. Або ми?

Повноваження праймів як чинників

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

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

Це має і кілька інших переваг. Зверніть увагу , що невеликі штрихи сприяють дуже мало для ¯Ln суми, але все ж вони вимагають самого решета часу. Для просіювання значення 3 потрібно більше часу, ніж 11, 13, 17, 19 та 23 разом . Натомість, ми можемо просто пропустити перші кілька простих розмірів і відповідно відкоригувати поріг, припускаючи, що певний відсоток з них пройшов би.

Іншим результатом є те, що ряд значень буде дозволений "проскочити", які в основному є гладкими, але містять один великий коефіцієнт. Ми могли б просто відкинути ці значення, але припустимо, що ми знайшли ще одне, здебільшого гладке значення, з точно таким же коефіцієнтом. Потім ми можемо використовувати ці два значення для побудови корисного y ; оскільки їхній продукт буде містити цей великий кофактор у квадраті, його більше не потрібно розглядати.

Збираючи все це разом

Останнє, що нам потрібно зробити, - це використовувати ці значення y, щоб побудувати адекватні x і d . Припустимо, ми розглянемо лише неквадратичні фактори y , тобто прості фактори коефіцієнта непарної сили. Тоді кожне y може бути виражене таким чином:

які можна виразити у матричній формі:

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

Побудова x досить прямолінійна. Це просто добуток Ax + B для кожного з використаних y . Побудова d трохи складніше. Якби ми взяли добуток усього y , ми отримаємо значення з 10s тисяч, якщо не 100s тисяч цифр, для яких нам потрібно знайти квадратний корінь. Цей розрахунок недоцільно дорогий. Натомість ми можемо відслідковувати рівномірні сили прайменів під час процесу просіювання, а потім використовувати і та операції xor на векторах неквадратних факторів для реконструкції квадратного кореня.

Я, здається, досяг границі 30000 символів. Ну добре, я гадаю, що це досить добре.


5
Ну, я ніколи не передавав алгебру в середній школі (насправді випав під час першого семестру першокурсників), але ви полегшуєте розуміння з точки зору програміста. Я не буду робити вигляд, що повністю розумію це, не втілюючи його в життя, але я аплодую вам. Ви повинні розглянути питання про розширення цієї публікації за межами сайту та публікацію її серйозно!
jdstankosky

2
Я згоден. Відмінна відповідь з чудовим поясненням. +1
Soham Chowdhury

1
@primo Ваші відповіді на кілька запитань тут були надзвичайно ретельними та цікавими. Цінується!
Пол Стін

4
Як останнє зауваження, я хотів би висловити вдячність Віллі Несс за нагороду +100 щедрості за це питання. Це була буквально вся його репутація.
примо

2
@StepHen це робить. На жаль, він використовує оригінальну версію з 2012 року, без покращення швидкості та з помилкою при гаусовій елімінації (помилки, коли кінцевий стовпчик є зведеним стовпцем). Я намагався зв’язатися з автором деякий час тому, але відповіді не отримав.
примо

2

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

Однак, використовуючи 8980935344490257 (86028157 * 104395301), мій сценарій на моєму домашньому комп'ютері (2,61 ГГц AMD Phenom 9950) зумів час 25,963 секунди . Набагато швидше, ніж мій робочий комп'ютер, який був майже 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 чи іншій мові компіляції.


Числа PHP мають лише 53 біт точності, приблизно 16 десяткових цифр
копіюйте

3
Реалізація того ж алгоритму в C ++ за допомогою 64-бітових цілих чисел займала лише 1,8 секунди. Однак у цього підходу є кілька проблем: 1. Він не може впоратися з достатньою кількістю. 2. Навіть якщо б це могло і припускаючи, що всі числа, незалежно від тривалості, використовували однакову кількість часу для пробного поділу, кожен порядок збільшення величини призведе до еквівалентного збільшення часу. Оскільки ваш перший коефіцієнт приблизно на 14 порядків менший, ніж даний перший коефіцієнт, то цей алгоритм зайняв би 9 мільйонів років, щоб підрахувати дану напівпервіну.
CasaDeRobison

Я, мабуть, не найкращий в математиці, але для дуже великої кількості стандартні методи факторингу напівпрості просто не спрацюють (використовуючи еліпси тощо), наскільки я знаю. Зважаючи на це, як можна було вдосконалити сам алгоритм?
jdstankosky

2
Решето Ератосфена починається зі списком номера, а потім видаляє всі кратні 2, а потім 3, а потім 5, а потім 7 і т.д. Те , що залишається після того, як решето завершено тільки прості числа. Це сито може бути «попередньо проціджене» для певної кількості факторів. Тому lcm(2, 3, 5, 7) == 210що шаблон, що ліквідується цими факторами, повторюватиметься кожні 210 чисел, а залишається лише 48. Таким чином, ви можете вилучити 77% усіх чисел із пробного поділу, а не 50%, взявши лише шанси.
прим

1
@primo З цікавості, скільки часу ви присвятили цьому? Мені знадобилося б віки думати про цей матеріал. У той час, коли я це писав, я думав лише про те, як прості номери завжди були непарними. Я не намагався вийти за рамки цього і усунути непрості шанси. Це виглядає так просто в ретроспективі.
jdstankosky
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.