การแยกตัวประกอบ semiprime เร็วที่สุด


28

เขียนโปรแกรมเพื่อแยกจำนวนกึ่งนายกรัฐมนตรีในเวลาที่สั้นที่สุด

สำหรับจุดประสงค์ในการทดสอบใช้สิ่งนี้: 38! +1 (523022617466601111760007224100074291200000001)

มันเท่ากับ: 14029308060317546154181 × 37280713718589679646221


2
ในขณะที่ฉันชอบบิตที่ "เร็วที่สุด" เพราะมันให้ประโยชน์กับภาษาอย่าง C มากกว่าภาษา codegolfing ทั่วไปฉันสงสัยว่าคุณจะทดสอบผลลัพธ์อย่างไร
Mr Lister

1
หากคุณหมายความว่า12259243จะใช้ในการทดสอบความเร็วของโปรแกรมผลลัพธ์จะเล็กจนคุณจะไม่ได้รับความแตกต่างอย่างมีนัยสำคัญทางสถิติ
Peter Taylor

ฉันเพิ่มจำนวนที่มากขึ้นขอบคุณสำหรับหัวขึ้น
Soham Chowdhury

@ Mister Lister ฉันจะทดสอบบนพีซีของฉันเอง
Soham Chowdhury

5
inb4 มีคนใช้การละเมิดพรีโปรเซสเซอร์ในการเขียนตารางการค้นหา 400 ไบต์
Wug

คำตอบ:


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):
หากในตอนท้ายของการคำนวณไม่พบความไม่สอดคล้องที่ไม่สำคัญตอนนี้ progam จะกรองพหุนามเพิ่มเติม 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

ตัวอย่าง 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 - วันที่ทั้งสองหารnโดยความหมาย ความสัมพันธ์นี้สามารถลดลงได้อีกซึ่งเป็นผลมาจากการอนุญาตให้มีความสอดคล้องเล็กน้อยที่อาจเกิดขึ้นกับแบบฟอร์มต่อไปนี้:

ดังนั้นโดยทั่วไปถ้าเราสามารถหาสองสี่เหลี่ยมที่สมบูรณ์แบบซึ่งเทียบเท่าสมัย nแล้วก็ค่อนข้างมีแนวโน้มที่เราโดยตรงสามารถผลิตปัจจัยของnลาGCD (x ± d, n) ดูเหมือนง่ายสวยใช่มั้ย

ยกเว้นมันไม่ใช่ ถ้าเราตั้งใจที่จะดำเนินการค้นหาหมดจดกว่าเป็นไปได้ทั้งหมดxเราจะต้องค้นหาทั้งช่วงจาก [ n , √ ( 2n ) ] ซึ่งเป็นเล็กน้อยมีขนาดเล็กกว่าส่วนการพิจารณาคดีเต็ม แต่ยังต้องมีราคาแพงis_squareการดำเนินงานแต่ละซ้ำไป ยืนยันค่าของd เว้นแต่เป็นกรณีที่เป็นที่รู้จักกันก่อนที่nมีปัจจัยมากใกล้nส่วนการพิจารณาคดีมีแนวโน้มที่จะได้เร็วขึ้น

บางทีเราอาจทำให้ความสัมพันธ์นี้อ่อนแอลงยิ่งกว่าเดิม สมมติว่าเราเลือกxเช่นนั้น

รู้จักการแยกตัวประกอบเฉพาะของyอย่างเต็มที่ หากเรามีความสัมพันธ์ดังกล่าวเพียงพอเราควรจะสามารถสร้างd ที่เพียงพอได้ถ้าเราเลือกจำนวนyเพื่อให้ผลิตภัณฑ์ของพวกเขาเป็นสี่เหลี่ยมจัตุรัสที่สมบูรณ์ นั่นคือปัจจัยสำคัญทั้งหมดจะถูกใช้เป็นจำนวนเท่าตัว ในความเป็นจริงถ้าเรามีyมากกว่าจำนวนรวมของปัจจัยสำคัญเฉพาะที่มีอยู่รับประกันว่าจะมีอยู่จริง มันกลายเป็นระบบของสมการเชิงเส้น ตอนนี้กลายเป็นคำถามเราจะเลือกxอย่างนั้นได้อย่างไร? นั่นคือสิ่งที่ sieving เข้ามาเล่น

ตะแกรง

พิจารณาพหุนาม:

จากนั้นสำหรับไพร์มpและเลขจำนวนเต็มkค่าต่อไปนี้เป็นจริง:

ซึ่งหมายความว่าหลังจากแก้หารากของพหุนามmod p - นั่นคือคุณได้พบxเช่นy (x) ≡ 0 (mod p) , ergo yหารด้วยp - แล้วคุณได้พบจำนวนอนันต์ เช่นx ด้วยวิธีนี้คุณสามารถกรองช่วงของxโดยระบุปัจจัยเฉพาะขนาดเล็กของyหวังว่าจะได้พบกับปัจจัยสำคัญทั้งหมดที่มีขนาดเล็ก ตัวเลขดังกล่าวรู้จักกันในชื่อk-smoothโดยที่kเป็นตัวประกอบที่สำคัญที่สุดที่ใช้

แม้ว่าจะมีปัญหาเล็กน้อยเกี่ยวกับวิธีการนี้ ไม่ได้ค่าทั้งหมดของxที่เพียงพอในความเป็นจริงมีเพียงมากน้อยของพวกเขาแน่นิ่ง n ค่าที่น้อยลงจะกลายเป็นค่าลบส่วนใหญ่ (เนื่องจากคำว่า-n ) และค่าที่มากขึ้นจะกลายเป็นค่าที่ใหญ่เกินไปเช่นว่ามันไม่น่าเป็นไปได้ที่การแยกตัวประกอบเฉพาะของพวกเขาจะประกอบไปด้วยเฉพาะช่วงเวลาเล็ก ๆ จะมีจำนวนของxดังกล่าวแต่ถ้าคอมโพสิตที่คุณแยกตัวประกอบมีขนาดเล็กมากมันไม่น่าเป็นไปได้สูงที่คุณจะพบว่าผิวเรียบเนียนพอที่จะทำให้เกิดการแยกตัวประกอบ ดังนั้นสำหรับn ที่ใหญ่กว่ามันจำเป็นต้องกรองหลายพหุนามของรูปแบบที่กำหนด

หลายพหุนาม

ดังนั้นเราจึงต้องการพหุนามอีกมากมาย เกี่ยวกับสิ่งนี้:

นั่นจะได้ผล โปรดทราบว่าAและBอาจเป็นค่าจำนวนเต็มใด ๆ และคณิตศาสตร์ยังคงมีอยู่ สิ่งที่เราต้องทำคือเลือกค่าสุ่มสองสามค่าแก้หารากของพหุนามและกรองค่าใกล้กับศูนย์ เมื่อถึงจุดนี้เราก็เรียกมันว่าดีพอ: ถ้าคุณขว้างก้อนหินไปในทิศทางที่สุ่มมากพอคุณจะต้องแตกหน้าต่างไม่ช้าก็เร็ว

ยกเว้นมีปัญหากับที่เกินไป หากความชันของพหุนามมีขนาดใหญ่ที่จุดตัดแกน x ซึ่งจะเป็นถ้ามันไม่ค่อนข้างแบนจะมีค่าที่เหมาะสมเพียงไม่กี่ที่จะกรองต่อพหุนาม มันจะใช้งานได้ แต่คุณจะได้จำนวนมากมายของพหุนามก่อนที่จะได้รับสิ่งที่คุณต้องการ เราทำได้ดีกว่านี้ไหม

เราทำได้ดีกว่า ข้อสังเกตซึ่งเป็นผลมาจากมอนต์โกเมอรี่มีดังนี้: ถ้าเลือกAและBเช่นนั้นจะมีCพอใจอยู่บ้าง

จากนั้นพหุนามทั้งหมดสามารถเขียนใหม่เป็น

นอกจากนี้หากถูกเลือกให้เป็นตารางที่สมบูรณ์แบบชั้นนำระยะสามารถละเลยในขณะที่ sieving ผลในค่าที่มีขนาดเล็กมากและโค้งประจบมาก สำหรับการแก้ปัญหาดังกล่าวจะมีชีวิตอยู่, nจะต้องเป็นกำลังสองตกค้างmodซึ่งสามารถเป็นที่รู้จักทันทีโดยคำนวณสัญลักษณ์ Legendre : ( n | √A ) = 1 โปรดทราบว่าเพื่อที่จะแก้ปัญหาสำหรับBจะต้องทราบการแยกตัวประกอบเฉพาะที่สมบูรณ์ของ√A (เพื่อใช้สแควร์รูทแบบแยกส่วน√n (mod √A) ) ซึ่งเป็นเหตุผลว่าทำไม√Aจึงถูกเลือกให้เป็นนายก

จากนั้นจะสามารถแสดงให้เห็นว่าหากจากนั้นสำหรับค่าทั้งหมดของx ∈ [ -M, M ] :

และตอนนี้ในที่สุดเรามีส่วนประกอบทั้งหมดที่จำเป็นในการใช้ตะแกรงของเรา หรือพวกเรา

พลังแห่งกาลเป็นปัจจัย

ตะแกรงของเราดังที่อธิบายไว้ข้างต้นมีข้อบกพร่องที่สำคัญอย่างหนึ่ง มันสามารถระบุได้ว่าค่าใดของxจะส่งผลให้yหารด้วยpได้ แต่ไม่สามารถระบุได้ว่าyนี้หารด้วยพลังของpได้หรือไม่ เพื่อตรวจสอบว่าเราจะต้องดำเนินการส่วนการพิจารณาคดีในค่าที่จะร่อนจนกว่าจะไม่มีอีกต่อไปหารด้วยP ดูเหมือนว่าเราจะไปถึงทางตัน: ​​จุดรวมของตะแกรงนั้นเพื่อที่เราจะได้ไม่ต้องทำอย่างนั้น ใช้เวลาในการตรวจสอบ playbook

นั่นดูมีประโยชน์ทีเดียว หากผลรวมของlnของปัจจัยสำคัญทั้งหมดเล็ก ๆ ของyใกล้เคียงกับค่าที่คาดหวังของln (y) , มันก็เกือบจะเป็นได้ว่าyไม่มีปัจจัยอื่น ๆ นอกจากนี้หากเราปรับค่าที่คาดหวังลงเล็กน้อยเราสามารถระบุค่าได้อย่างราบรื่นซึ่งมีอำนาจหลายช่วงเวลาเป็นปัจจัย ด้วยวิธีนี้เราสามารถใช้ตะแกรงเป็นกระบวนการ 'คัดกรองล่วงหน้า' และพิจารณาเฉพาะค่าที่น่าจะราบรื่น

นี่มีข้อดีอื่น ๆ เช่นกัน โปรดทราบว่าจำนวนเฉพาะน้อยมีผลต่อlnน้อยมากแต่ถึงกระนั้นพวกเขาก็ต้องการเวลาร่อนมากที่สุด sieving มูลค่า 3 ต้องใช้เวลามากกว่า 11, 13, 17, 19, และ 23 รวม แต่เราสามารถข้ามช่วงเวลาสองสามช่วงแรกและปรับเกณฑ์ลงตามนั้นโดยสมมติว่าเปอร์เซ็นต์ที่แน่นอนของพวกเขาจะผ่านไป

ผลลัพธ์อื่นคือจำนวนของค่าที่ได้รับอนุญาตให้ 'ส่งผ่าน' ซึ่งส่วนใหญ่จะราบรื่น แต่มีปัจจัยร่วมขนาดใหญ่เพียงครั้งเดียว เราสามารถทิ้งค่าเหล่านี้ได้ แต่สมมติว่าเราพบค่าที่ราบรื่นเป็นส่วนใหญ่โดยมีปัจจัยร่วมเดียวกัน แล้วเราสามารถใช้ทั้งสองค่าที่จะสร้างใช้งานY ; เนื่องจากผลิตภัณฑ์ของพวกเขาจะมีปัจจัยร่วมขนาดใหญ่กำลังสองจึงไม่จำเป็นต้องพิจารณาอีกต่อไป

วางมันทั้งหมดเข้าด้วยกัน

สิ่งสุดท้ายที่เราต้องทำคือการใช้ค่าเหล่านี้ของปีสร้างเพียงพอxและd สมมติว่าเราพิจารณาเฉพาะปัจจัยที่ไม่ใช่กำลังสองของyนั่นคือปัจจัยสำคัญของพลังคี่ จากนั้นแต่ละyสามารถแสดงในลักษณะดังต่อไปนี้:

ซึ่งสามารถแสดงในรูปแบบเมทริกซ์:

จากนั้นปัญหาจะกลายเป็นการค้นหาเวกเตอร์vเช่นนั้นvM =(mod 2)โดยที่เป็นเวกเตอร์โมฆะ นั่นคือการแก้สำหรับพื้นที่ null ซ้ายM ซึ่งสามารถทำได้ในหลายวิธีที่ง่ายที่สุดในการที่จะดำเนินการกำจัดแบบเกาส์ในM Tเปลี่ยนการดำเนินงานนอกจากนี้แถวที่มีแถวxor สิ่งนี้จะส่งผลให้มีเวกเตอร์พื้นฐานพื้นที่ว่างจำนวนหนึ่งซึ่งการรวมกันใด ๆ จะสร้างโซลูชันที่ถูกต้อง

การสร้างxค่อนข้างตรงไปตรงมา มันเป็นผลผลิตของAx + Bสำหรับแต่ละy ที่ใช้ การก่อสร้างของdซับซ้อนกว่าเล็กน้อย ถ้าเราจะเอาผลคูณของyทั้งหมด, เราจะจบลงด้วยค่า 10s ของหลักพัน, ถ้าไม่ใช่ 100s ของหลักพัน, ซึ่งเราต้องหาสแควร์รูท การคำนวณนี้มีราคาแพงมาก แต่เราสามารถติดตามพลังของจำนวนเฉพาะในระหว่างกระบวนการกรองและจากนั้นใช้และและการดำเนินงานxorบนเวกเตอร์ของปัจจัยที่ไม่ใช่สแควร์เพื่อสร้างรากที่สองใหม่

ฉันดูเหมือนจะมีจำนวนอักขระสูงสุดถึง 30,000 ตัว อ่าฉันคิดว่ามันดีพอ


5
ฉันไม่เคยผ่านพีชคณิตในโรงเรียนมัธยม (ออกจริงในช่วงภาคเรียนแรกของปีแรก) แต่คุณทำให้ง่ายต่อการเข้าใจจากมุมมองของโปรแกรมเมอร์ ฉันจะไม่แกล้งทำเป็นเข้าใจอย่างถ่องแท้โดยไม่ต้องฝึก แต่ฉันปรบมือให้คุณ คุณควรพิจารณาขยายโพสต์นี้นอกไซต์และเผยแพร่อย่างจริงจัง!
jdstankosky

2
ฉันเห็นด้วย. คำตอบที่ยอดเยี่ยมพร้อมคำอธิบายที่ดี +1
Soham Chowdhury

1
@primo คำตอบของคุณสำหรับคำถามมากมายที่นี่มีความละเอียดและน่าสนใจอย่างไม่น่าเชื่อ ชื่นชมมาก!
Paul Walls

4
เป็นคำพูดสุดท้ายที่ฉันต้องการแสดงความขอบคุณของฉันไปที่Will Nessสำหรับรางวัล +100 เงินรางวัลสำหรับคำถามนี้ มันเป็นชื่อเสียงทั้งหมดของเขาอย่างแท้จริง
โม่

2
@ ขั้นตอนที่มันทำ น่าเสียดายที่มันใช้เวอร์ชันดั้งเดิมตั้งแต่ปี 2012 โดยไม่มีการปรับปรุงความเร็วและมีข้อบกพร่องในการกำจัดแบบเกาส์เซียน (ข้อผิดพลาดเมื่อคอลัมน์สุดท้ายคือคอลัมน์เดือย) ฉันพยายามติดต่อผู้เขียนเมื่อไม่นานมานี้ แต่ไม่ได้รับคำตอบ
โม่

2

38! +1 ของคุณทำให้ PHP php ของฉันพังไม่แน่ใจว่าทำไม อันที่จริงมีกึ่งยาวเกิน 16 หลักแตกสคริปต์ของฉัน

อย่างไรก็ตามการใช้ 8980935344490257 (86028157 * 104395301) สคริปต์ของฉันจัดการเวลา25.963 วินาทีในคอมพิวเตอร์ที่บ้านของฉัน (2.61GHz AMD Phenom 9950) เร็วกว่าคอมพิวเตอร์ในที่ทำงานซึ่งเกือบ 31 วินาที @ 2.93GHz Core 2 Duo

php - 757 charsรวมอยู่ด้วย บรรทัดใหม่

<?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 ล้านปีในการแยกปัจจัย semiprime ที่กำหนด
CasaDeRobison

ฉันไม่ใช่คนเก่งคณิตศาสตร์ แต่สำหรับตัวเลขจำนวนมากวิธีการมาตรฐานของการแบ่งส่วนช่วงเวลาจะไม่ทำงาน (โดยใช้วงรี ฯลฯ ) เท่าที่ฉันรู้ เมื่อคำนึงถึงสิ่งนั้นอัลกอริทึมจะปรับปรุงได้อย่างไร
jdstankosky

2
ตะแกรงของ Eratosthenesเริ่มต้นด้วยรายการจำนวนแล้วลบคูณทุก 2 แล้ว 3 แล้ว 5 แล้ว 7 ฯลฯ สิ่งที่ยังคงอยู่หลังจากตะแกรงเสร็จสมบูรณ์เป็นเพียงตัวเลขที่สำคัญ ตะแกรงนี้สามารถ 'คำนวณล่วงหน้า' สำหรับปัจจัยจำนวนหนึ่ง เพราะlcm(2, 3, 5, 7) == 210รูปแบบของตัวเลขที่ถูกกำจัดโดยปัจจัยเหล่านี้จะทำซ้ำทุก ๆ 210 หมายเลขและมีเพียง 48 แห่งเท่านั้น ด้วยวิธีนี้คุณสามารถกำจัด 77% ของตัวเลขทั้งหมดออกจากแผนกทดลองแทนที่จะเป็น 50% โดยรับเพียงราคาต่อรอง
โม่

1
@primo จากความอยากรู้คุณทุ่มเทเวลานี้ไปเท่าไหร่แล้ว? คงต้องใช้เวลานานพอที่ฉันจะนึกถึงสิ่งนี้ ในขณะที่ฉันเขียนสิ่งนี้ฉันแค่คิดว่าตัวเลขที่สำคัญที่สุดนั้นคี่เสมอ ฉันไม่ได้พยายามไปไกลกว่านั้นและกำจัดโอกาสที่ไม่สำคัญเช่นกัน ดูเหมือนง่ายมากในการหวนกลับ
jdstankosky
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.