Logo Studenta

¿Alguien sabe de algun programa que me permita calcular números primos por ejemplo del 1 al billón?

💡 1 Respuesta

User badge image

Notas de Estudio

Pensaría que lo mejor es que tú mismo escribieras el programa. De paso podrías probar técnicas de codificación.

Lo primero, para probar si un número es primo, es intentar dividirlo por todos los números anteriores. Escribiré mi algoritmo en un pseudo C.

  1. bool es_primo(long n) 
  2. { 
  3. for(long i=2; i 
  4. { 
  5. if( n%i == 0 ) return false; 
  6. } 
  7. return true; 
  8. } 

Asumo aquí que long es suficientemente grande para almacenar números del orden de un billón (10121012), para lo cual requiero de 40 bits. Dependiendo el sistema, long puede ser 32 bits o 64 bits; en el primer caso tendría problemas y tengo que hacer el algoritmo más complejo. Si programo en Python no tengo que preocuparme por el tamaño.

Otro problema con este algoritmo, es que si quiero evaluar todos los primos desde n=2n=2 hasta n1012n≤1012, tendré que repetir el llamado a función casi un billón de veces, y dentro de cada llamado, estoy iterando hasta n3n−3 veces dentro de la función. La complejidad es cuadrática, aunque la mayor parte de los llamados a la función devuelve muy rápido (la mitad de las veces, tengo una respuesta cuando i=2i=2).

Una pequeña mejora, que reduce a la mitad las pruebas, es que si nn es impar, sólo pruebo con divisores impares. Si 22 no divide a nn, tampoco lo hará 44, ni 66, ni 88, ni 1010, etc. Así que primero pruebo si es par, y luego busco entre los impares:

  1. bool es_primo(long n) 
  2. { 
  3. if( n==2 ) return true; /* 2 siempre es primo */ 
  4. if( n%2 == 0 ) return false; /* si n es par, no es primo */ 
  5. for(long i=3; i 
  6. { 
  7. if( n%i == 0 ) return false; 
  8. } 
  9. return true; 
  10. } 

Aquí me ahorro varias pruebas innecesarias, pero la complejidad del algoritmo es aún alta.

La siguiente optimización es la siguiente. Si ii divide a nn, entonces existe un número pp, tal que i×p=ni×p=n. Pero i>ni>n, entonces p<np; ya que pnp≥n, entonces i×p>n×ni×p>n×n y i×p>ni×p>n, lo que contradice la igualdad. Así que si nn tiene un divisor propio no trivial, entonces nn tiene un divisor menor o igual a su raíz cuadrada.

Nuestro algoritmo optimizado va entonces así:

  1. bool es_primo(long n) 
  2. { 
  3. if( n==2 ) return true; /* 2 siempre es primo */ 
  4. if( n%2 == 0 ) return false; /* si n es par, no es primo */ 
  5. for(long i=3; i*i<=n; i+=2 ) /* pruebo impares hasta raiz(n) */ 
  6. { 
  7. if( n%i == 0 ) return false; 
  8. } 
  9. return true; 
  10. } 

La complejidad del algoritmo bajó de nn a nn para cada nn, o de n2n2 a n112n112 para buscar todos los números.

La siguiente optimización. Así como descartamos probar los pares diferentes a 2, podemos descartar los múltiplos impares de 3, y los múltiplos de 5, y de 7… En fin, podemos descartar todos los números no primos, y probar sólo con los números primos menores o iguales a nn.

  1. long primos[BUFFER_SIZE]; 
  2.  
  3. bool es_primo(long n) 
  4. { 
  5. for(int i=0; primos[i]*primos[i]<=n; i++) { 
  6. { 
  7. if( n%primos[i] == 0) return false; 
  8. } 
  9. return true; 
  10. } 

Para que esto funcione se supone que primos debe tener ya todos los posibles números primos hasta nn. Así que necesitamos llenar el vector, o conocer los primos. Por ejemplo si sólo quiero probar los números hasta el 100 para ver si son primos tendría que definir a primos como:

  1. long primos[5] = { 2, 3, 5, 7, 11 }; 

(Nota, con el 11 no se probará divisibilidad, pero es necesario como condición de cierre en el for.)

Pero necesito conocer entonces cuales son los números primos. Así que la siguiente mejora es buscar los números primos e irlos guardando.

  1. long primos[BUFFER_SIZE]; 
  2.  
  3. bool es_primo(long n) 
  4. { 
  5. for(int i=0; primos[i]*primos[i]<=n; i++) { 
  6. { 
  7. if( n%primos[i] == 0) return false; 
  8. } 
  9. return true; 
  10. } 
  11.  
  12. long buscar_primos(long max) 
  13. { 
  14. long i=0; 
  15. for(long n=2; n 
  16. { 
  17. if(es_primo(n)) 
  18. { 
  19. primos[i++] = n; 
  20. } 
  21. } 
  22. } 

Ahora, nuestro programa principal puede ser algo como esto:

  1. void main() 
  2. { 
  3. long m = buscar_primos(1000000000000l); 
  4. printf("Hay %ld primos antes del billón\n", m); 
  5. printf("Estos son:\n"); 
  6. for(long p=0; p 
  7. { 
  8. printf("%13ld\n", p); 
  9. } 
  10. } 

Sin embargo este programa tiene todavía un problema grande: memoria.

Lo primero es que necesito que el vector de primos sea lo suficientemente grande. En principio podría asegurar que si BUFFER_SIZE es un billón, no tendré problemas. Pero para ello necesito una memoria que almacene un billón de números. Dado que cada número long de 64 bits me ocupa 8 octetos, necesito casi 8 TB (terabyte) de memoria sólo para almacenar el arreglo. Y eso es irreal con la tecnología actual.

Pero no necesitamos un billón. Puedo lograr un estimado de cuantos números primos hay entre 1 y un billón, utilizando el teorema de densidad de los números primos, y encuentro que serán algo menos de 36.200 millones de números primos, y para almacenarlos me bastan 270 GB (gigabytes).

Esto es aún mucha memoria. Sin embargo, puedo optimizar mejor el algoritmo. La razón del vector es almacenar los primos menores o iguales a nn, donde nn es el valor que quiero saber si es primo o no. Si voy a buscar todos los primos menores o iguales a un billón, necesito almacenar los primos menores o iguales a un millón. Aquí puedo hacer dos cosas. Primero, ya no necesito almacenar un billón de números, o 36 mil millones, sino que son sólo un millón (tope), o algo menos de 72.400 primos. Adicionalmente necesito sólo 20 bits para almacenar al mayor de esos números primos, así que puedo utilizar enteros de 32 bits, que me ocupan sólo 4 octetos. La memoria que necesito, por lo tanto, es de 283 kB (kilobytes). Esto ya es razonable en un computador casero moderno.

Sin embargo, no puedo usar el truco de almacenar todos los primos encontrados e imprimirlos luego.

Así que el programa final sería el siguiente:

  1. #include  
  2. #include  
  3. #define BUFFER_SIZE 72400 
  4.  
  5. int32_t primos[BUFFER_SIZE]; 
  6. int max_idx=0; 
  7.  
  8. int es_primo(int64_t n) 
  9. { 
  10. int i; 
  11. int32_t raiz = (int32_t)sqrt((double)n); 
  12. for( i=0; i 
  13. { 
  14. if( n % (int64_t)primos[i] == 0 ) return FALSE; 
  15. } 
  16. if(max_idx < BUFFER_SIZE) 
  17. { 
  18. primos[max_idx] = n; 
  19. max_idx++; 
  20. } 
  21. return TRUE; 
  22. } 
  23.  
  24. void main() 
  25. { 
  26. int64_t n; 
  27. int c=0; 
  28. printf("Los primos antes del billón son:\n"); 
  29. for( n=2; n<1000000000000l; n++ ) 
  30. { 
  31. if( es_primo(n) ) 
  32. { 
  33. printf("%13ld\n", n); 
  34. c++; 
  35. } 
  36. } 
  37. printf("Para un total de %d números primos.\n", c); 
  38. } 

No garantizo que el programa funcione, ni que sea rápido. Igual este programa imprimirá en pantalla más de 36 mil millones de números, de los cuales más de 32 mil millones tienen 12 cifras. Si en lugar de pantalla, redireccionas todo a un archivo de texto, será un archivo de 472 GB de tamaño (asegúrate de tener medio tera de disco libre). Si redireccionas a una impresora que imprime 60 líneas por página, serán 600 millones de páginas. Si imprime 60 páginas por minuto, serán 7 mil días imprimiendo (20 años). (si imprimes a varias columnas, podrían ser 6 columnas y durar 3 años y medio imprimiendo.)

Usé C en el ejemplo para mostrar los problemas de memoria que se presentan en este tipo de programas. Otros lenguajes de programación esconden esos problemas (el programador no tiene que pensar en ellos a detalle) pero esos problemas están ahí.

Es posible hacer algunas optimizaciones adicionales, pero los problemas básicos están ahí.

P.D. (edición)
Probé el programa hasta 10 millones y funciona. Tarda 8 segundos en ejecutarse en mi PC si se redirecciona a un archivo (el cual mide 9 MB) y me arroja 664.579 primos entre 2 y 9.999.991 .

Hasta 100 millones, tarda 150 segundos, el archivo final mide 80 MB
y me arroja 5.761.455 primos entre 2 y 99.999.989.

0
Dislike0

✏️ Responder

FlechasNegritoItálicoSubrayadaTachadoCitaCódigoLista numeradaLista con viñetasSuscritoSobreDisminuir la sangríaAumentar la sangríaColor de fuenteColor de fondoAlineaciónLimpiarInsertar el linkImagenFórmula

Para escribir su respuesta aquí, Ingresar o Crear una cuenta

User badge image

Otros materiales