Nhân tố bán kết nhanh nhất


28

Viết chương trình tính hệ số bán nguyên tố trong khoảng thời gian ngắn nhất.

Đối với mục đích thử nghiệm, hãy sử dụng mục này: 38! +1 (523022617466601111760007224100074291200000001)

Nó tương đương với: 14029308060317546154181 × 37280713718589679646221


2
Mặc dù tôi thích bit "nhanh nhất", vì nó mang lại lợi thế cho các ngôn ngữ như C so với các ngôn ngữ mã hóa thông thường, tôi tự hỏi làm thế nào bạn sẽ kiểm tra kết quả?
Ông Lister

1
Nếu bạn có nghĩa là 12259243sẽ được sử dụng để kiểm tra các chương trình nhanh như thế nào, thì kết quả sẽ nhỏ đến mức bạn sẽ không nhận được bất kỳ sự khác biệt có ý nghĩa thống kê nào.
Peter Taylor

Tôi đã thêm một số lượng lớn hơn, thx cho những người đứng đầu.
Soham Chowdhury

@Mr Lister, tôi sẽ kiểm tra nó trên PC của chính tôi.
Soham Chowdhury

5
inb4 ai đó sử dụng lạm dụng tiền xử lý để viết bảng tra cứu 400 exabyte.
Wug

Câu trả lời:


59

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

Sử dụng một sàng đa phương bậc hai . Tôi coi đây là một thách thức về mã, vì vậy tôi đã chọn không sử dụng bất kỳ thư viện bên ngoài nào (ngoài logchức năng tiêu chuẩn , tôi cho rằng). Khi định thời gian, nên sử dụng JIT PyPy , vì nó cho thời gian nhanh hơn 4-5 lần so với cPython .

Cập nhật (2013-07-29):
Kể từ khi đăng bài ban đầu, tôi đã thực hiện một số thay đổi nhỏ nhưng quan trọng giúp tăng tốc độ tổng thể lên khoảng 2,5 lần.

Cập nhật (2014-08-27):
Vì bài đăng này vẫn đang được chú ý, tôi đã cập nhật my_math.pysửa hai lỗi, cho bất kỳ ai có thể đang sử dụng nó:

  • isqrtđã bị lỗi, đôi khi tạo ra đầu ra không chính xác cho các giá trị rất gần với một hình vuông hoàn hảo. Điều này đã được sửa chữa, và hiệu suất tăng lên bằng cách sử dụng hạt giống tốt hơn nhiều.
  • is_primeđã được cập nhật. Nỗ lực trước đây của tôi để loại bỏ 2 spps vuông hoàn hảo là tốt nhất. Tôi đã thêm một kiểm tra 3 nhánh - một kỹ thuật được Mathmatica sử dụng - để đảm bảo rằng giá trị được kiểm tra là không vuông.

Cập nhật (2014-11-24):
Nếu ở cuối phép tính không tìm thấy các đồng đẳng không tầm thường, thì progam hiện sàng lọc các đa thức bổ sung. Điều này trước đây đã được đánh dấu trong mã là 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

Mẫu 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

Lưu ý: không sử dụng --verbosetùy chọn sẽ cho thời gian tốt hơn một chút:

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

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

Các khái niệm cơ bản

Nói chung, một sàng bậc hai dựa trên quan sát sau: mọi n hỗn hợp lẻ có thể được biểu diễn dưới dạng:

Điều này không khó để xác nhận. Vì n là số lẻ nên khoảng cách giữa hai đồng yếu tố của n phải bằng 2d , trong đó x là trung điểm giữa chúng. Hơn nữa, cùng một mối quan hệ giữ cho bất kỳ bội số của n

Lưu ý rằng nếu có thể tìm thấy bất kỳ xd như vậy , nó sẽ ngay lập tức dẫn đến hệ số (không nhất thiết là số nguyên tố) của n , vì cả x + dx - d đều chia n theo định nghĩa. Mối quan hệ này có thể bị suy yếu thêm - do hậu quả của việc cho phép các đồng đẳng tầm thường tiềm năng - theo hình thức sau:

Vì vậy, nói chung, nếu chúng ta có thể tìm thấy hai hình vuông hoàn hảo tương đương mod n , thì có khả năng là chúng ta có thể trực tiếp tạo ra hệ số n a la gcd (x ± d, n) . Có vẻ khá đơn giản, phải không?

Ngoại trừ nó không phải. Nếu chúng tôi dự định tiến hành tìm kiếm toàn diện trên tất cả x có thể , chúng tôi sẽ cần tìm kiếm toàn bộ phạm vi từ [ n , √ ( 2n ) ], nhỏ hơn một chút so với phân chia thử nghiệm đầy đủ, nhưng cũng đòi hỏi một is_squarethao tác đắt tiền mỗi lần lặp đến xác nhận giá trị của d . Trừ khi nó được biết đến trước đó n có yếu tố rất gần n , chia phiên tòa có thể sẽ nhanh hơn.

Có lẽ chúng ta có thể làm suy yếu mối quan hệ này hơn nữa. Giả sử chúng ta đã chọn một x , sao cho

một thừa số nguyên tố đầy đủ của y là dễ dàng được biết đến. Nếu chúng ta có đủ các mối quan hệ như vậy, chúng ta sẽ có thể xây dựng một d đầy đủ , nếu chúng ta chọn một số y sao cho sản phẩm của họ là một hình vuông hoàn hảo; có nghĩa là, tất cả các yếu tố chính được sử dụng một số lần chẵn. Trong thực tế, nếu chúng ta có nhiều y như vậy hơn tổng số các thừa số nguyên tố duy nhất mà chúng chứa, một giải pháp được đảm bảo tồn tại; Nó trở thành một hệ phương trình tuyến tính. Câu hỏi bây giờ trở thành, làm thế nào để chúng ta chọn x như vậy ? Đó là nơi sàng lọc phát huy tác dụng.

Sàng

Xét đa thức:

Khi đó với bất kỳ số nguyên tố p và số nguyên k nào , điều sau đây là đúng:

Điều này có nghĩa là sau khi giải các gốc của mod đa thức p - nghĩa là bạn đã tìm thấy một x sao cho y (x) ≡ 0 (mod p) , ergo y chia hết cho p - khi đó bạn đã tìm thấy một số vô hạn của x . Theo cách này, bạn có thể sàng qua một phạm vi x , xác định các thừa số nguyên tố nhỏ của y , hy vọng tìm thấy một số yếu tố mà tất cả các yếu tố chính đều nhỏ. Những số như vậy được gọi là k-smooth , trong đó k là thừa số nguyên tố lớn nhất được sử dụng.

Có một vài vấn đề với cách tiếp cận này, mặc dù. Không phải tất cả các giá trị của x là đầy đủ, trên thực tế, chỉ có rất ít trong số họ, tập trung vào n . Các giá trị nhỏ hơn sẽ trở thành phần lớn âm (do thuật ngữ -n ) và các giá trị lớn hơn sẽ trở nên quá lớn, do đó không chắc là hệ số nguyên tố của chúng chỉ bao gồm các số nguyên tố nhỏ. Sẽ có một số x như vậy , nhưng trừ khi tổng hợp mà bạn bao thanh toán là rất nhỏ, rất khó có khả năng bạn sẽ tìm thấy đủ độ mịn để dẫn đến một hệ số. Và vì vậy, đối với n lớn hơn , cần phải sàng qua nhiều đa thức của một dạng đã cho.

Đa thức

Vậy chúng ta cần thêm đa thức để sàng? Còn cái này thì sao:

Điều đó sẽ làm việc. Lưu ý rằng AB theo nghĩa đen có thể là bất kỳ giá trị nguyên nào và toán học vẫn giữ. Tất cả những gì chúng ta cần làm là chọn một vài giá trị ngẫu nhiên, giải quyết gốc của đa thức và sàng các giá trị gần bằng không. Tại thời điểm này, chúng tôi chỉ có thể gọi nó là đủ tốt: nếu bạn ném đủ đá theo các hướng ngẫu nhiên, bạn sẽ sớm phá vỡ một cửa sổ sớm hơn.

Ngoại trừ, có một vấn đề với điều đó quá. Nếu độ dốc của đa thức lớn tại giao thoa x, thì nó sẽ là nếu nó không tương đối bằng phẳng, sẽ chỉ có một vài giá trị phù hợp để sàng cho mỗi đa thức. Nó sẽ hoạt động, nhưng cuối cùng bạn sẽ sàng lọc được rất nhiều đa thức trước khi bạn có được thứ bạn cần. Chúng ta có thể làm tốt hơn không?

Chúng ta có thể làm tốt hơn. Một quan sát, kết quả của Montgomery là như sau: nếu AB được chọn sao cho tồn tại một số C thỏa mãn

sau đó toàn bộ đa thức có thể được viết lại thành

Hơn nữa, nếu A được chọn là một hình vuông hoàn hảo, thuật ngữ A hàng đầu có thể bị bỏ qua trong khi sàng, dẫn đến các giá trị nhỏ hơn nhiều và đường cong phẳng hơn nhiều. Để giải pháp như vậy tồn tại, n phải là mod dư lượng bậc hai A , có thể được biết ngay bằng cách tính biểu tượng Legendre : ( n | A ) = 1 . Lưu ý rằng để giải quyết cho B , một thừa số hoàn chỉnh thủ của √A cần được biết đến (theo thứ tự để có những vuông gốc mô-đun √n (mod √A) ), đó là lý do √A thường chọn để trở thành thủ.

Sau đó, có thể hiển thị rằng nếu , thì với tất cả các giá trị của x ∈ [ -M, M ] :

Và bây giờ, cuối cùng, chúng ta có tất cả các thành phần cần thiết để thực hiện sàng của chúng tôi. Hay chúng ta?

Quyền hạn của số nguyên tố là yếu tố

Sàng của chúng tôi, như được mô tả ở trên, có một lỗ hổng lớn. Nó có thể xác định giá trị nào của x sẽ dẫn đến một y chia hết cho p , nhưng nó không thể xác định liệu y này có chia hết cho một lũy thừa của p hay không . Để xác định điều đó, chúng ta sẽ cần thực hiện phân chia thử nghiệm trên giá trị được sàng, cho đến khi nó không còn chia hết cho p . Chúng tôi dường như đã đạt đến một sự ngẫu hứng: toàn bộ điểm của sàng là để chúng tôi không phải làm điều đó. Thời gian để kiểm tra playbook.

Điều đó có vẻ khá hữu ích. Nếu tổng ln của tất cả các thừa số nguyên tố nhỏ của y gần với giá trị kỳ vọng của ln (y) , thì gần như chắc chắn rằng y không có yếu tố nào khác. Ngoài ra, nếu chúng ta điều chỉnh giá trị mong đợi xuống một chút, chúng ta cũng có thể xác định các giá trị là mịn có một số quyền của các số nguyên tố là các yếu tố. Theo cách này, chúng ta có thể sử dụng sàng như một quy trình 'sàng lọc trước' và chỉ tính đến các giá trị có khả năng trơn tru.

Điều này có một vài lợi thế khác là tốt. Lưu ý rằng các số nguyên tố nhỏ đóng góp rất ít vào tổng ln , nhưng chúng đòi hỏi nhiều thời gian sàng nhất. Việc chọn giá trị 3 đòi hỏi nhiều thời gian hơn 11, 13, 17, 19 và 23 cộng lại . Thay vào đó, chúng ta chỉ có thể bỏ qua một vài số nguyên tố đầu tiên và điều chỉnh ngưỡng xuống cho phù hợp, giả sử một tỷ lệ nhất định trong số chúng sẽ vượt qua.

Một kết quả khác, là một số giá trị sẽ được phép 'lướt qua', phần lớn trơn tru, nhưng chứa một đồng yếu tố lớn. Chúng tôi chỉ có thể loại bỏ các giá trị này, nhưng giả sử chúng tôi đã tìm thấy một giá trị chủ yếu khác, với cùng một đồng yếu tố. Sau đó chúng ta có thể sử dụng hai giá trị này để xây dựng một y có thể sử dụng được ; vì sản phẩm của họ sẽ chứa bình phương lớn này bình phương, nên nó không còn cần phải xem xét.

Để tất cả chúng cùng nhau

Điều cuối cùng chúng ta cần làm là sử dụng các giá trị này của y để xây dựng một xd đầy đủ . Giả sử chúng ta chỉ xem xét các yếu tố không vuông của y , nghĩa là các yếu tố chính của một sức mạnh kỳ lạ. Sau đó, mỗi y có thể được thể hiện theo cách sau:

có thể được thể hiện dưới dạng ma trận:

Vấn đề sau đó trở thành để tìm một vector vVM =(mod 2) , nơi là vector null. Đó là, để giải quyết cho không gian rỗng bên trái của M . Điều này có thể được thực hiện theo một số cách, cách đơn giản nhất là thực hiện Loại bỏ Gaussian trên M T , thay thế thao tác thêm hàng bằng một xor hàng . Điều này sẽ dẫn đến một số vectơ cơ sở không gian rỗng, bất kỳ sự kết hợp nào sẽ tạo ra một giải pháp hợp lệ.

Việc xây dựng x khá đơn giản. Nó chỉ đơn giản là sản phẩm của Axe + B cho mỗi y được sử dụng. Việc xây dựng d hơi phức tạp hơn một chút. Nếu chúng ta lấy sản phẩm của tất cả y , chúng ta sẽ có giá trị bằng 10 nghìn, nếu không phải là 100 nghìn chữ số, chúng ta cần tìm căn bậc hai. Canxi này là chính thức đắt tiền. Thay vào đó, chúng ta có thể theo dõi sức mạnh của các số nguyên tố trong quá trình sàng, sau đó sử dụng và các phép toán xor trên các vectơ của các thừa số không vuông để tái tạo căn bậc hai.

Tôi dường như đã đạt đến giới hạn 30000 ký tự. Ahh tốt, tôi cho rằng đó là đủ tốt.


5
Chà, tôi chưa bao giờ thi đại số ở trường trung học (thực sự đã bỏ học trong học kỳ đầu tiên của năm thứ nhất), nhưng bạn làm cho nó đơn giản để hiểu từ quan điểm của một lập trình viên. Tôi sẽ không giả vờ hiểu nó hoàn toàn mà không đưa nó vào thực tế, nhưng tôi hoan nghênh bạn. Bạn nên xem xét mở rộng bài đăng này ngoài trang web và xuất bản nó, nghiêm túc!
jdstankosky

2
Tôi đồng ý. Câu trả lời tuyệt vời với một lời giải thích tuyệt vời. +1
Soham Chowdhury

1
@primo Câu trả lời của bạn cho nhiều câu hỏi ở đây đã cực kỳ kỹ lưỡng và thú vị. Nhiều đánh giá cao!
Paul Walls

4
Như một nhận xét cuối cùng, tôi muốn bày tỏ lòng biết ơn của mình tới Will Ness vì đã trao giải thưởng tiền thưởng +100 cho câu hỏi này. Đó thực sự là toàn bộ danh tiếng của anh ấy.
primo

2
@StepHen nó làm. Thật không may, nó sử dụng phiên bản gốc từ năm 2012, không có sự cải thiện về tốc độ và có lỗi trong việc loại bỏ gaussian (lỗi khi cột cuối cùng là cột trụ). Tôi đã cố gắng liên lạc với tác giả một thời gian trước, nhưng không nhận được phản hồi.
primo

2

Chà, 38! +1 của bạn đã phá vỡ tập lệnh php của tôi, không biết tại sao. Trong thực tế, bất kỳ bán nguyên tố nào dài hơn 16 chữ số đều phá vỡ tập lệnh của tôi.

Tuy nhiên, bằng cách sử dụng 8980935344490257 (86028157 * 104395301) tập lệnh của tôi đã quản lý thời gian 25.963 giây trên máy tính ở nhà của tôi (AMD Phenom 9950 tốc độ 2,61 GHz). Nhanh hơn rất nhiều so với máy tính làm việc của tôi gần 31 giây @ 2,93 GHz Core 2 Duo.

php - 757 chars bao gồm. dòng mới

<?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;
}
?>

Tôi rất muốn thấy thuật toán tương tự này trong c hoặc một số ngôn ngữ được biên dịch khác.


Các số của PHP chỉ có độ chính xác 53 bit, khoảng 16 chữ số thập phân
sao chép

3
Việc thực hiện cùng một thuật toán trong C ++ bằng số nguyên 64 bit chỉ mất khoảng 1,8 giây để chạy trên máy tính của tôi. Có một số vấn đề với cách tiếp cận này: 1. Nó không thể xử lý số lượng đủ lớn. 2. Ngay cả khi nó có thể & giả sử tất cả các số, bất kể độ dài, sử dụng cùng một lượng thời gian để phân chia thử nghiệm, mỗi thứ tự tăng cường độ sẽ dẫn đến tăng thời gian tương đương. Vì yếu tố đầu tiên của bạn nhỏ hơn khoảng 14 bậc so với yếu tố đầu tiên đã cho, thuật toán này sẽ mất hơn 9 triệu năm để tính hệ số bán thời gian đã cho.
CasaDeRobison

Tôi không phải là người giỏi nhất môn toán, nhưng với số lượng rất lớn, các phương pháp tiêu chuẩn bao thanh toán bán phần sẽ đơn giản là không hoạt động (sử dụng một hình elip, v.v.), theo như tôi biết. Với ý nghĩ đó, làm thế nào thuật toán có thể được cải thiện?
jdstankosky

2
Các Sieve of Eratosthenes bắt đầu với một danh sách các số, sau đó loại bỏ tất cả các bội số của 2, và sau đó 3, và sau đó 5, và sau đó 7 vv Những gì còn lại sau khi rây xong là chỉ số nguyên tố. Sàng này có thể được 'chuẩn bị trước' cho một số yếu tố nhất định. Bởi vì lcm(2, 3, 5, 7) == 210, mẫu số bị loại bỏ bởi các yếu tố này sẽ lặp lại sau mỗi 210 số và chỉ còn lại 48 số. Bằng cách đó, bạn có thể loại bỏ 77% tất cả các số khỏi phân chia thử nghiệm, thay vì 50% bằng cách chỉ lấy tỷ lệ cược.
Primo

1
@primo Vì tò mò, bạn đã dành bao nhiêu thời gian cho việc này? Nó sẽ khiến tôi mất nhiều thời gian để nghĩ về công cụ này. Vào thời điểm tôi viết bài này, tôi chỉ nghĩ về việc các số nguyên tố luôn luôn là số lẻ. Tôi đã không cố gắng vượt qua điều đó và cũng loại bỏ tỷ lệ cược không chính. Có vẻ như rất đơn giản khi nhìn lại.
jdstankosky
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.