Skip to content

Commit 69e10cf

Browse files
committed
fix: Add primality test before Pollard-Rho factorization
- Add Miller-Rabin check before factorization - Add comprehensive factor benchmarks
1 parent 2d1048a commit 69e10cf

File tree

3 files changed

+125
-4
lines changed

3 files changed

+125
-4
lines changed

src/uu/factor/benches/factor_bench.rs

Lines changed: 109 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,123 @@
33
// For the full copyright and license information, please view the LICENSE
44
// file that was distributed with this source code.
55

6-
// spell-checker:ignore funcs
6+
// spell-checker:ignore funcs semiprimes
77

88
use divan::{Bencher, black_box};
99
use uu_factor::uumain;
1010
use uucore::benchmark::run_util_function;
1111

12-
/// Benchmark multiple u64 digits
12+
/// Benchmark factoring a range of small consecutive integers
13+
/// This tests the trial division fast path for small numbers
14+
#[divan::bench(args = [1000, 5000])]
15+
fn factor_small_range(bencher: Bencher, count: usize) {
16+
let numbers: Vec<String> = (2..=(count as u64)).map(|n| n.to_string()).collect();
17+
bencher.bench(|| {
18+
for num_str in &numbers {
19+
black_box(run_util_function(uumain, &[num_str]));
20+
}
21+
});
22+
}
23+
24+
/// Benchmark factoring small primes
25+
#[divan::bench(args = [
26+
("prime_1009", "1009"),
27+
("prime_10007", "10007"),
28+
("prime_100003", "100003"),
29+
])]
30+
fn factor_small_primes(bencher: Bencher, (_name, num_str): (&str, &str)) {
31+
bencher.bench(|| {
32+
black_box(run_util_function(uumain, &[num_str]));
33+
});
34+
}
35+
36+
/// Benchmark factoring large u64 primes
37+
/// These require primality testing but no factorization
38+
#[divan::bench(args = [
39+
("prime_near_u32_max", "4294967291"),
40+
("prime_near_i64_max", "9223372036854775783"),
41+
("prime_near_u64_max", "18446744073709551557"),
42+
])]
43+
fn factor_large_u64_primes(bencher: Bencher, (_name, num_str): (&str, &str)) {
44+
bencher.bench(|| {
45+
black_box(run_util_function(uumain, &[num_str]));
46+
});
47+
}
48+
49+
/// Benchmark factoring u64 semiprimes (product of two primes)
50+
/// These exercise the Pollard-Rho factorization algorithm
51+
#[divan::bench(args = [
52+
("semiprime_32bit", "3215031751"),
53+
("semiprime_48bit", "281474976710597"),
54+
("fermat_number_f5", "4294967297"),
55+
])]
56+
fn factor_u64_semiprimes(bencher: Bencher, (_name, num_str): (&str, &str)) {
57+
bencher.bench(|| {
58+
black_box(run_util_function(uumain, &[num_str]));
59+
});
60+
}
61+
62+
/// Benchmark factoring highly composite numbers
63+
/// These have many small factors and test trial division efficiency
64+
#[divan::bench(args = [
65+
("primorial_7", "510510"),
66+
("factorial_12", "479001600"),
67+
("highly_composite", "720720"),
68+
])]
69+
fn factor_highly_composite(bencher: Bencher, (_name, num_str): (&str, &str)) {
70+
bencher.bench(|| {
71+
black_box(run_util_function(uumain, &[num_str]));
72+
});
73+
}
74+
75+
/// Benchmark the maximum u64 value
76+
/// 2^64 - 1 = 3 × 5 × 17 × 257 × 641 × 65537 × 6700417
77+
#[divan::bench]
78+
fn factor_u64_max(bencher: Bencher) {
79+
bencher.bench(|| {
80+
black_box(run_util_function(uumain, &["18446744073709551615"]));
81+
});
82+
}
83+
84+
/// Benchmark factoring Mersenne prime M61 (2^61 - 1)
85+
/// This is a prime number, so it tests primality checking for large u64
86+
#[divan::bench]
87+
fn factor_mersenne_61(bencher: Bencher) {
88+
// 2^61 - 1 = 2305843009213693951 (prime)
89+
bencher.bench(|| {
90+
black_box(run_util_function(uumain, &["2305843009213693951"]));
91+
});
92+
}
93+
94+
/// Benchmark factoring 100-bit numbers with many small factors
95+
/// This tests the u128 factorization path with numbers that factor quickly
96+
#[divan::bench(args = [
97+
("100bit_smooth", "123456789012345678901234567890"),
98+
])]
99+
fn factor_100bit_numbers(bencher: Bencher, (_name, num_str): (&str, &str)) {
100+
bencher.bench(|| {
101+
black_box(run_util_function(uumain, &[num_str]));
102+
});
103+
}
104+
105+
/// Benchmark factoring 128-bit numbers
106+
/// Tests various 128-bit cases: prime, power of 2, and smooth numbers
107+
#[divan::bench(args = [
108+
("128bit_prime", "340282366920938463463374607431768211297"),
109+
("128bit_power_of_2", "340282366920938463463374607431768211456"),
110+
("128bit_smooth", "340282366920938463463374607431768211455"),
111+
])]
112+
fn factor_128bit_numbers(bencher: Bencher, (_name, num_str): (&str, &str)) {
113+
bencher.bench(|| {
114+
black_box(run_util_function(uumain, &[num_str]));
115+
});
116+
}
117+
118+
/// Benchmark processing multiple numbers in sequence
119+
/// This tests the overhead of repeated invocations
13120
#[divan::bench(args = [(2)])]
14121
fn factor_multiple_u64s(bencher: Bencher, start_num: u64) {
15122
bencher
16-
// this is a range of 5000 different u128 integers
17123
.with_inputs(|| (start_num, start_num + 2500))
18124
.bench_values(|(start_u64, end_u64)| {
19125
for u64_digit in start_u64..=end_u64 {

src/uu/factor/src/algorithm_selection.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use num_bigint::BigUint;
1313
use num_traits::ToPrimitive;
1414
use std::collections::BTreeMap;
1515

16+
use super::ecm::is_probable_prime;
1617
use super::fermat::{fermat_factor_biguint, fermat_factor_u64};
1718
use super::pollard_rho::pollard_rho_with_target;
1819
use super::trial_division::{extract_small_factors, quick_trial_divide};
@@ -135,6 +136,13 @@ fn factorize_biguint_fast(n: &BigUint) -> BTreeMap<BigUint, usize> {
135136
return factors;
136137
}
137138

139+
// Check if remaining is prime before attempting factorization
140+
// Primality testing is much faster than trying Pollard-Rho on a prime
141+
if is_probable_prime(&remaining) {
142+
factors.insert(remaining, 1);
143+
return factors;
144+
}
145+
138146
// Try Fermat's method for numbers up to ~90 bits (optimal for close factors)
139147
if remaining.bits() <= 90 {
140148
if let Some(fermat_factor) = fermat_factor_biguint(&remaining) {
@@ -163,6 +171,13 @@ fn factorize_biguint_pollard_rho(factors: &mut BTreeMap<BigUint, usize>, n: BigU
163171
return;
164172
}
165173

174+
// Check if n is prime before attempting factorization
175+
// Primality testing is much faster than trying Pollard-Rho on a prime
176+
if is_probable_prime(&n) {
177+
*factors.entry(n).or_insert(0) += 1;
178+
return;
179+
}
180+
166181
// Estimate factor size (assume roughly balanced factors)
167182
let target_bits = (n.bits() as u32) / 2;
168183

src/uu/factor/src/ecm.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -669,7 +669,7 @@ fn compute_prime_product(bound: u64) -> u64 {
669669
}
670670

671671
/// Miller-Rabin primality test
672-
fn is_probable_prime(n: &BigUint) -> bool {
672+
pub fn is_probable_prime(n: &BigUint) -> bool {
673673
// Use Miller-Rabin primality test with 15 iterations (high confidence)
674674
// This properly distinguishes composites from primes even for large numbers
675675
use num_integer::Integer;

0 commit comments

Comments
 (0)