/* Enumerate smooth values from a range of numbers (with Brent-Suyama extension) Copyright 2003 Alexander Kruppa. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02111-1307, USA. */ #include #include #include #include #include /* uncomment the following to use primegen, cf http://cr.yp.to/primegen/primegen-0.97.tar.gz */ /* #define PRIMEGEN */ #ifdef PRIMEGEN #include #else #include "ecm-impl.h" /* for getprime() */ #endif #define mulmod(r,u,v,n) mpz_mul(r,u,v);mpz_mod(r,r,n); #define DEBUG void dicksonmod(mpz_t, mpz_t, unsigned int, mpz_t, mpz_t); int is_P_minus_i (mpz_t, unsigned int); unsigned long eulerphi (unsigned long); unsigned int get_lenF (unsigned int); unsigned long gcd (unsigned long, unsigned long); void quicksort (mpz_t *, unsigned int); void quicksort_with_index (mpz_t *, unsigned int *, unsigned int); int issorted (mpz_t *, unsigned int); int getparm_ui (int, char **, int, char *, unsigned int *); int getparm_d (int, char **, int, char *, double *); int getparm_mpz (int, char **, int, char *, mpz_t *); int brent_suyama_match (mpz_t, unsigned int, unsigned int, unsigned int, unsigned int, mpz_t, mpz_t *, mpz_t *, unsigned int *, unsigned int *); double brent_suyama_theo (mpz_t, unsigned int, unsigned int, unsigned int, unsigned int, mpz_t); void print_help (void); /* Computes Dickson_{n, a}(x), the degree n Dickson polynomial with parameter a, evaluated at point x, and returns value modulo p in r */ void dicksonmod(mpz_t r, mpz_t x, unsigned int n, mpz_t a, mpz_t p) { unsigned int i, b = 0; mpz_t t, u; mpz_init(t); mpz_init(u); if (n == 0) { mpz_set_ui (r, 1); return; } if (n == 1) { mpz_set (r, x); return; } /* Now n >= 2 */ while (n > 2 && (n & 1) == 0) { b++; n >>= 1; } mpz_mul (t, x, x); /* r = x^2 */ mpz_sub (t, t, a); mpz_sub (t, t, a); /* r = x^2 - 2*a = Dickson_{2,a}(x) */ mpz_mod (r, t, p); mpz_set (t, x); /* t = Dickson_{1,a}(x) */ for (i = 2; i < n; i++) { mulmod (u, t, a, p); /* u = a * Dickson_{i-1,a}(x) */ mpz_set(t, r); /* t = Dickson_{i,a}(x) */ mpz_mul (r, r, x); /* r = x * Dickson_{i,a}(x) */ mpz_sub (r, r, u); /* r = x * Dickson_{i,a}(x) - a * Dickson_{i-1,a}(x) */ mpz_mod (r, r, p); /* = Dickson_{i+1,a}(x) */ } for ( ; b > 0; b--) { mulmod (t, r, r, p); /* t = (Dickson_{n,a}(x))^2 */ mpz_powm_ui (u, a, n, p); mpz_mul_2exp (u, u, 1); mpz_sub (r, t, u); /* r = (Dickson_{n,a}(x))^2 - 2 * a^n */ mpz_mod (r, r, p); /* = Dickson_{2*n,a}(x) */ } mpz_clear (u); mpz_clear (t); } /* Test if N+i is a probable prime */ int is_P_minus_i (mpz_t N, unsigned int i) { int r; mpz_add_ui (N, N, i); r = mpz_probab_prime_p (N, 2); mpz_sub_ui (N, N, i); return r; } /* Euler Phi(n) function, number of residues coprime to n */ unsigned long eulerphi (unsigned long n) { unsigned long i, r=1; for (i=2; i*i<=n; i++) if (n%i == 0) { r *= i-1; n /= i; while (n%i == 0) { r *= i; n /= i; } } if (n>1) r *= n-1; return r; } unsigned int get_lenF (unsigned int D) { return (eulerphi (D) / 2); } unsigned long gcd (unsigned long a, unsigned long b) { unsigned long t; while (b != 0) { t = a % b; a = b; b = t; } return a; } #define swap(a,b,t) {(t)=(a);(a)=(b);(b)=(t);} /* Sorts list of mpz_t values in a of length len. pivot is a temp var */ void quicksort_with_index (mpz_t *data, unsigned int *index, unsigned int len) { unsigned int i, j, t; if (len <= 1) return; if (len == 2) { if (mpz_cmp (data[0], data[1]) > 0) { mpz_swap (data[0], data[1]); swap(index[0],index[1],t); } return; } i = 0; j = len; while (1) { /* Top half gets everything greater than pivot */ while (mpz_cmp(data[++i], data[0]) <= 0 && i < j); while (mpz_cmp(data[--j], data[0]) > 0 && i < j); if (i >= j) break; mpz_swap (data[i], data[j]); swap(index[i],index[j],t); } mpz_swap (data[0], data[i-1]); swap(index[0],index[i-1],t); quicksort_with_index (data, index, i-1); quicksort_with_index (data+i, index+i, len-i); } #ifdef NO_INDEX /* Sorts list of mpz_t values in a of length len. pivot is a temp var */ void quicksort (mpz_t *data, unsigned int len) { unsigned int i, j; if (len <= 1) return; if (len == 2) { if (mpz_cmp (data[0], data[1]) > 0) mpz_swap (data[0], data[1]); return; } i = 0; j = len; while (1) { /* Top half gets everything greater than pivot */ while (mpz_cmp(data[++i], data[0]) <= 0 && i < j); while (mpz_cmp(data[--j], data[0]) > 0 && i < j); if (i >= j) break; mpz_swap (data[i], data[j]); } mpz_swap (data[0], data[i-1]); quicksort (data, i-1); quicksort (data+i, len-i); } #endif int issorted (mpz_t *a, unsigned int len) { unsigned int i; for (i = 0; i < len - 1; i++) { if (mpz_cmp (a[i], a[i+1]) > 0) return 0; } return 1; } int getparm_ui (int argc, char **argv, int i, char *parm, unsigned int *res) { if (strcmp(argv[i], parm) == 0) { i++; if (i >= argc) { printf("%s needs parameter\n", parm); exit(EXIT_FAILURE); } *res = atoi (argv[i]); return 1; } return 0; } int getparm_d (int argc, char **argv, int i, char *parm, double *res) { if (strcmp(argv[i], parm) == 0) { i++; if (i >= argc) { printf("%s needs parameter\n", parm); exit(EXIT_FAILURE); } *res = atof (argv[i]); return 1; } return 0; } int getparm_mpz (int argc, char **argv, int i, char *parm, mpz_t *res) { if (strcmp(argv[i], parm) == 0) { i++; if (i >= argc) { printf("%s needs parameter\n", parm); exit(EXIT_FAILURE); } if (index (argv[i], 'e')) { /* Hmm, looks like scientific notation */ double t; t = atof (argv[i]); mpz_set_d (*res, t); mpz_add_ui (*res, *res, 1); /* Avoid getting a value with lots of 2 factors because of mantissa truncation */ } else mpz_set_str (*res, argv[i], 10); return 1; } return 0; } /* Searches for matches in the lists F={f_S(i) % modulus : 0 0) j++; if (j < lenG && mpz_cmp (F[i], G[j]) == 0 && (startG > 0 || i+j > 0)) /* Eliminate spurious factors in Dickson */ { unsigned int tj = j; if (nrfactors == 0 && luckyF != NULL && luckyG != NULL) { *luckyF = indexF[i]; *luckyG = indexG[j]; } while (tj < lenG && mpz_cmp (F[i], G[tj]) == 0) { nrfactors++; tj++; } } } if (F_param == NULL) { for (i = 0; i < lenF; i++) mpz_clear (F[i]); free (F); } free (indexF); if (G_param == NULL) { for (i = 0; i < lenG; i++) mpz_clear (G[i]); free (G); } free (indexG); mpz_clear (t); return nrfactors; } /* Compute probability that value "modulus" is found by Brent-Suyama extension */ double brent_suyama_theo (mpz_t modulus, unsigned int S, unsigned int D, unsigned int startG, unsigned int endG, mpz_t a) { unsigned int mod_S, gcd_S; double nr_points, p; if (endG < startG || D <= 1 || S == 0 || S & 1) return 0.; /* See if the linear factors catch it */ if (mpz_cmp_d (modulus, (double)D * (double)(startG)) > 0 && mpz_cmp_d (modulus, (double)D * (double)(endG)) < 0) return 1.0; mod_S = mpz_fdiv_ui (modulus, S); if (mpz_sgn (a) == 0) gcd_S = gcd (mod_S - 1, S) - 2; else gcd_S = (gcd (mod_S - 1, S) + gcd (mod_S + 1, S)) / 2 - 2; nr_points = (double) get_lenF (D) * (double) (endG - startG + 1); p = mpz_get_d (modulus); return 1.0 - pow(1.0 - (double)gcd_S / p, nr_points); } void print_help () { printf ("countsmooth [-N ] [-tests ] [-blocks ] [-B1 ]\n" " [-B2 ] [-S ] [-D ] [-startG ] [-endG ] [-a ]\n" " [-maxBS ] [-v] [-theo] [-pm1] [-ecm]\n\n\n"); printf (" Determines which values in [N, N+nrtests-1] are smooth according to given\n" " parameters.\n"); printf (" A value is smooth if its factorization contains only primes and prime\n" " powers <= B1 (B1-smooth), or if one prime factor is >B1 but <=B2 and all\n" " others are B1-smooth, or if the largest prime factor can be expressed as\n" " f_{S,a}(k*D) - f_{S,a}(i), where startG<=k<=endG and 0 Start of range of numbers to invesigate.\n"); printf (" Length of range of numbers to investigate.\n"); printf (" Split range into this many blocks to conserve memory.\n" " By default = 1\n"); printf (" B1-smoothness limit.\n"); printf (" B2-smoothness limit.\n"); printf (" Degree of Brent-Suyama polynomial in stage 2.\n"); printf (" Stride for roots of G in stage 2.\n"); printf (" Starting value for roots of G is * .\n" " Default is floor( / )\n"); printf (" Ending value for roots of G is * .\n" " Default is floor( / ) + 1\n"); printf (" Parameter for Dickson polynomial. If == 0, -th powers\n" " are used. = 0 is the default.\n"); printf (" Brent-Suyama extension is only tried on cofactors <= \n" " Default is * 1000\n"); printf (" -v Verbose. Print info on each value found smooth.\n"); printf (" -theo Don't really compute and match Brent-Suyama values, just\n" " calculate the probability of success and add up expected values.\n"); printf (" -pm1 Investigate only those values in [N, N+nrtest-1] that are\n" " a prime minus 1\n"); printf( " -ecm Adjust for the ECM algorithm (divides N by 12, doubles S)\n"); } int main(int argc, char **argv) { mpz_t N, a, *cofac; unsigned int len_cofac; unsigned int p, B1=0, i, D, S, startG , endG, Nmod12, blocklen; unsigned int nr_tests = 0, nr_blocks = 1, blockstart = 0, nr_primes = 0; #ifdef PRIMEGEN primegen pg[1]; #endif double ppow, B2=0., maxBS = 0., /* Try Brent-Suyama only on cofactors <= maxBS */ nrBSsmooth = 0.; /* Nr (or expected value) of Brent-Suyama successes */ unsigned int nrB1smooth = 0, nrB2smooth = 0; int verbose = 0, theo = 0, ecm_style = 0, pminus1 = 0; mpz_t *F, *G; mpz_init (N); mpz_init (a); D = endG = S = 0; startG = 0xffffffff; B1 = 1000000; B2 = 100000000; nr_tests = 10000; mpz_set_str (N, "8333333333333333333333333331", 10); /* Get parameters */ for (i = 1; i < (unsigned int) argc; i++) { if (strcmp (argv[i], "-v") == 0) verbose = 1; else if (strcmp (argv[i], "-theo") == 0) theo = 1; else if (strcmp (argv[i], "-ecm") == 0) ecm_style = 1; else if (strcmp (argv[i], "-pm1") == 0) pminus1 = 1; else if (getparm_ui (argc, argv, i, "-tests", &nr_tests)) i++; else if (getparm_ui (argc, argv, i, "-blocks", &nr_blocks)) i++; else if (getparm_ui (argc, argv, i, "-B1", &B1)) i++; else if (getparm_d (argc, argv, i, "-B2", &B2)) i++; else if (getparm_ui (argc, argv, i, "-D", &D)) i++; else if (getparm_ui (argc, argv, i, "-S", &S)) i++; else if (getparm_ui (argc, argv, i, "-startG", &startG)) i++; else if (getparm_ui (argc, argv, i, "-endG", &endG)) i++; else if (getparm_mpz (argc, argv, i, "-N", &N)) i++; else if (getparm_mpz (argc, argv, i, "-a", &a)) i++; else if (getparm_d (argc, argv, i, "-maxBS", &maxBS)) i++; else { printf ("Don't know parameter %s\n", argv[i]); print_help(); exit (EXIT_FAILURE); } } if (maxBS == 0.) maxBS = B2 * 1000.; if (startG == 0xffffffff && D > 1) startG = B1 / D; if (endG == 0 && D > 1) { if (B2 > (double)(~1U)*(double)D) { fprintf (stderr, "Overflow error computing default endG. Please set explicitly\n"); exit (EXIT_FAILURE); } endG = (unsigned int) (B2 / (double) D); } if (ecm_style) { /* ECM is known to have a group order divisible by 12, which effectively makes the part that has yet to be smooth N/12. Instead of generating multiples of 12 and sieving those, we simply divide N by 12 here. (This leads to rounding down if N was not a multiple of 12, which we shouls correct when printing.) ECM finds a factor if p|f_S(x)-f_S(y) or p|f_S(x)+f_S(y), (because a point and it's negative have the same x-coordinate) where p is the missing group order factor, and f_S(x) and f_S(y) are the polynomial values examined by the Brent-Suyma extension in stage 2. If p is prime, this is equivalent to the condition p|(f_S(x)-f_S(y))(f_S(x)+f_S(y)), and for both f_S(x)=x^S and f_S(x) = S-th Dickson polynomials, (f_S(x)-f_S(y))(f_S(x)+f_S(y)) = f_{2S}(x)-f_{2S}(y). So effectively, ECM behaves as if S were twice as large, which we account for by simply doubling S here. */ Nmod12 = mpz_fdiv_q_ui (N, N, 12); S = S * 2; } if (verbose) { gmp_printf ("B1=%u, B2=%.0f, %s%u, D=%u, %u<=G<=%u\nN=%Zd\n", B1, B2, mpz_sgn(a) ? "Dickson_" : "X^", S, D, startG, endG, N); fflush (stdout); } len_cofac = (nr_tests + nr_blocks - 1) / nr_blocks; cofac = (mpz_t *) malloc (len_cofac * sizeof (mpz_t)); for (i = 0; i < len_cofac; i++) mpz_init (cofac[i]); F = (mpz_t *) malloc (get_lenF (D) * sizeof (mpz_t)); for (i = 0; i < get_lenF (D); i++) mpz_init (F[i]); G = (mpz_t *) malloc ((endG - startG + 1) * sizeof (mpz_t)); for (i = 0; i < (endG - startG + 1); i++) mpz_init (G[i]); while (nr_tests > 0) { if (nr_tests < len_cofac) blocklen = nr_tests; else blocklen = len_cofac; /* Init cofac */ for (i = 0; i < blocklen; i++) mpz_add_ui (cofac[i], N, i); /* Do the sieving */ #ifdef PRIMEGEN primegen_init (pg); for (p = primegen_next (pg); p <= B1; p = primegen_next (pg)) #else for (p = 2; p <= B1; p = getprime ()) #endif { /* Compute first sieve location where p divides */ i = mpz_fdiv_ui (N, p); if (i > 0) i = p - i; while (i < blocklen) { if (mpz_fdiv_q_ui (cofac[i], cofac[i], p) != 0) { fprintf (stderr, "%d does not divide cofac[%d]\n", p, i); exit (EXIT_FAILURE); } /* Take out prime powers. Not very efficient. */ ppow = (double)p * (double)p; while (ppow <= B1 && mpz_fdiv_ui (cofac[i], p) == 0) { mpz_fdiv_q_ui (cofac[i], cofac[i], p); ppow *= (double) p; } i += p; } } /* Check which cofactors are small enough and report */ for (i = 0; i < blocklen; i++) { if (pminus1) /* Report only primes - 1 if pminus1 is set */ { /* Test if N+i+1 is prime. If it is, obviously no factors can have been divided out of it, and the cofactor must still be larger than N. */ if ((i + 1 == blocklen || mpz_cmp (cofac[i + 1], N) > 0) && is_P_minus_i (N, i+1)) nr_primes++; else continue; } if (mpz_cmp_ui (cofac[i], 1) == 0) { if (verbose) printf ("N%s+%d: B1-smooth\n", ecm_style ? "/12" : "", i+blockstart); nrB1smooth++; continue; } if (mpz_cmp_d (cofac[i], B2) <= 0) { if (verbose) { printf ("N%s+%d: ", ecm_style ? "/12" : "", i + blockstart); mpz_out_str (stdout, 10, cofac[i]); printf ("\n"); } nrB2smooth++; continue; } if (mpz_cmp_d (cofac[i], maxBS) <= 0) { if (theo) { double pr; pr = brent_suyama_theo (cofac[i], S, D, startG, endG, a); nrBSsmooth += pr; if (verbose && pr >= 0.3) { printf ("N%s+%d: ", ecm_style ? "/12" : "", i + blockstart); mpz_out_str (stdout, 10, cofac[i]); printf (" (Brent-Suyama, pr=%f)\n", pr); } } else { unsigned int luckyF, luckyG; if (brent_suyama_match (cofac[i], S, D, startG, endG, a, F, G, &luckyF, &luckyG)) { unsigned int j; for (j = 2; j < S; j += 2) if (S % j == 0 && brent_suyama_match (cofac[i], j, D, startG, endG, a, F, G, &luckyF, &luckyG)) break; if (verbose) { printf ("N%s+%d: ", ecm_style ? "/12" : "", i+blockstart); mpz_out_str (stdout, 10, cofac[i]); printf (" (Brent-Suyama, divides "); if (mpz_sgn(a)) printf( "Dickson_%u(D*%u)-Dickson_%u(%u))\n", j, luckyG, j, luckyF); else printf( "(D*%u)^%u-%u^%u)\n", luckyG, j, luckyF, j); } nrBSsmooth++; continue; } } } } nr_tests -= blocklen; blockstart += blocklen; mpz_add_ui (N, N, blocklen); #ifndef PRIMEGEN getprime_clear (); #endif } for (i = 0; i < get_lenF (D); i++) mpz_clear (F[i]); free (F); for (i = 0; i < (endG - startG + 1); i++) mpz_clear (G[i]); free (G); printf ("B1-smooth: %d, B2-smooth: %d, found by Brent-Suyama: %f, Total: %d\n", nrB1smooth, nrB2smooth, nrBSsmooth ,nrB1smooth + nrB2smooth + (unsigned int) nrBSsmooth); if (pminus1) printf ("Number of N that are a prime - 1: %d\n", nr_primes); return 0; }