Factorización semiprime más rápida

28

Escriba un programa para factorizar un número semi-primo en el menor tiempo posible.

Para fines de prueba, use esto: 38! +1 (523022617466601111760007224100074291200000001)

Es igual a: 14029308060317546154181 × 37280713718589679646221

Soham Chowdhury
fuente
2
Si bien me gusta el bit "más rápido", dado que le da a los lenguajes como C la ventaja sobre los lenguajes de codegolf típicos, me pregunto cómo probará los resultados.
Sr. Lister el
1
Si quiere decir que 12259243se usará para probar qué tan rápidos son los programas, los resultados serán tan pequeños que no obtendrá diferencias estadísticamente significativas.
Peter Taylor
Agregué un número mayor, gracias por el aviso.
Soham Chowdhury
@ Señor Lister, lo probaré en mi propia PC.
Soham Chowdhury
55
inb4 alguien usa el abuso del preprocesador para escribir una tabla de búsqueda de 400 exabytes.
Wug

Respuestas:

59

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

Uso de un tamiz cuadrático polinómico múltiple . Tomé esto como un desafío de código, así que opté por no usar ninguna biblioteca externa (aparte de la logfunción estándar , supongo). Al cronometrar, se debe usar PyPy JIT , ya que da como resultado tiempos 4-5 veces más rápidos que los de cPython .

Actualización (2013-07-29):
desde la publicación original, he realizado varios cambios menores, pero significativos, que aumentan la velocidad general en un factor de aproximadamente 2.5x.

Actualización (2014-08-27):
como esta publicación aún recibe atención, he actualizado la my_math.pycorrección de dos errores, para cualquier persona que pueda estar usándola:

  • isqrtfue defectuoso, a veces produciendo resultados incorrectos para valores muy cercanos a un cuadrado perfecto. Esto se ha corregido y el rendimiento aumentó mediante el uso de una semilla mucho mejor.
  • is_primeHa sido actualizado. Mi intento anterior de eliminar 2 sprps cuadrados perfectos fue poco entusiasta, en el mejor de los casos. Agregué una verificación de 3 sprp, una técnica utilizada por Mathmatica, para garantizar que el valor probado no tenga cuadrados.

Actualización (2014-11-24):
si al final del cálculo no se encuentran congruencias no triviales, el programa ahora tamiza polinomios adicionales. Esto se marcó previamente en el código como 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

Muestra de E / S:

$ 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: no usar la --verboseopción dará tiempos ligeramente mejores:

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

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

Conceptos básicos

En general, un tamiz cuadrático se basa en la siguiente observación: cualquier compuesto compuesto n puede representarse como:

Esto no es muy difícil de confirmar. Como n es impar, la distancia entre dos cofactores de n debe ser par 2d , donde x es el punto medio entre ellos. Además, la misma relación se mantiene para cualquier múltiplo de n

Tenga en cuenta que si cualquiera de tales x y D se pueden encontrar, resultará inmediatamente en un factor de (no necesariamente primo) de n , ya que x + d y x - delta tanto brecha n por definición. Esta relación puede debilitarse aún más, como consecuencia de permitir posibles congruencias triviales, a la siguiente forma:

Entonces, en general, si podemos encontrar dos cuadrados perfectos que sean mod n equivalentes , entonces es bastante probable que podamos producir directamente un factor de n a la gcd (x ± d, n) . Parece bastante simple, ¿verdad?

Excepto que no lo es. Si pretendemos realizar una búsqueda exhaustiva de todas las posibles x , tendríamos que buscar en todo el rango desde [ n , √ ( 2n ) ], que es marginalmente más pequeño que la división de prueba completa, pero también requiere una is_squareoperación costosa cada iteración para confirmar el valor de d . A menos que se sabe de antemano que n tiene factores muy cerca n , sala de primera instancia es probable que sea más rápido.

Quizás podamos debilitar aún más esta relación. Supongamos que elegimos una x , tal que para

Se conoce fácilmente una factorización prima completa de y . Si tuviéramos suficientes relaciones, deberíamos ser capaces de construir una d adecuada , si elegimos un número de y tal que su producto sea un cuadrado perfecto; es decir, todos los factores primos se usan un número par de veces. De hecho, si tenemos más y más que el número total de factores primos únicos que contienen, se garantiza que existe una solución; Se convierte en un sistema de ecuaciones lineales. La pregunta ahora es, ¿cómo elegimos tal x ? Ahí es donde entra en juego el tamizado.

El tamiz

Considere el polinomio:

Entonces, para cualquier primo p y entero k , lo siguiente es cierto:

Esto significa que después de resolver las raíces del polinomio mod p , es decir, has encontrado una x tal que y (x) ≡ 0 (mod p) , ergo y es divisible por p , entonces has encontrado un número infinito de tal x . De esta manera, puede tamizar sobre un rango de x , identificando pequeños factores primos de y , con suerte encontrando algunos para los cuales todos los factores primos son pequeños. Tales números conocidos como k-smooth , donde k es el factor primo más grande utilizado.

Sin embargo, hay algunos problemas con este enfoque. No todos los valores de x son adecuados, de hecho, solo hay muy pocos de ellos, centrados alrededor de n . Los valores más pequeños se volverán en gran medida negativos (debido al término -n ), y los valores más grandes se volverán demasiado grandes, de modo que es poco probable que su factorización prima consista solo en números primos pequeños. Habrá una cantidad de tales x , pero a menos que el compuesto que está factorizando sea muy pequeño, es muy poco probable que encuentre suficientes suavidades para resultar en una factorización. Y así, para n más grande , se hace necesario tamizar sobre múltiples polinomios de una forma dada.

Polinomios múltiples

¿Entonces necesitamos más polinomios para tamizar? Qué tal esto:

Eso funcionará Tenga en cuenta que A y B podrían ser literalmente cualquier valor entero, y las matemáticas aún se mantienen. Todo lo que necesitamos hacer es elegir algunos valores aleatorios, resolver la raíz del polinomio y tamizar los valores cercanos a cero. En este punto, podríamos llamarlo lo suficientemente bueno: si arrojas suficientes piedras en direcciones aleatorias, es probable que rompas una ventana tarde o temprano.

Excepto, hay un problema con eso también. Si la pendiente del polinomio es grande en la intersección x, que será si no es relativamente plana, solo habrá unos pocos valores adecuados para tamizar por polinomio. Funcionará, pero terminarás tamizando un montón de polinomios antes de obtener lo que necesitas. ¿Podemos hacerlo mejor?

Podemos hacerlo mejor. Una observación, como resultado de Montgomery, es la siguiente: si A y B se eligen de manera tal que exista algo de C que satisfaga

entonces todo el polinomio se puede reescribir como

Además, si A es elegido para ser un cuadrado perfecto, el principal Un término puede despreciarse mientras tamizado, lo que resulta en valores mucho más pequeños, y una curva más plana mucho. Para que exista una solución de este tipo, n debe ser un mod de residuo cuadrático A , que se puede conocer inmediatamente calculando el símbolo de Legendre : ( n | √A ) = 1 . Tenga en cuenta que para resolver B , es necesario conocer una factorización prima completa de √A (para tomar la raíz cuadrada modular √n (mod √A) ), por lo que normalmente se elige √A como primo.

Entonces se puede mostrar que si , entonces para todos los valores de x ∈ [ -M, M ] :

Y ahora, finalmente, tenemos todos los componentes necesarios para implementar nuestro tamiz. O nosotros?

Poderes de los primes como factores

Nuestro tamiz, como se describió anteriormente, tiene un defecto importante. Puede identificar qué valores de x resultarán en una y divisible por p , pero no puede identificar si esta y es divisible por una potencia de p . Para determinar eso, tendríamos que realizar una división de prueba en el valor a tamizar, hasta que ya no sea divisible por p . Parecíamos haber llegado a un punto muerto: el objetivo del tamiz era que no teníamos que hacer eso. Hora de revisar el libro de jugadas.

Eso se ve muy útil. Si la suma de ln de todos los factores primos pequeños de y está cerca del valor esperado de ln (y) , entonces es casi un hecho que y no tiene otros factores. Además, si ajustamos un poco el valor esperado, también podemos identificar valores tan suaves que tienen varios poderes de primos como factores. De esta manera, podemos usar el tamiz como un proceso de 'preselección' y solo factorizar aquellos valores que probablemente sean suaves.

Esto tiene algunas otras ventajas también. Tenga en cuenta que los primos pequeños contribuyen muy poco a la suma de ln , pero requieren el mayor tiempo de tamizado. Tamizar el valor 3 requiere más tiempo que 11, 13, 17, 19 y 23 combinados . En cambio, podemos omitir los primeros números primos y ajustar el umbral en consecuencia, asumiendo que un cierto porcentaje de ellos hubiera pasado.

Otro resultado, es que se permitirá que una serie de valores 'se deslicen', que en su mayoría son suaves, pero contienen un solo cofactor grande. Podríamos descartar estos valores, pero supongamos que encontramos otro valor mayormente uniforme, con exactamente el mismo cofactor. Entonces podemos usar estos dos valores para construir una y utilizable ; Como su producto contendrá este cofactor grande al cuadrado, ya no necesita ser considerado.

Poniendolo todo junto

Lo último que debemos hacer es usar estos valores de y construir una x y d adecuadas . Supongamos que solo consideramos los factores no cuadrados de y , es decir, los factores primos de una potencia impar. Entonces, cada y se puede expresar de la siguiente manera:

que se puede expresar en forma de matriz:

El problema es encontrar un vector v tal que vM =(mod 2) , donde es el vector nulo. Es decir, para despejar el espacio nulo izquierda de M . Esto puede hacerse en un número de maneras, el más simple de los cuales es para llevar a cabo Gaussian Eliminación en M T , en sustitución de la operación de adición fila con una fila xor . Esto dará como resultado una serie de vectores de base de espacio nulo, cualquier combinación de los cuales producirá una solución válida.

La construcción de x es bastante sencilla. Es simplemente el producto de Ax + B para cada uno de los y utilizados. La construcción de d es un poco más complicada. Si tomáramos el producto de todo y , terminaríamos con un valor con 10s de miles, si no 100s de miles de dígitos, para lo cual necesitamos encontrar la raíz cuadrada. Este cálculo es poco costoso. En cambio, podemos hacer un seguimiento de los poderes pares de los números primos durante el proceso de tamizado, y luego usar las operaciones y y xor en los vectores de factores no cuadrados para reconstruir la raíz cuadrada.

Parece que he alcanzado el límite de 30000 caracteres. Ahh bien, supongo que es lo suficientemente bueno.

primo
fuente
55
Bueno, nunca pasé el álgebra en la escuela secundaria (en realidad abandoné durante el primer semestre del primer año), pero haces que sea fácil de entender desde la perspectiva de un programador. No pretendo entenderlo completamente sin ponerlo en práctica, pero lo aplaudo. ¡Debería considerar expandir esta publicación fuera del sitio y publicarla, en serio!
jdstankosky
2
Estoy de acuerdo. Excelente respuesta con una gran explicación. +1
Soham Chowdhury
1
@primo Sus respuestas a múltiples preguntas aquí han sido increíblemente completas e interesantes. ¡Muy apreciado!
Paul Walls
44
Como último comentario, me gustaría expresar mi gratitud a Will Ness por otorgar la recompensa de +100 por esta pregunta. Era literalmente toda su reputación.
primo
2
@StepHen lo hace. Desafortunadamente, usa la versión original de 2012, sin las mejoras de velocidad, y con un error en la eliminación gaussiana (errores cuando la columna final es una columna pivote). Intenté contactar al autor hace algún tiempo, pero no recibí respuesta.
primo
2

Bueno, tu 38! +1 rompió mi script php, no estoy seguro de por qué. De hecho, cualquier semi-prime de más de 16 dígitos rompe mi script.

Sin embargo, usando 8980935344490257 (86028157 * 104395301) mi script logró un tiempo de 25.963 segundos en la computadora de mi casa (2.61GHz AMD Phenom 9950). Mucho más rápido que mi computadora de trabajo, que fue casi 31 segundos a 2.93GHz Core 2 Duo.

php - 757 caracteres incl. nuevas lineas

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

Me interesaría ver este mismo algoritmo en algún otro lenguaje compilado.

jdstankosky
fuente
Los números de PHP tienen solo 53 bits de precisión, aproximadamente 16 dígitos decimales
copie el
3
Implementar el mismo algoritmo en C ++ usando enteros de 64 bits solo tardó aproximadamente 1,8 segundos en ejecutarse en mi computadora. Sin embargo, hay varios problemas con este enfoque: 1. No puede manejar números lo suficientemente grandes. 2. Incluso si pudiera y suponiendo que todos los números, sin importar la longitud, usaran la misma cantidad de tiempo para la división de prueba, cada aumento de orden de magnitud daría como resultado una cantidad equivalente de aumento de tiempo. Como su primer factor es aproximadamente 14 órdenes de magnitud más pequeño que el primer factor dado, este algoritmo tomaría más de 9 millones de años para factorizar el semiprime dado.
CasaDeRobison el
No soy el mejor en matemáticas, lo admito, pero para números muy grandes, los métodos estándar de factorización de semi-primos simplemente no funcionarán (usando una elipse, etc.), que yo sepa. Con eso en mente, ¿cómo podría mejorarse el algoritmo?
jdstankosky
2
El Tamiz de Eratóstenes comienza con una lista de números, luego elimina todos los múltiplos de 2, y luego 3, y luego 5, y luego 7, etc. Lo que queda después de que el tamiz está completo son solo números primos. Este tamiz se puede 'precalcular' para un cierto número de factores. Porque lcm(2, 3, 5, 7) == 210, el patrón de números eliminados por estos factores se repetirá cada 210 números, y solo quedan 48. De esa manera, puede eliminar el 77% de todos los números de la división de prueba, en lugar del 50% tomando solo probabilidades.
primo
1
@primo Por curiosidad, ¿cuánto tiempo dedicaste a esto? Me hubiera llevado años pensar en estas cosas. Cuando escribí esto, solo estaba pensando en cómo los números primos siempre eran impares. No intenté ir más allá de eso y eliminar las probabilidades no principales también. Parece tan simple en retrospectiva.
jdstankosky