Fattorizzazione semiprime più veloce


28

Scrivi un programma per fattorizzare un numero semi-primo nel minor tempo possibile.

A scopo di test, utilizzare questo: 38! +1 (523022617466601111760007224100074291200000001)

È uguale a: 14029308060317546154181 × 37280713718589679646221


2
Mentre mi piace il bit "più veloce", dato che offre alle lingue come C il vantaggio rispetto ai linguaggi di codegolfing tipici, mi chiedo come testerai i risultati?
Lister,

1
Se intendi che 12259243verrà utilizzato per testare la velocità dei programmi, i risultati saranno così piccoli che non otterrai differenze statisticamente significative.
Peter Taylor,

Ho aggiunto un numero maggiore, grazie per l'heads up.
Soham Chowdhury,

@ Signor Lister, lo proverò sul mio PC.
Soham Chowdhury,

5
inb4 qualcuno usa l'abuso del preprocessore per scrivere una tabella di ricerca di 400 exabyte.
Wug

Risposte:


59

Python (con PyPy JIT v1.9) ~ 1.9s

Utilizzo di un setaccio quadratico polinomiale multiplo . Ho preso questo come una sfida del codice, quindi ho deciso di non utilizzare alcuna libreria esterna (a parte la logfunzione standard , suppongo). Durante il cronometraggio, dovrebbe essere usato PyPy JIT , poiché risulta in tempi 4-5 volte più veloci di quello di cPython .

Aggiornamento (29/07/2013):
dalla pubblicazione originale, ho apportato diverse modifiche minori, ma significative che aumentano la velocità complessiva di un fattore di circa 2,5 volte.

Aggiornamento (27-08-2014):
poiché questo post sta ancora ricevendo attenzione, ho aggiornato my_math.pycorreggendo due errori, per chiunque possa utilizzarlo:

  • isqrtera difettoso, a volte produceva output errato per valori molto vicini a un quadrato perfetto. Questo è stato corretto e le prestazioni sono aumentate usando un seme molto migliore.
  • is_primeè stato aggiornato. Il mio precedente tentativo di rimuovere i 2 sprps quadrati perfetti era insensibile, nella migliore delle ipotesi. Ho aggiunto un controllo a 3 sprp - una tecnica utilizzata da Mathmatica - per garantire che il valore testato sia privo di quadrati.

Aggiornamento (24-11-2014):
se alla fine del calcolo non vengono rilevate congruenze non banali, il programma ora setaccia polinomi aggiuntivi. Questo era precedentemente contrassegnato nel codice come 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 di esempio:

$ 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

Nota: il mancato utilizzo --verbosedell'opzione darà tempi leggermente migliori:

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

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

Concetti basilari

In generale, un setaccio quadratico si basa sulla seguente osservazione: qualsiasi composito dispari n può essere rappresentato come:

Non è molto difficile da confermare. Poiché n è dispari, la distanza tra due cofattori di n deve essere pari a 2d , dove x è il punto medio tra di loro. Inoltre, la stessa relazione vale per qualsiasi multiplo di n

Si noti che se si trova tale x e d , si otterrà immediatamente un fattore (non necessariamente primo) di n , poiché x + d e x - d dividono entrambi n per definizione. Questa relazione può essere ulteriormente indebolita - a seguito di consentire potenziali banali congruenze - alla seguente forma:

Quindi, in generale, se possiamo trovare due quadrati perfetti che sono equivalenti a mod n , allora è abbastanza probabile che possiamo produrre direttamente un fattore di n a la gcd (x ± d, n) . Sembra piuttosto semplice, vero?

Tranne che non lo è. Se intendessimo effettuare una ricerca esaustiva su tutte le possibili x , avremmo bisogno di cercare l'intero intervallo da [ n , √ ( 2n ) ], che è leggermente più piccolo della divisione di prova completa, ma richiede anche un'operazione costosa per is_squareogni iterazione confermare il valore di d . A meno che non si sappia in anticipo che n ha fattori molto vicini a n , è probabile che la divisione di prova sia più veloce.

Forse possiamo indebolire ulteriormente questa relazione. Supponiamo di aver scelto una x , tale che per

una fattorizzazione primaria completa di y è prontamente nota. Se avessimo avuto abbastanza relazioni del genere, dovremmo essere in grado di costruire una d adeguata , se scegliamo un numero di y tale che il loro prodotto sia un quadrato perfetto; cioè, tutti i fattori primi sono usati un numero pari di volte. In effetti, se disponiamo di un numero tale di y maggiore del numero totale di fattori primi unici che contengono, una soluzione è garantita; Diventa un sistema di equazioni lineari. La domanda ora diventa: come abbiamo scelto tale x ? È qui che entra in gioco il setaccio.

The Sieve

Considera il polinomio:

Quindi per ogni primo p e intero k , vale quanto segue:

Questo significa che dopo aver risolto le radici del mod polinomiale p - cioè, hai trovato una x tale che y (x) ≡ 0 (mod p) , ergo y è divisibile per p - quindi hai trovato un numero infinito di tale x . In questo modo, puoi setacciare un intervallo di x , identificando piccoli fattori primi di y , sperando di trovarne alcuni per i quali tutti i fattori primi sono piccoli. Numeri noti come k-smooth , dove k è il fattore primo più grande utilizzato.

Tuttavia, ci sono alcuni problemi con questo approccio. Non tutti i valori di x sono adeguati, infatti ce ne sono solo pochi, centrati attorno a n . Valori più piccoli diventeranno in gran parte negativi (a causa del termine -n ) e valori più grandi diventeranno troppo grandi, in modo tale che è improbabile che la loro fattorizzazione in numeri primi sia costituita solo da piccoli numeri primi. Ci sarà un certo numero di tali x , ma a meno che il composito che stai prendendo in considerazione sia molto piccolo, è altamente improbabile che troverai abbastanza smooth per provocare una fattorizzazione. E così, per n più grandi , diventa necessario setacciare più polinomi di una data forma.

Polinomi multipli

Quindi abbiamo bisogno di più polinomi per setacciare? Cosa ne pensi di questo:

Funzionerà. Nota che A e B potrebbero letteralmente essere qualsiasi valore intero e la matematica è ancora valida. Tutto quello che dobbiamo fare è scegliere alcuni valori casuali, risolvere la radice del polinomio e setacciare i valori vicini a zero. A questo punto potremmo chiamarlo abbastanza buono: se lanci abbastanza pietre in direzioni casuali, prima o poi sarai costretto a rompere una finestra.

Tranne, c'è un problema anche con quello. Se la pendenza del polinomio è grande all'intercetta x, che sarà se non è relativamente piatta, ci saranno solo alcuni valori adatti da setacciare per polinomio. Funzionerà, ma finirai per setacciare molti polinomi prima di ottenere ciò di cui hai bisogno. Possiamo fare di meglio?

Possiamo fare di meglio. Un'osservazione, a seguito di Montgomery è la seguente: se A e B sono scelti in modo tale che esista qualche C soddisfacente

quindi l'intero polinomio può essere riscritto come

Inoltre, se A viene scelto per essere un quadrato perfetto, il termine A iniziale può essere trascurato durante la setacciatura, risultando in valori molto più piccoli e una curva molto più piatta. Perché una tale soluzione esista, n deve essere un residuo quadratico modA , che può essere immediatamente riconosciuto calcolando il simbolo Legendre :
( n | √A ) = 1 . Si noti che per risolvere per B , è necessario conoscere una fattorizzazione primaria completa di √A (al fine di prendere la radice quadrata modulare √n (mod √A) ), motivo per cui √A viene in genere scelto come primo.

Si può quindi dimostrare che if , quindi per tutti i valori di x ∈ [ -M, M ] :

E ora, finalmente, abbiamo tutti i componenti necessari per implementare il nostro setaccio. O noi?

Poteri dei Primi come Fattori

Il nostro setaccio, come descritto sopra, ha un grosso difetto. Può identificare quali valori di x comporteranno una y divisibile per p , ma non può identificare se questa y sia divisibile per una potenza di p . Per determinarlo, dovremmo eseguire la divisione di prova sul valore da setacciare, fino a quando non sarà più divisibile per p . Sembrava che avessimo raggiunto un impassé: l'intero punto del setaccio era che non dovevamo farlo. È ora di controllare il playbook.

Sembra abbastanza utile. Se la somma di ln di tutti i piccoli fattori primi di y è vicina al valore atteso di ln (y) , allora è quasi scontato che y non abbia altri fattori. Inoltre, se modifichiamo un po 'il valore atteso, possiamo anche identificare valori come smooth che hanno diversi poteri di numeri primi come fattori. In questo modo, possiamo utilizzare il setaccio come processo di "pre-screening" e considerare solo quei valori che possono essere fluidi.

Questo ha anche alcuni altri vantaggi. Nota che i piccoli numeri primi contribuiscono ben poco alla ln somma, ma tuttavia richiedono il tempo più setaccio. La setacciatura del valore 3 richiede più tempo di 11, 13, 17, 19 e 23 combinati . Invece, possiamo semplicemente saltare i primi numeri primi e regolare la soglia di conseguenza, supponendo che una certa percentuale di questi sarebbe passata.

Un altro risultato è che un certo numero di valori potrà "scivolare", che sono per lo più fluidi, ma contengono un unico grande cofattore. Potremmo semplicemente scartare questi valori, ma supponiamo di aver trovato un altro valore per lo più regolare, con lo stesso cofattore. Possiamo quindi usare questi due valori per costruire una y utilizzabile ; poiché il loro prodotto conterrà questo grande cofattore al quadrato, non è più necessario considerarlo.

Mettere tutto insieme

L'ultima cosa che dobbiamo fare è usare questi valori di y per costruire una x e d adeguata . Supponiamo di considerare solo i fattori non quadrati di y , cioè i fattori primi di un potere dispari. Quindi, ogni y può essere espresso nel modo seguente:

che può essere espresso sotto forma di matrice:

Il problema diventa quindi trovare un vettore v tale che vM =(mod 2) , dove è il vettore null. Cioè, per risolvere per lo spazio nullo fianco di M . Questo può essere fatto in diversi modi, il più semplice dei quali è eseguire l'eliminazione gaussiana su M T , sostituendo l'operazione di aggiunta di riga con una riga xor . Ciò comporterà un numero di vettori con base spazio nullo, qualsiasi combinazione di questi produrrà una soluzione valida.

La costruzione di x è abbastanza semplice. È semplicemente il prodotto di Ax + B per ciascuna delle y utilizzate. La costruzione di d è leggermente più complicata. Se dovessimo prendere il prodotto di tutti y , finiremmo con un valore con 10s di migliaia, se non 100s di migliaia di cifre, per cui dobbiamo trovare la radice quadrata. Questo calcolo è poco costoso. Invece, possiamo tenere traccia dei poteri uniformi dei numeri primi durante il processo di setacciatura, e quindi utilizzare le operazioni and e xor sui vettori di fattori non quadrati per ricostruire la radice quadrata.

Mi sembra di aver raggiunto il limite di 30000 caratteri. Ah bene, suppongo che sia abbastanza buono.


5
Beh, non ho mai superato l'algebra al liceo (in realtà abbandonato durante il primo semestre dell'anno matricola), ma rendi semplice capire dal punto di vista di un programmatore. Non pretendo di capirlo appieno senza metterlo in pratica, ma ti applaudo. Dovresti considerare di espandere questo post fuori sede e pubblicarlo, sul serio!
jdstankosky,

2
Sono d'accordo. Ottima risposta con una grande spiegazione. +1
Soham Chowdhury,

1
@primo Le tue risposte a più domande qui sono state incredibilmente complete e interessanti. Molto apprezzato!
Paul Walls,

4
Come ultima osservazione, vorrei esprimere la mia gratitudine a Will Ness per aver assegnato il premio +100 a questa domanda. Era letteralmente tutta la sua reputazione.
primo

2
@StepHen lo fa. Sfortunatamente, utilizza la versione originale del 2012, senza i miglioramenti della velocità e con un bug nell'eliminazione gaussiana (errori quando la colonna finale è una colonna pivot). Ho provato a contattare l'autore qualche tempo fa, ma non ho ricevuto risposta.
primo

2

Bene, il tuo 38! +1 ha rotto il mio script php, non so perché. In effetti, qualsiasi semi-primo di oltre 16 cifre rompe a lungo la mia sceneggiatura.

Tuttavia, usando 8980935344490257 (86028157 * 104395301) il mio script ha gestito un tempo di 25.963 secondi sul mio computer di casa (2.61GHz AMD Phenom 9950). Molto più veloce del mio computer di lavoro, che era quasi 31 secondi a 2.93GHz Core 2 Duo.

php - 757 caratteri incl. nuove linee

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

Sarei interessato a vedere questo stesso algoritmo in c o in qualche altro linguaggio compilato.


I numeri di PHP hanno solo 53 bit di precisione, circa 16 cifre decimali
copia il

3
L'implementazione dello stesso algoritmo in C ++ usando numeri interi a 64 bit ha richiesto circa 1,8 secondi per essere eseguita sul mio computer. Esistono diversi problemi con questo approccio: 1. Non è in grado di gestire numeri abbastanza grandi. 2. Anche se potesse e supponendo che tutti i numeri, indipendentemente dalla lunghezza, usassero lo stesso tempo per la divisione di prova, ogni aumento di ordine di grandezza comporterebbe un aumento equivalente di tempo. Poiché il tuo primo fattore è inferiore di circa 14 ordini di grandezza rispetto al primo fattore dato, questo algoritmo impiegherebbe oltre 9 milioni di anni per fattorizzare il semiprime dato.
CasaDeRobison,

Certo, non sono il migliore in matematica, ma per numeri molto grandi, i metodi standard di factoring semi-primi semplicemente non funzioneranno (usando un'ellisse, ecc.), Per quanto ne so. Con questo in mente, come potrebbe essere migliorato l'algoritmo stesso?
jdstankosky,

2
Il setaccio di Eratostene inizia con un elenco di numeri, quindi rimuove tutti i multipli di 2, quindi 3, quindi 5, quindi 7, ecc. Ciò che rimane dopo che il setaccio è completo sono solo numeri primi. Questo setaccio può essere "pre-calcolato" per un certo numero di fattori. Perché lcm(2, 3, 5, 7) == 210, il modello di numeri eliminati da questi fattori si ripeterà ogni 210 numeri e ne rimarranno solo 48. In questo modo, puoi eliminare il 77% di tutti i numeri dalla divisione di prova, invece del 50% prendendo solo le probabilità.
primo

1
@primo Per curiosità, quanto tempo hai dedicato a questo? Mi ci sarebbero voluti anni per pensare a questa roba. Al momento in cui ho scritto questo, pensavo solo a come i numeri primi erano sempre strani. Non ho tentato di andare oltre ed eliminare anche le probabilità non prime. Sembra così semplice a posteriori.
jdstankosky,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.