Skip to main content

num_prime/
factor.rs

1//! Implementations for various factorization algorithms.
2//!
3//! Note general prime number field sieve is not planned to be implemented, since it's too complex
4//!
5//! See <https://web.archive.org/web/20110331180514/https://diamond.boisestate.edu/~liljanab/BOISECRYPTFall09/Jacobsen.pdf>
6//! for a detailed comparison between different factorization algorithms
7
8// XXX: make the factorization method resumable? Maybe let all of them returns a Future
9
10use crate::traits::ExactRoots;
11use num_integer::{Integer, Roots};
12use num_modular::{ModularCoreOps, ModularUnaryOps};
13use num_traits::{CheckedAdd, CheckedMul, FromPrimitive, NumRef, RefNum};
14use std::collections::BTreeMap;
15
16/// Find factors by trial division, returns a tuple of the found factors and the residual.
17///
18/// The target is guaranteed fully factored only if bound * bound > target, where bound = max(primes).
19/// The parameter limit additionally sets the maximum of primes to be tried.
20/// The residual will be Ok(1) or Ok(p) if fully factored.
21///
22/// # Examples
23///
24/// ```
25/// use num_prime::factor::trial_division;
26///
27/// let primes = vec![2, 3, 5, 7, 11, 13];
28/// let (factors, residual) = trial_division(primes.into_iter(), 60u64, None);
29/// assert_eq!(factors[&2], 2);
30/// assert_eq!(factors[&3], 1);
31/// assert_eq!(factors[&5], 1);
32/// assert!(residual.is_ok());
33/// ```
34///
35/// TODO: implement fast check for small primes with `BigInts` in the precomputed table, and skip them in this function
36pub fn trial_division<
37    I: Iterator<Item = u64>,
38    T: Integer + Clone + Roots + NumRef + FromPrimitive,
39>(
40    primes: I,
41    target: T,
42    limit: Option<u64>,
43) -> (BTreeMap<u64, usize>, Result<T, T>)
44where
45    for<'r> &'r T: RefNum<T>,
46{
47    let tsqrt: T = Roots::sqrt(&target) + T::one();
48    let limit = if let Some(l) = limit {
49        tsqrt.clone().min(T::from_u64(l).unwrap())
50    } else {
51        tsqrt.clone()
52    };
53
54    let mut residual = target;
55    let mut result = BTreeMap::new();
56    let mut factored = false;
57    for (p, pt) in primes.map(|p| (p, T::from_u64(p).unwrap())) {
58        if pt > tsqrt {
59            factored = true;
60        }
61        if pt > limit {
62            break;
63        }
64
65        while residual.is_multiple_of(&pt) {
66            residual = residual / &pt;
67            *result.entry(p).or_insert(0) += 1;
68        }
69        if residual.is_one() {
70            factored = true;
71            break;
72        }
73    }
74
75    if factored {
76        (result, Ok(residual))
77    } else {
78        (result, Err(residual))
79    }
80}
81
82/// Find factors using Pollard's rho algorithm with Brent's loop detection algorithm
83///
84/// The returned values are the factor and the count of passed iterations.
85///
86/// # Examples
87///
88/// ```
89/// use num_prime::factor::pollard_rho;
90///
91/// let (factor, _iterations) = pollard_rho(&8051u16, 2, 1, 100);
92/// assert_eq!(factor, Some(97));  // 8051 = 83 × 97
93/// ```
94pub fn pollard_rho<
95    T: Integer
96        + FromPrimitive
97        + NumRef
98        + Clone
99        + for<'r> ModularCoreOps<&'r T, &'r T, Output = T>
100        + for<'r> ModularUnaryOps<&'r T, Output = T>,
101>(
102    target: &T,
103    start: T,
104    offset: T,
105    max_iter: usize,
106) -> (Option<T>, usize)
107where
108    for<'r> &'r T: RefNum<T>,
109{
110    let mut a = start.clone();
111    let mut b = start.clone();
112    let mut z = T::one() % target; // accumulator for gcd
113
114    // using Brent's loop detection, i = tortoise, j = hare
115    let (mut i, mut j) = (0usize, 1usize);
116
117    // backtracing states
118    let mut s = start;
119    let mut backtrace = false;
120
121    while i < max_iter {
122        i += 1;
123        a = a.sqm(target).addm(&offset, target);
124        if a == b {
125            return (None, i);
126        }
127
128        // FIXME: optimize abs_diff for montgomery form if we are going to use the abs_diff in the std lib
129        let diff = if b > a { &b - &a } else { &a - &b }; // abs_diff
130        z = z.mulm(&diff, target);
131        if z.is_zero() {
132            // the factor is missed by a combined GCD, do backtracing
133            if backtrace {
134                // ultimately failed
135                return (None, i);
136            } else {
137                backtrace = true;
138                a = std::mem::replace(&mut s, T::one()); // s is discarded
139                z = T::one() % target; // clear gcd
140                continue;
141            }
142        }
143
144        // here we check gcd every 2^k steps or 128 steps
145        // larger batch size leads to large overhead when backtracing.
146        // reference: https://www.cnblogs.com/812-xiao-wen/p/10544546.html
147        if i == j || i & 127 == 0 || backtrace {
148            let d = z.gcd(target);
149            if !d.is_one() && &d != target {
150                return (Some(d), i);
151            }
152
153            // save state
154            s = a.clone();
155        }
156
157        // when tortoise catches up with hare, let hare jump to the next stop
158        if i == j {
159            b = a.clone();
160            j <<= 1;
161        }
162    }
163
164    (None, i)
165}
166
167/// This function implements Shanks's square forms factorization (SQUFOF).
168///
169/// The input is usually multiplied by a multiplier, and the multiplied integer should be put in
170/// the `mul_target` argument. The multiplier can be choosen from `SQUFOF_MULTIPLIERS`, or other square-free odd numbers.
171/// The returned values are the factor and the count of passed iterations.
172///
173/// The max iteration can be choosed as 2*n^(1/4), based on Theorem 4.22 from \[1\].
174///
175/// # Examples
176///
177/// ```
178/// use num_prime::factor::squfof;
179///
180/// let (factor, _iterations) = squfof(&11111u32, 11111u32, 100);
181/// assert_eq!(factor, Some(41));  // 11111 = 41 × 271
182/// ```
183///
184/// Reference: Gower, J., & Wagstaff Jr, S. (2008). Square form factorization.
185/// In \[1\] [Mathematics of Computation](https://homes.cerias.purdue.edu/~ssw/gowerthesis804/wthe.pdf)
186/// or \[2\] [his thesis](https://homes.cerias.purdue.edu/~ssw/gowerthesis804/wthe.pdf)
187/// The code is from \[3\] [Rosetta code](https://rosettacode.org/wiki/Square_form_factorization)
188pub fn squfof<T: Integer + NumRef + Clone + ExactRoots + std::fmt::Debug>(
189    target: &T,
190    mul_target: T,
191    max_iter: usize,
192) -> (Option<T>, usize)
193where
194    for<'r> &'r T: RefNum<T>,
195{
196    assert!(
197        &mul_target.is_multiple_of(target),
198        "mul_target should be multiples of target"
199    );
200    let rd = Roots::sqrt(&mul_target); // root of k*N
201
202    /// Reduction operator for binary quadratic forms. It's equivalent to
203    /// the one used in the `num-irrational` crate, in a little different form.
204    ///
205    /// This function reduces (a, b, c) = (qm1, p, q), updates qm1 and q, returns new p.
206    #[inline]
207    fn rho<T: Integer + Clone + NumRef>(rd: &T, p: &T, q: &mut T, qm1: &mut T) -> T
208    where
209        for<'r> &'r T: RefNum<T>,
210    {
211        let b = (rd + p).div_floor(&*q);
212        let new_p = &b * &*q - p;
213        let new_q = if p > &new_p {
214            &*qm1 + b * (p - &new_p)
215        } else {
216            &*qm1 - b * (&new_p - p)
217        };
218
219        *qm1 = std::mem::replace(q, new_q);
220        new_p
221    }
222
223    // forward loop, search principal cycle
224    let (mut p, mut q, mut qm1) = (rd.clone(), &mul_target - &rd * &rd, T::one());
225    if q.is_zero() {
226        // shortcut for perfect square
227        return (Some(rd), 0);
228    }
229
230    for i in 1..max_iter {
231        p = rho(&rd, &p, &mut q, &mut qm1);
232        if i.is_odd() {
233            if let Some(rq) = q.sqrt_exact() {
234                let b = (&rd - &p) / &rq;
235                let mut u = b * &rq + &p;
236                let (mut v, mut vm1) = ((&mul_target - &u * &u) / &rq, rq);
237
238                // backward loop, search ambiguous cycle
239                loop {
240                    let new_u = rho(&rd, &u, &mut v, &mut vm1);
241                    if new_u == u {
242                        break;
243                    } else {
244                        u = new_u;
245                    }
246                }
247
248                let d = target.gcd(&u);
249                if d > T::one() && &d < target {
250                    return (Some(d), i);
251                }
252            }
253        }
254    }
255    (None, max_iter)
256}
257
258/// Good squfof multipliers sorted by efficiency descendingly, from Dana Jacobsen.
259///
260/// Note: square-free odd numbers are suitable as SQUFOF multipliers
261pub const SQUFOF_MULTIPLIERS: [u16; 38] = [
262    3 * 5 * 7 * 11,
263    3 * 5 * 7,
264    3 * 5 * 7 * 11 * 13,
265    3 * 5 * 7 * 13,
266    3 * 5 * 7 * 11 * 17,
267    3 * 5 * 11,
268    3 * 5 * 7 * 17,
269    3 * 5,
270    3 * 5 * 7 * 11 * 19,
271    3 * 5 * 11 * 13,
272    3 * 5 * 7 * 19,
273    3 * 5 * 7 * 13 * 17,
274    3 * 5 * 13,
275    3 * 7 * 11,
276    3 * 7,
277    5 * 7 * 11,
278    3 * 7 * 13,
279    5 * 7,
280    3 * 5 * 17,
281    5 * 7 * 13,
282    3 * 5 * 19,
283    3 * 11,
284    3 * 7 * 17,
285    3,
286    3 * 11 * 13,
287    5 * 11,
288    3 * 7 * 19,
289    3 * 13,
290    5,
291    5 * 11 * 13,
292    5 * 7 * 19,
293    5 * 13,
294    7 * 11,
295    7,
296    3 * 17,
297    7 * 13,
298    11,
299    1,
300];
301
302/// William Hart's one line factorization algorithm for 64 bit integers.
303///
304/// The number to be factored could be multiplied by a smooth number (coprime to the target)
305/// to speed up, put the multiplied number in the `mul_target` argument. A good multiplier given by Hart is 480.
306/// `iters` determine the range for iterating the inner multiplier itself. The returned values are the factor
307/// and the count of passed iterations.
308///
309///
310/// The one line factorization algorithm is especially good at factoring semiprimes with form pq,
311/// where p = `next_prime(c^a+d1`), p = `next_prime(c^b+d2`), a and b are close, and c, d1, d2 are small integers.
312///
313/// # Examples
314///
315/// ```
316/// use num_prime::factor::one_line;
317///
318/// let (factor, _iterations) = one_line(&11111u32, 11111u32, 100);
319/// assert_eq!(factor, Some(271));  // 11111 = 41 × 271
320/// ```
321///
322/// Reference: Hart, W. B. (2012). A one line factoring algorithm. Journal of the Australian Mathematical Society, 92(1), 61-69. doi:10.1017/S1446788712000146
323// TODO: add multipliers preset for one_line method?
324pub fn one_line<T: Integer + NumRef + FromPrimitive + ExactRoots + CheckedAdd + CheckedMul>(
325    target: &T,
326    mul_target: T,
327    max_iter: usize,
328) -> (Option<T>, usize)
329where
330    for<'r> &'r T: RefNum<T>,
331{
332    assert!(
333        &mul_target.is_multiple_of(target),
334        "mul_target should be multiples of target"
335    );
336
337    let mut ikn = mul_target.clone();
338    for i in 1..max_iter {
339        let s = ikn.sqrt() + T::one(); // assuming target is not perfect square
340
341        // Use checked multiplication to prevent overflow
342        let s_squared = if let Some(result) = s.checked_mul(&s) {
343            result
344        } else {
345            // If s*s would overflow, this method won't work for this range
346            return (None, i);
347        };
348        let m = s_squared - &ikn;
349        if let Some(t) = m.sqrt_exact() {
350            let g = target.gcd(&(s - t));
351            if !g.is_one() && &g != target {
352                return (Some(g), i);
353            }
354        }
355
356        // prevent overflow
357        ikn = if let Some(n) = ikn.checked_add(&mul_target) {
358            n
359        } else {
360            return (None, i);
361        }
362    }
363    (None, max_iter)
364}
365
366// TODO: ECM, (self initialize) Quadratic sieve, Lehman's Fermat(https://en.wikipedia.org/wiki/Fermat%27s_factorization_method, n_factor_lehman)
367// REF: https://pypi.org/project/primefac/
368//      http://flintlib.org/doc/ulong_extras.html#factorisation
369//      https://github.com/zademn/facto-rs/
370//      https://github.com/elmomoilanen/prime-factorization
371//      https://cseweb.ucsd.edu/~ethome/teaching/2022-cse-291-14/
372#[cfg(test)]
373mod tests {
374    use super::*;
375    use crate::mint::SmallMint;
376    use num_modular::MontgomeryInt;
377    use rand::random;
378
379    #[test]
380    fn pollard_rho_test() {
381        assert_eq!(pollard_rho(&8051u16, 2, 1, 100).0, Some(97));
382        assert!(matches!(pollard_rho(&8051u16, random(), 1, 100).0, Some(i) if i == 97 || i == 83));
383        assert_eq!(pollard_rho(&455_459_u32, 2, 1, 100).0, Some(743));
384
385        // Mint test
386        for _ in 0..10 {
387            let target = random::<u16>() | 1;
388            let start = random::<u16>() % target;
389            let offset = random::<u16>() % target;
390
391            let expect = pollard_rho(&target, start, offset, 65536);
392            let mint_result = pollard_rho(
393                &SmallMint::from(target),
394                MontgomeryInt::new(start, &target).into(),
395                MontgomeryInt::new(offset, &target).into(),
396                65536,
397            );
398            assert_eq!(expect.0, mint_result.0.map(|v| v.value()));
399        }
400    }
401
402    #[test]
403    fn squfof_test() {
404        // case from wikipedia
405        assert_eq!(squfof(&11111u32, 11111u32, 100).0, Some(41));
406
407        // cases from https://rosettacode.org/wiki/Square_form_factorization
408        let cases: Vec<u64> = vec![
409            2501,
410            12851,
411            13289,
412            75301,
413            120_787,
414            967_009,
415            997_417,
416            7_091_569,
417            5_214_317,
418            20_834_839,
419            23_515_517,
420            33_409_583,
421            44_524_219,
422            13_290_059,
423            223_553_581,
424            2_027_651_281,
425            11_111_111_111,
426            100_895_598_169,
427            60_012_462_237_239,
428            287_129_523_414_791,
429            9_007_199_254_740_931,
430            11_111_111_111_111_111,
431            314_159_265_358_979_323,
432            384_307_168_202_281_507,
433            419_244_183_493_398_773,
434            658_812_288_346_769_681,
435            922_337_203_685_477_563,
436            1_000_000_000_000_000_127,
437            1_152_921_505_680_588_799,
438            1_537_228_672_809_128_917,
439            // this case should success at step 276, from https://rosettacode.org/wiki/Talk:Square_form_factorization
440            4_558_849,
441        ];
442        for n in cases {
443            let d = squfof(&n, n, 40000)
444                .0
445                .or(squfof(&n, 3 * n, 40000).0)
446                .or(squfof(&n, 5 * n, 40000).0)
447                .or(squfof(&n, 7 * n, 40000).0)
448                .or(squfof(&n, 11 * n, 40000).0);
449            assert!(d.is_some(), "{}", n);
450        }
451    }
452
453    #[test]
454    fn one_line_test() {
455        assert_eq!(one_line(&11111u32, 11111u32, 100).0, Some(271));
456    }
457
458    // --- one_line with overflow via checked_add ---
459    #[test]
460    fn one_line_overflow() {
461        // Use a u64 target large enough that repeated addition overflows
462        let n = u64::MAX / 4 + 1; // ~4.6e18, adding 5 times overflows u64
463        let result = one_line(&n, n, 1000);
464        // Should return None (overflow triggered early exit via checked_add)
465        assert!(result.0.is_none());
466    }
467
468    #[test]
469    fn one_line_with_multiplier() {
470        // Use multiplier 480 as recommended
471        let n = 11111u32;
472        let result = one_line(&n, n * 480, 100);
473        assert!(result.0.is_some());
474        let f = result.0.unwrap();
475        assert!(n % f == 0 && f > 1 && f < n);
476    }
477
478    // --- trial_division with limit=None (no limit path) ---
479    #[test]
480    fn trial_division_no_limit() {
481        let primes: Vec<u64> = vec![2, 3, 5, 7, 11, 13];
482        let (factors, residual) = trial_division(primes.into_iter(), 2 * 3 * 5 * 7u64, None);
483        assert!(residual.is_ok());
484        assert_eq!(residual.unwrap(), 1);
485        assert_eq!(factors[&2], 1);
486        assert_eq!(factors[&3], 1);
487        assert_eq!(factors[&5], 1);
488        assert_eq!(factors[&7], 1);
489    }
490
491    // --- trial_division where residual is 1 (fully factored) ---
492    #[test]
493    fn trial_division_residual_one() {
494        let primes: Vec<u64> = vec![2, 3, 5];
495        let (factors, residual) = trial_division(primes.into_iter(), 60u64, Some(100));
496        assert!(residual.is_ok());
497        assert_eq!(residual.unwrap(), 1);
498        assert_eq!(factors[&2], 2);
499        assert_eq!(factors[&3], 1);
500        assert_eq!(factors[&5], 1);
501    }
502
503    // --- trial_division where bound exceeds sqrt (factored=true with residual prime) ---
504    #[test]
505    fn trial_division_bound_exceeds_sqrt() {
506        // 91 = 7 * 13. Primes up to 13 will factor it, and 13 > sqrt(91) ~ 9.5
507        let primes: Vec<u64> = vec![2, 3, 5, 7, 11, 13];
508        let (factors, residual) = trial_division(primes.into_iter(), 91u64, Some(100));
509        assert!(residual.is_ok());
510        // After dividing by 7, residual is 13, and bound > sqrt means factored
511        assert_eq!(factors[&7], 1);
512        assert_eq!(residual.unwrap(), 13);
513    }
514
515    #[test]
516    fn trial_division_prime_target() {
517        // 97 is prime. Primes up to 10 > sqrt(97) ~ 9.85, so factored=true
518        let primes: Vec<u64> = vec![2, 3, 5, 7, 11];
519        let (factors, residual) = trial_division(primes.into_iter(), 97u64, Some(100));
520        assert!(residual.is_ok());
521        assert!(factors.is_empty());
522        assert_eq!(residual.unwrap(), 97);
523    }
524
525    // --- squfof with perfect square input ---
526    #[test]
527    fn squfof_perfect_square() {
528        // Perfect square: q.is_zero() shortcut
529        let result = squfof(&49u64, 49u64, 100);
530        assert_eq!(result.0, Some(7));
531        assert_eq!(result.1, 0); // should return immediately
532    }
533
534    #[test]
535    fn squfof_perfect_square_large() {
536        let n = 10201u64; // 101^2
537        let result = squfof(&n, n, 100);
538        assert_eq!(result.0, Some(101));
539        assert_eq!(result.1, 0);
540    }
541
542    // --- pollard_rho with known backtracing scenario ---
543    #[test]
544    fn pollard_rho_various_starts() {
545        // Test multiple start/offset combinations to increase path coverage
546        let target = 8051u32;
547        for start in [1u32, 2, 3, 5, 7, 11, 13] {
548            for offset in [1u32, 2, 3, 5, 7] {
549                let (result, _) = pollard_rho(&target, start, offset, 10000);
550                if let Some(f) = result {
551                    assert!(target % f == 0 && f > 1 && f < target);
552                }
553            }
554        }
555    }
556
557    #[test]
558    fn pollard_rho_loop_detection() {
559        // Case where a == b (cycle detected) should return None
560        // Start 0, offset 0: f(x) = x^2 + 0 mod n, starting from 0 => always 0, quick cycle
561        let (result, _) = pollard_rho(&15u32, 0, 0, 100);
562        // This should either find a factor or hit the cycle
563        // Just verify it doesn't panic
564        let _ = result;
565    }
566
567    // --- trial_division with small limit ---
568    #[test]
569    fn trial_division_limited() {
570        // Limit smaller than sqrt means not fully factored
571        let primes: Vec<u64> = vec![2, 3, 5, 7, 11, 13];
572        // 1001 = 7 * 11 * 13, sqrt(1001) ~ 31.6
573        // With limit 10, we can only find factor 7, then residual 143 = 11*13
574        let (factors, residual) = trial_division(primes.into_iter(), 1001u64, Some(10));
575        assert!(residual.is_err()); // not fully factored
576        assert_eq!(factors[&7], 1);
577        assert_eq!(residual.unwrap_err(), 143);
578    }
579}