Skip to content

Commit 3673060

Browse files
committed
factor: optimize for u128 performance with modular architecture
Add modular factorization algorithms for improved u128 performance: - algorithm_selection.rs: Smart algorithm selection based on number size - crypto_bigint_adapter.rs: Adapter for crypto-bigint library - ecm.rs, ecm_bsgs.rs, ecm_params.rs: Elliptic Curve Method implementation - fermat.rs: Fermat's factorization method - montgomery.rs: Montgomery arithmetic for modular operations - pollard_rho.rs: Pollard's rho algorithm - precomputed_curves.rs: Precomputed elliptic curves for ECM - trial_division.rs: Optimized trial division - u64_factor.rs: Fast u64 factorization
1 parent 7c62885 commit 3673060

15 files changed

+4192
-283
lines changed

Cargo.lock

Lines changed: 318 additions & 253 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,9 @@ memmap2 = "0.9.4"
346346
nix = { version = "0.30", default-features = false }
347347
nom = "8.0.0"
348348
notify = { version = "=8.2.0", features = ["macos_kqueue"] }
349+
crypto-bigint = "0.5"
349350
num-bigint = "0.4.4"
351+
num-integer = "0.1"
350352
num-prime = "0.4.4"
351353
num-traits = "0.2.19"
352354
onig = { version = "~6.5.1", default-features = false }

src/uu/factor/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ uucore = { workspace = true }
2424
num-bigint = { workspace = true }
2525
num-prime = { workspace = true }
2626
fluent = { workspace = true }
27+
num-integer = { workspace = true }
28+
rand = { workspace = true }
29+
crypto-bigint = { workspace = true }
2730

2831
[[bin]]
2932
name = "factor"
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
// This file is part of the uutils coreutils package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// file that was distributed with this source code.
5+
6+
//! Algorithm selection for optimal factorization
7+
//!
8+
//! This module routes numbers to the appropriate factorization method:
9+
//! - Small numbers (< 128 bits): fast_factor (optimized for u64/u128 range)
10+
//! - Larger numbers (>= 128 bits): falls back to num_prime
11+
12+
use num_bigint::BigUint;
13+
use num_traits::ToPrimitive;
14+
use std::collections::BTreeMap;
15+
16+
use super::fermat::{fermat_factor_biguint, fermat_factor_u64};
17+
use super::pollard_rho::pollard_rho_with_target;
18+
use super::trial_division::{extract_small_factors, quick_trial_divide};
19+
use super::u64_factor::{is_probable_prime_u64, pollard_rho_brent_u64, trial_division_u64};
20+
21+
/// Fast factorization for numbers < 128 bits
22+
///
23+
/// Strategy (internal routing):
24+
/// - ≤ 64 bits: Use optimized u64 algorithms (trial division + Pollard-Rho, Fermat hint)
25+
/// - 64-~90 bits: Use optimized BigUint Pollard-Rho after stripping small factors
26+
fn fast_factorize_small(n: &BigUint) -> BTreeMap<BigUint, usize> {
27+
let bits = n.bits();
28+
29+
// Handle trivial cases
30+
if n <= &BigUint::from(1u32) {
31+
return BTreeMap::new();
32+
}
33+
34+
// For numbers ≤ 64 bits, use u64 optimization path
35+
if bits <= 64 {
36+
if let Some(n_u64) = n.to_u64() {
37+
return factorize_u64_fast(n_u64);
38+
}
39+
}
40+
41+
// For 64-~90 bit numbers, use BigUint path with optimizations
42+
factorize_biguint_fast(n)
43+
}
44+
45+
/// Optimized factorization for u64 numbers
46+
fn factorize_u64_fast(mut n: u64) -> BTreeMap<BigUint, usize> {
47+
let mut factors = BTreeMap::new();
48+
49+
if n <= 1 {
50+
return factors;
51+
}
52+
53+
if n == 2 || n == 3 || n == 5 {
54+
factors.insert(BigUint::from(n), 1);
55+
return factors;
56+
}
57+
58+
// Trial division for small primes (up to ~1000)
59+
let small_primes_u64 = trial_division_u64(&mut n, 1000);
60+
for prime in small_primes_u64 {
61+
*factors.entry(BigUint::from(prime)).or_insert(0) += 1;
62+
}
63+
64+
// If fully factored, return
65+
if n == 1 {
66+
return factors;
67+
}
68+
69+
// Check if remaining number is prime
70+
if is_probable_prime_u64(n) {
71+
factors.insert(BigUint::from(n), 1);
72+
return factors;
73+
}
74+
75+
// Try Fermat's method first for semiprimes (optimal for close factors)
76+
if let Some(fermat_factor) = fermat_factor_u64(n) {
77+
// Found via Fermat! Recursively factor both parts
78+
factorize_u64_pollard_rho(&mut factors, fermat_factor);
79+
factorize_u64_pollard_rho(&mut factors, n / fermat_factor);
80+
return factors;
81+
}
82+
83+
// Fallback to Pollard-Rho for remaining composite
84+
factorize_u64_pollard_rho(&mut factors, n);
85+
86+
factors
87+
}
88+
89+
/// Recursive Pollard-Rho factorization for u64
90+
fn factorize_u64_pollard_rho(factors: &mut BTreeMap<BigUint, usize>, n: u64) {
91+
if n == 1 {
92+
return;
93+
}
94+
95+
if is_probable_prime_u64(n) {
96+
*factors.entry(BigUint::from(n)).or_insert(0) += 1;
97+
return;
98+
}
99+
100+
// Find a factor using Pollard-Rho
101+
if let Some(factor) = pollard_rho_brent_u64(n) {
102+
// Recursively factor both parts
103+
factorize_u64_pollard_rho(factors, factor);
104+
factorize_u64_pollard_rho(factors, n / factor);
105+
} else {
106+
// Couldn't find factor, assume it's prime (shouldn't happen often)
107+
*factors.entry(BigUint::from(n)).or_insert(0) += 1;
108+
}
109+
}
110+
111+
/// Optimized factorization for BigUint (64-~90 bit range, internal)
112+
fn factorize_biguint_fast(n: &BigUint) -> BTreeMap<BigUint, usize> {
113+
let mut factors = BTreeMap::new();
114+
115+
// Extract small factors first
116+
let (small_factors, mut remaining) = extract_small_factors(n.clone());
117+
for factor in small_factors {
118+
*factors.entry(factor).or_insert(0) += 1;
119+
}
120+
121+
// If fully factored, return
122+
if remaining == BigUint::from(1u32) {
123+
return factors;
124+
}
125+
126+
// Trial division for medium-sized primes
127+
let (more_factors, final_remaining) = quick_trial_divide(remaining);
128+
for factor in more_factors {
129+
*factors.entry(factor).or_insert(0) += 1;
130+
}
131+
remaining = final_remaining;
132+
133+
// If fully factored, return
134+
if remaining == BigUint::from(1u32) || remaining == BigUint::from(0u32) {
135+
return factors;
136+
}
137+
138+
// Try Fermat's method for numbers up to ~90 bits (optimal for close factors)
139+
if remaining.bits() <= 90 {
140+
if let Some(fermat_factor) = fermat_factor_biguint(&remaining) {
141+
// Found via Fermat! Recursively factor both parts
142+
factorize_biguint_pollard_rho(&mut factors, fermat_factor.clone());
143+
factorize_biguint_pollard_rho(&mut factors, &remaining / &fermat_factor);
144+
return factors;
145+
}
146+
}
147+
148+
// Fallback to Pollard-Rho for remaining composite
149+
factorize_biguint_pollard_rho(&mut factors, remaining);
150+
151+
factors
152+
}
153+
154+
/// Recursive Pollard-Rho factorization for BigUint (internal)
155+
fn factorize_biguint_pollard_rho(factors: &mut BTreeMap<BigUint, usize>, n: BigUint) {
156+
if n == BigUint::from(1u32) {
157+
return;
158+
}
159+
160+
// For very small n, assume prime
161+
if n.bits() <= 20 {
162+
*factors.entry(n).or_insert(0) += 1;
163+
return;
164+
}
165+
166+
// Estimate factor size (assume roughly balanced factors)
167+
let target_bits = (n.bits() as u32) / 2;
168+
169+
// Find a factor using Pollard-Rho
170+
if let Some(factor) = pollard_rho_with_target(&n, target_bits) {
171+
if factor < n {
172+
// Recursively factor both parts
173+
factorize_biguint_pollard_rho(factors, factor.clone());
174+
factorize_biguint_pollard_rho(factors, &n / &factor);
175+
} else {
176+
// Factor is same as n, assume prime
177+
*factors.entry(n).or_insert(0) += 1;
178+
}
179+
} else {
180+
// Couldn't find factor, assume it's prime
181+
*factors.entry(n).or_insert(0) += 1;
182+
}
183+
}
184+
185+
/// Main factorization entry point with algorithm selection
186+
///
187+
/// Routes to the optimal algorithm based on number size:
188+
/// - < 128 bits: fast_factor (trial division + Fermat + Pollard-Rho)
189+
/// - otherwise: num_prime fallback (best-effort for very large numbers)
190+
pub fn factorize(n: &BigUint) -> (BTreeMap<BigUint, usize>, Option<Vec<BigUint>>) {
191+
let bits = n.bits();
192+
193+
// < 128-bit path: use our fast implementation
194+
if bits < 128 {
195+
return (fast_factorize_small(n), None);
196+
}
197+
198+
// Fallback: delegate to num_prime for larger inputs
199+
num_prime::nt_funcs::factors(n.clone(), None)
200+
}
201+
202+
#[cfg(test)]
203+
mod tests {
204+
use super::*;
205+
206+
#[test]
207+
fn test_factorize_128bit() {
208+
// 128-bit semiprime (boundary of <u128 focus)
209+
// Using two ~64-bit primes to create a ~128-bit semiprime
210+
let p = BigUint::parse_bytes(b"18446744073709551629", 10).unwrap();
211+
let q = BigUint::parse_bytes(b"18446744073709551557", 10).unwrap();
212+
let n = &p * &q;
213+
214+
assert!(n.bits() >= 100);
215+
216+
let (factors, remaining) = factorize(&n);
217+
assert_eq!(remaining, None);
218+
// Should factor successfully
219+
assert!(!factors.is_empty());
220+
}
221+
222+
#[test]
223+
#[ignore] // factoring ~200-bit semiprimes is out of scope for this <u128-focused build
224+
fn test_factorize_200bit() {
225+
// ~200-bit semiprime (previously used SIQS). Out of scope without yamaquasi.
226+
let p = BigUint::parse_bytes(b"1267650600228229401496703205653", 10).unwrap();
227+
let q = BigUint::parse_bytes(b"1267650600228229401496703205659", 10).unwrap();
228+
let n = &p * &q;
229+
230+
assert!(n.bits() >= 180); // ~200 bits
231+
232+
// Without SIQS, we do not require full factorization here.
233+
let (_factors, _remaining) = factorize(&n);
234+
}
235+
236+
#[test]
237+
#[ignore] // This test may be slow - 400 bits might exceed yamaquasi's optimal range
238+
fn test_factorize_400bit() {
239+
// 400-bit number (beyond yamaquasi's 330-bit limit, falls back to num_prime)
240+
// Using smaller factors to ensure it can still be factored
241+
let p = BigUint::parse_bytes(
242+
b"1606938044258990275541962092341162602522202993782792831",
243+
10,
244+
)
245+
.unwrap(); // ~200 bits
246+
let q = BigUint::parse_bytes(
247+
b"1606938044258990275541962092341162602522202993782792833",
248+
10,
249+
)
250+
.unwrap(); // ~200 bits
251+
let n = &p * &q;
252+
253+
assert!(n.bits() > 330); // Should exceed yamaquasi's range
254+
255+
let (_factors, remaining) = factorize(&n);
256+
// This may timeout or fail depending on num_prime's capabilities
257+
// We're mainly testing that it doesn't panic
258+
assert!(remaining.is_none() || remaining.is_some());
259+
}
260+
}

0 commit comments

Comments
 (0)