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}