From 8ae51a5ef5a0ce1c75b957ee5a18e6e79d463f9d Mon Sep 17 00:00:00 2001 From: mattsu Date: Thu, 13 Nov 2025 21:43:46 +0900 Subject: [PATCH 1/3] feat(factor): improve big integer factoring and add num-integer - Add num-integer dependency to support enhanced numeric operations. - Refactor factorization logic to avoid redundant parsing and optimize u64/u128 paths. - Improve handling of non-positive and invalid inputs to align with GNU factor behavior. - Enhance large BigUint factoring with additional algorithms and clearer limitations. --- Cargo.toml | 1 + src/uu/factor/src/factor.rs | 431 +++++++++++++++++++++++++++++++---- tests/by-util/test_factor.rs | 12 + 3 files changed, 404 insertions(+), 40 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1aa3892798e..334cfe5ae2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -357,6 +357,7 @@ nix = { version = "0.30", default-features = false } nom = "8.0.0" notify = { version = "=8.2.0", features = ["macos_kqueue"] } num-bigint = "0.4.4" +num-integer = "0.1" num-prime = "0.4.4" num-traits = "0.2.19" onig = { version = "~6.5.1", default-features = false } diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 898679893c9..6b13b8ead61 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -4,6 +4,13 @@ // file that was distributed with this source code. // spell-checker:ignore funcs +// NOTE: +// For BigUint > u128, this implementation attempts factorization using Miller-Rabin, +// an improved Pollard-rho, and p-1. +// However, compared to GNU factor, there may still be differences in performance +// and success rate. +// To further approach GNU factor behavior, additional algorithms (e.g. ECM) +// and parameter tuning may be required. use std::collections::BTreeMap; use std::io::BufRead; @@ -11,9 +18,9 @@ use std::io::{self, Write, stdin, stdout}; use clap::{Arg, ArgAction, Command}; use num_bigint::BigUint; -use num_traits::FromPrimitive; +use num_traits::{FromPrimitive, One, Zero}; use uucore::display::Quotable; -use uucore::error::{FromIo, UResult, USimpleError, set_exit_code}; +use uucore::error::{FromIo, UResult, set_exit_code}; use uucore::translate; use uucore::{format_usage, show_error, show_warning}; @@ -28,52 +35,55 @@ fn write_factors_str( w: &mut io::BufWriter, print_exponents: bool, ) -> UResult<()> { - let rx = num_str.trim().parse::(); - let Ok(x) = rx else { - // return Ok(). it's non-fatal and we should try the next number. - show_warning!("{}: {}", num_str.maybe_quote(), rx.unwrap_err()); + let s = num_str.trim(); + + // First, interpret as BigUint. + let rx_big = s.parse::(); + let Ok(x_big) = rx_big else { + // Non-fatal error. Proceed to the next input number. + show_warning!("{}: {}", num_str.maybe_quote(), rx_big.unwrap_err()); set_exit_code(1); return Ok(()); }; - if x > BigUint::from_u32(1).unwrap() { - // use num_prime's factorize64 algorithm for u64 integers - if x <= BigUint::from_u64(u64::MAX).unwrap() { - let prime_factors = num_prime::nt_funcs::factorize64(x.clone().to_u64_digits()[0]); - write_result_u64(w, &x, prime_factors, print_exponents) - .map_err_context(|| translate!("factor-error-write-error"))?; - } - // use num_prime's factorize128 algorithm for u128 integers - else if x <= BigUint::from_u128(u128::MAX).unwrap() { - let rx = num_str.trim().parse::(); - let Ok(x) = rx else { - // return Ok(). it's non-fatal and we should try the next number. - show_warning!("{}: {}", num_str.maybe_quote(), rx.unwrap_err()); - set_exit_code(1); - return Ok(()); - }; - let prime_factors = num_prime::nt_funcs::factorize128(x); - write_result_u128(w, &x, prime_factors, print_exponents) - .map_err_context(|| translate!("factor-error-write-error"))?; - } - // use num_prime's fallible factorization for anything greater than u128::MAX - else { - let (prime_factors, remaining) = num_prime::nt_funcs::factors(x.clone(), None); - if let Some(_remaining) = remaining { - return Err(USimpleError::new( - 1, - translate!("factor-error-factorization-incomplete"), - )); - } - write_result_big_uint(w, &x, prime_factors, print_exponents) - .map_err_context(|| translate!("factor-error-write-error"))?; - } - } else { + if x_big <= BigUint::from_u32(1).unwrap() { + // For values <= 1: as in GNU factor, print the input with no prime factors. let empty_primes: BTreeMap = BTreeMap::new(); - write_result_big_uint(w, &x, empty_primes, print_exponents) + write_result_big_uint(w, &x_big, empty_primes, print_exponents) + .map_err_context(|| translate!("factor-error-write-error"))?; + return Ok(()); + } + + // Try parsing directly into u64 / u128 and delegate to num_prime if successful. + // This avoids unnecessary BigUint conversions and speeds up the common cases. + if let Ok(v) = s.parse::() { + let prime_factors = num_prime::nt_funcs::factorize64(v); + write_result_u64(w, &x_big, prime_factors, print_exponents) .map_err_context(|| translate!("factor-error-write-error"))?; + return Ok(()); + } + + if let Ok(v) = s.parse::() { + let prime_factors = num_prime::nt_funcs::factorize128(v); + write_result_u128(w, &v, prime_factors, print_exponents) + .map_err_context(|| translate!("factor-error-write-error"))?; + return Ok(()); + } + + // For BigUint > u128: use our own recursive factorization based on + // Miller-Rabin + Pollard-rho + p-1. + let mut factors: Vec = Vec::new(); + let success = factor_biguint_recursive(&x_big, &mut factors); + + if !success { + // 完全分解できなかった場合のみ exit code=1 + set_exit_code(1); } + let factorization = collect_biguint_factors(&factors); + write_result_big_uint(w, &x_big, factorization, print_exponents) + .map_err_context(|| translate!("factor-error-write-error"))?; + Ok(()) } @@ -123,6 +133,347 @@ fn write_result_u128( w.flush() } +fn collect_biguint_factors(factors: &[BigUint]) -> BTreeMap { + let mut map = BTreeMap::::new(); + for f in factors { + *map.entry(f.clone()).or_insert(0) += 1; + } + map +} + +fn is_probable_prime(n: &BigUint) -> bool { + if *n < BigUint::from_u32(2).unwrap() { + return false; + } + if *n == BigUint::from_u32(2).unwrap() || *n == BigUint::from_u32(3).unwrap() { + return true; + } + // even check: n % 2 == 0 + if (&*n & BigUint::from_u32(1).unwrap()).is_zero() { + return false; + } + + let one = BigUint::one(); + let two = BigUint::from_u32(2).unwrap(); + + // n - 1 = d * 2^s + let mut d = n - &one; + let mut s = 0u32; + // while d is even + while (&d & BigUint::from_u32(1).unwrap()).is_zero() { + d >>= 1; + s += 1; + } + + let bases_32: [u64; 3] = [2, 7, 61]; + let bases_64: [u64; 12] = [ + 2, 325, 9375, 28178, 450775, 9780504, 1795265022, 3, 5, 7, 11, 13, + ]; + + let bases: Vec = if n.bits() <= 32 { + bases_32.to_vec() + } else if n.bits() <= 64 { + bases_64.to_vec() + } else { + vec![2, 3, 5, 7, 11, 13, 17, 19, 23] + }; + + 'outer: for &a_u64 in &bases { + if BigUint::from(a_u64) >= *n { + continue; + } + let a = BigUint::from(a_u64); + let mut x = a.modpow(&d, n); + if x == one || x == n - &one { + continue 'outer; + } + + for _ in 1..s { + x = x.modpow(&two, n); + if x == n - &one { + continue 'outer; + } + if x == one { + return false; + } + } + return false; + } + + true +} + +fn small_trial_division(n: &BigUint) -> Option { + // Quickly strip very small prime factors before applying expensive algorithms. + // This is intentionally lightweight while still covering a reasonably wide range. + // GNU factor maintains a large trial table; here we mimic only a small portion of it. + // + // NOTE: By removing many small prime factors here, we significantly reduce the + // search space and failure count of subsequent Pollard-rho / p-1 steps. + const SMALL_PRIMES: [u16; 54] = [ + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, + 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, + 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, + ]; + + for &p in &SMALL_PRIMES { + let p_big = BigUint::from_u32(p as u32).unwrap(); + if n == &p_big { + return None; + } + if (n % &p_big).is_zero() { + return Some(p_big); + } + } + None +} + +//// Simplified Pollard p-1 method (Stage 1 only). +//// Effective when p-1 (for a prime divisor p of n) is smooth with small prime factors. +fn pollard_p_minus_1(n: &BigUint) -> Option { + // Stage 1 only (simplified). + // Best-effort: we do not spend too long here; give up quickly if it does not help. + let one = BigUint::one(); + let two = BigUint::from_u32(2).unwrap(); + + if n.is_zero() || n.is_one() { + return None; + } + + if (n & &one).is_zero() { + return Some(two); + } + + let bits = n.bits(); + + // Keep B1 relatively small to avoid excessive cost. + // (GNU factor adjusts based on input and retries; we use a fixed approximation.) + let b1: u64 = if bits <= 256 { + 10_000 + } else if bits <= 512 { + 20_000 + } else { + 50_000 + }; + + // Only try a few small prime bases. + const BASES: [u64; 3] = [2, 3, 5]; + + for &base in &BASES { + let mut a = BigUint::from(base); + if &a >= n { + continue; + } + + let mut g = gcd_biguint(&a, n); + if g > one && &g < n { + return Some(g); + } + + // a^(M) を段階的に構成(指数を 2^k で伸ばす近似) + let mut e = 2u64; + while e <= b1 { + a = a.modpow(&BigUint::from_u64(e).unwrap(), n); + if a.is_one() { + break; + } + let am1 = if a > one { &a - &one } else { continue }; + g = gcd_biguint(&am1, n); + if g > one && &g < n { + return Some(g); + } + e <<= 1; + } + } + + None +} + +//// Improved Pollard-rho (Brent variant with batched gcd). +//// Not equivalent to GNU factor, but aims for better convergence and performance +//// than a naive implementation. +fn pollard_rho(n: &BigUint) -> Option { + // NOTE: + // - This implementation is inspired by the approach in GNU factor but simplified. + // - For large inputs we avoid running too long; we cap the iterations so that + // we do not spend many seconds on hopeless cases. + // - If factorization fails, we return "Factorization incomplete"-style results. + let one = BigUint::one(); + + // For small n we expect earlier code paths to have handled the input. + if *n <= BigUint::from_u32(3).unwrap() { + return None; + } + + // If n is even, return 2 immediately. + if (n & &one).is_zero() { + return Some(BigUint::from_u32(2).unwrap()); + } + + // Use a deterministic LCG to generate parameter sequences. + fn lcg_next(x: &mut u128) { + *x = x + .wrapping_mul(6364136223846793005) + .wrapping_add(1442695040888963407); + } + + let bits = n.bits() as u64; + + // Search parameters: choose bounds based on bit length. + // Avoid overly large limits; when exhausted, treat as failure to find a factor. + let max_tries: u64 = 16; + let max_iter: u64 = (bits * bits).min(200_000).max(10_000); + + let mut seed: u128 = 0x9e3779b97f4a7c15; + + for _try in 0..max_tries { + lcg_next(&mut seed); + let mut x = BigUint::from(seed % (u128::MAX / 2 + 1)); + lcg_next(&mut seed); + let mut c = BigUint::from(seed % (u128::MAX / 2 + 1)); + if c.is_zero() { + c = BigUint::from(1u32); + } + x %= n; + c %= n; + + let mut y = x.clone(); + let mut g = one.clone(); + let mut q = one.clone(); + + let mut iter: u64 = 0; + let m: u64 = 128; + + while g == one && iter < max_iter { + // Brent variant: use batched gcd. + let mut k = 0; + let x_saved = x.clone(); + while k < m && iter < max_iter { + // f(z) = z^2 + c mod n (implemented via multiplication + mod, not modpow). + y = (&y * &y + &c) % n; + let diff = if &x > &y { &x - &y } else { &y - &x }; + if !diff.is_zero() { + q = (q * diff) % n; + } + k += 1; + iter += 1; + } + g = gcd_biguint(&q, n); + x = x_saved; + + if g == one { + // Update x to advance the sequence. + x = y.clone(); + } + } + + if g == one { + continue; + } + if &g == n { + // Fallback: step-by-step gcd. + let mut z = x.clone(); + loop { + z = (&z * &z + &c) % n; + let diff = if &z > &y { &z - &y } else { &y - &z }; + g = gcd_biguint(&diff, n); + if g > one || z == y { + break; + } + } + } + + if g > one && &g < n { + return Some(g); + } + } + + None +} + +//// Recursively factor n and append factors (primes or unfactored composites) to `factors`. +//// Returns true if full factorization succeeded, false otherwise. +fn gcd_biguint(a: &BigUint, b: &BigUint) -> BigUint { + // Standard Euclidean algorithm using owned BigUint values to avoid lifetime issues. + let mut x = a.clone(); + let mut y = b.clone(); + while !y.is_zero() { + let r = &x % &y; + x = y; + y = r; + } + x +} + +fn factor_biguint_recursive(n: &BigUint, factors: &mut Vec) -> bool { + let one = BigUint::one(); + if *n <= one { + return true; + } + + // First remove small prime factors, then apply more expensive methods. + if let Some(p) = small_trial_division(n) { + let mut q = n.clone(); + let mut ok = true; + while (&q % &p).is_zero() { + q /= &p; + factors.push(p.clone()); + } + if !q.is_one() { + ok &= factor_biguint_recursive(&q, factors); + } + return ok; + } + + // If n is small enough, use num_prime's factorize128 for speed. + if n.bits() <= 128 { + if let Ok(x128) = n.to_string().parse::() { + let pf = num_prime::nt_funcs::factorize128(x128); + if !pf.is_empty() { + for (p, e) in pf { + for _ in 0..e { + factors.push(BigUint::from(p)); + } + } + return true; + } + } + } + + if is_probable_prime(n) { + factors.push(n.clone()); + return true; + } + + // Try Pollard p-1 first (simplified Stage 1). + if let Some(f) = pollard_p_minus_1(n) { + if f.is_one() || &f == n { + // Treat as failure. + } else { + let q = n / &f; + let left_ok = factor_biguint_recursive(&f, factors); + let right_ok = factor_biguint_recursive(&q, factors); + return left_ok && right_ok; + } + } + + // Then try improved Pollard-rho (Brent variant). + if let Some(f) = pollard_rho(n) { + if f.is_one() || &f == n { + factors.push(n.clone()); + return false; + } + let q = n / &f; + let left_ok = factor_biguint_recursive(&f, factors); + let right_ok = factor_biguint_recursive(&q, factors); + return left_ok && right_ok; + } + + // If no factor was found, include n itself as part of the (incomplete) factorization. + factors.push(n.clone()); + false +} + /// Writing out the prime factors for BigUint integers fn write_result_big_uint( w: &mut io::BufWriter, diff --git a/tests/by-util/test_factor.rs b/tests/by-util/test_factor.rs index 0a9e6c3e5ea..d64a9e03ad2 100644 --- a/tests/by-util/test_factor.rs +++ b/tests/by-util/test_factor.rs @@ -169,6 +169,18 @@ fn test_cli_args() { .stdout_contains("9: 3 3"); } +#[test] +fn test_large_regression_number() { + let n = "15111234931751377131713914373267893176342349831"; + new_ucmd!() + .arg(n) + .timeout(Duration::from_secs(240)) + .succeeds() + .stdout_is(format!( + "{n}: 29 127 115319 1971799 82465494029827 218807630881735711\n" + )); +} + #[test] fn test_random() { let log_num_primes = f64::from(u32::try_from(NUM_PRIMES).unwrap()).log2().ceil(); From 25d3b63474d37154ca999cdc57779dc9ee5893ef Mon Sep 17 00:00:00 2001 From: mattsu Date: Wed, 18 Feb 2026 16:55:05 +0900 Subject: [PATCH 2/3] refactor(factor): improve readability in bigint factoring paths --- src/uu/factor/src/factor.rs | 204 ++++++++++++++++++++---------------- 1 file changed, 115 insertions(+), 89 deletions(-) diff --git a/src/uu/factor/src/factor.rs b/src/uu/factor/src/factor.rs index 6b13b8ead61..7df6375a9ca 100644 --- a/src/uu/factor/src/factor.rs +++ b/src/uu/factor/src/factor.rs @@ -3,7 +3,7 @@ // For the full copyright and license information, please view the LICENSE // file that was distributed with this source code. -// spell-checker:ignore funcs +// spell-checker:ignore funcs biguint modpow unfactored // NOTE: // For BigUint > u128, this implementation attempts factorization using Miller-Rabin, // an improved Pollard-rho, and p-1. @@ -18,7 +18,7 @@ use std::io::{self, Write, stdin, stdout}; use clap::{Arg, ArgAction, Command}; use num_bigint::BigUint; -use num_traits::{FromPrimitive, One, Zero}; +use num_traits::{FromPrimitive, One, ToPrimitive, Zero}; use uucore::display::Quotable; use uucore::error::{FromIo, UResult, set_exit_code}; use uucore::translate; @@ -76,7 +76,7 @@ fn write_factors_str( let success = factor_biguint_recursive(&x_big, &mut factors); if !success { - // 完全分解できなかった場合のみ exit code=1 + // Only set exit code=1 when complete factorization could not be achieved set_exit_code(1); } @@ -141,59 +141,74 @@ fn collect_biguint_factors(factors: &[BigUint]) -> BTreeMap { map } -fn is_probable_prime(n: &BigUint) -> bool { - if *n < BigUint::from_u32(2).unwrap() { +fn is_even(value: &BigUint) -> bool { + (value & BigUint::one()).is_zero() +} + +fn is_probable_prime(candidate: &BigUint) -> bool { + if *candidate < BigUint::from_u32(2).unwrap() { return false; } - if *n == BigUint::from_u32(2).unwrap() || *n == BigUint::from_u32(3).unwrap() { + if *candidate == BigUint::from_u32(2).unwrap() || *candidate == BigUint::from_u32(3).unwrap() { return true; } - // even check: n % 2 == 0 - if (&*n & BigUint::from_u32(1).unwrap()).is_zero() { + // even check: candidate % 2 == 0 + if is_even(candidate) { return false; } let one = BigUint::one(); let two = BigUint::from_u32(2).unwrap(); - // n - 1 = d * 2^s - let mut d = n - &one; - let mut s = 0u32; - // while d is even - while (&d & BigUint::from_u32(1).unwrap()).is_zero() { - d >>= 1; - s += 1; + // candidate - 1 = odd_component * 2^power_of_two + let mut odd_component = candidate - &one; + let mut power_of_two = 0u32; + // while odd_component is even + while is_even(&odd_component) { + odd_component >>= 1; + power_of_two += 1; } let bases_32: [u64; 3] = [2, 7, 61]; let bases_64: [u64; 12] = [ - 2, 325, 9375, 28178, 450775, 9780504, 1795265022, 3, 5, 7, 11, 13, + 2, + 325, + 9375, + 28178, + 450_775, + 9_780_504, + 1_795_265_022, + 3, + 5, + 7, + 11, + 13, ]; - let bases: Vec = if n.bits() <= 32 { + let bases: Vec = if candidate.bits() <= 32 { bases_32.to_vec() - } else if n.bits() <= 64 { + } else if candidate.bits() <= 64 { bases_64.to_vec() } else { vec![2, 3, 5, 7, 11, 13, 17, 19, 23] }; - 'outer: for &a_u64 in &bases { - if BigUint::from(a_u64) >= *n { + 'outer: for &base_value in &bases { + if BigUint::from(base_value) >= *candidate { continue; } - let a = BigUint::from(a_u64); - let mut x = a.modpow(&d, n); - if x == one || x == n - &one { + let base = BigUint::from(base_value); + let mut witness = base.modpow(&odd_component, candidate); + if witness == one || witness == candidate - &one { continue 'outer; } - for _ in 1..s { - x = x.modpow(&two, n); - if x == n - &one { + for _ in 1..power_of_two { + witness = witness.modpow(&two, candidate); + if witness == candidate - &one { continue 'outer; } - if x == one { + if witness == one { return false; } } @@ -228,8 +243,8 @@ fn small_trial_division(n: &BigUint) -> Option { None } -//// Simplified Pollard p-1 method (Stage 1 only). -//// Effective when p-1 (for a prime divisor p of n) is smooth with small prime factors. +/// Simplified Pollard p-1 method (Stage 1 only). +/// Effective when p-1 (for a prime divisor p of n) is smooth with small prime factors. fn pollard_p_minus_1(n: &BigUint) -> Option { // Stage 1 only (simplified). // Best-effort: we do not spend too long here; give up quickly if it does not help. @@ -240,7 +255,7 @@ fn pollard_p_minus_1(n: &BigUint) -> Option { return None; } - if (n & &one).is_zero() { + if is_even(n) { return Some(two); } @@ -270,7 +285,7 @@ fn pollard_p_minus_1(n: &BigUint) -> Option { return Some(g); } - // a^(M) を段階的に構成(指数を 2^k で伸ばす近似) + // Construct a^(M) step by step (approximating by extending the exponent with 2^k) let mut e = 2u64; while e <= b1 { a = a.modpow(&BigUint::from_u64(e).unwrap(), n); @@ -289,120 +304,131 @@ fn pollard_p_minus_1(n: &BigUint) -> Option { None } -//// Improved Pollard-rho (Brent variant with batched gcd). -//// Not equivalent to GNU factor, but aims for better convergence and performance -//// than a naive implementation. -fn pollard_rho(n: &BigUint) -> Option { +/// Improved Pollard-rho (Brent variant with batched gcd). +/// Not equivalent to GNU factor, but aims for better convergence and performance +/// than a naive implementation. +fn pollard_rho(composite: &BigUint) -> Option { // NOTE: // - This implementation is inspired by the approach in GNU factor but simplified. // - For large inputs we avoid running too long; we cap the iterations so that // we do not spend many seconds on hopeless cases. // - If factorization fails, we return "Factorization incomplete"-style results. let one = BigUint::one(); + let two = BigUint::from_u32(2).unwrap(); // For small n we expect earlier code paths to have handled the input. - if *n <= BigUint::from_u32(3).unwrap() { + if *composite <= BigUint::from_u32(3).unwrap() { return None; } - // If n is even, return 2 immediately. - if (n & &one).is_zero() { - return Some(BigUint::from_u32(2).unwrap()); + // If composite is even, return 2 immediately. + if is_even(composite) { + return Some(two); } // Use a deterministic LCG to generate parameter sequences. + const LCG_MULTIPLIER: u128 = 6_364_136_223_846_793_005; + const LCG_INCREMENT: u128 = 1_442_695_040_888_963_407; + fn lcg_next(x: &mut u128) { - *x = x - .wrapping_mul(6364136223846793005) - .wrapping_add(1442695040888963407); + *x = x.wrapping_mul(LCG_MULTIPLIER).wrapping_add(LCG_INCREMENT); } - let bits = n.bits() as u64; + let bits = composite.bits(); // Search parameters: choose bounds based on bit length. // Avoid overly large limits; when exhausted, treat as failure to find a factor. let max_tries: u64 = 16; - let max_iter: u64 = (bits * bits).min(200_000).max(10_000); + let max_iter: u64 = (bits * bits).clamp(10_000, 200_000); - let mut seed: u128 = 0x9e3779b97f4a7c15; + const LCG_DEFAULT_SEED: u128 = 0x9e37_79b9_7f4a_7c15; + let mut seed: u128 = LCG_DEFAULT_SEED; for _try in 0..max_tries { lcg_next(&mut seed); - let mut x = BigUint::from(seed % (u128::MAX / 2 + 1)); + let mut x_state = BigUint::from(seed % (u128::MAX / 2 + 1)); lcg_next(&mut seed); - let mut c = BigUint::from(seed % (u128::MAX / 2 + 1)); - if c.is_zero() { - c = BigUint::from(1u32); + let mut constant = BigUint::from(seed % (u128::MAX / 2 + 1)); + if constant.is_zero() { + constant = BigUint::from(1u32); } - x %= n; - c %= n; + x_state %= composite; + constant %= composite; - let mut y = x.clone(); - let mut g = one.clone(); - let mut q = one.clone(); + let mut y_state = x_state.clone(); + let mut current_gcd = one.clone(); + let mut product = one.clone(); let mut iter: u64 = 0; - let m: u64 = 128; + let batch_size: u64 = 128; - while g == one && iter < max_iter { + while current_gcd == one && iter < max_iter { // Brent variant: use batched gcd. - let mut k = 0; - let x_saved = x.clone(); - while k < m && iter < max_iter { - // f(z) = z^2 + c mod n (implemented via multiplication + mod, not modpow). - y = (&y * &y + &c) % n; - let diff = if &x > &y { &x - &y } else { &y - &x }; + let mut batch_iter = 0; + let x_saved = x_state.clone(); + while batch_iter < batch_size && iter < max_iter { + // f(z) = z^2 + c mod composite. + y_state = (&y_state * &y_state + &constant) % composite; + let diff = if x_state > y_state { + &x_state - &y_state + } else { + &y_state - &x_state + }; if !diff.is_zero() { - q = (q * diff) % n; + product = (product * diff) % composite; } - k += 1; + batch_iter += 1; iter += 1; } - g = gcd_biguint(&q, n); - x = x_saved; + current_gcd = gcd_biguint(&product, composite); + x_state = x_saved; - if g == one { - // Update x to advance the sequence. - x = y.clone(); + if current_gcd == one { + // Update x_state to advance the sequence. + x_state.clone_from(&y_state); } } - if g == one { + if current_gcd == one { continue; } - if &g == n { + if ¤t_gcd == composite { // Fallback: step-by-step gcd. - let mut z = x.clone(); + let mut z_state = x_state.clone(); loop { - z = (&z * &z + &c) % n; - let diff = if &z > &y { &z - &y } else { &y - &z }; - g = gcd_biguint(&diff, n); - if g > one || z == y { + z_state = (&z_state * &z_state + &constant) % composite; + let diff = if z_state > y_state { + &z_state - &y_state + } else { + &y_state - &z_state + }; + current_gcd = gcd_biguint(&diff, composite); + if current_gcd > one || z_state == y_state { break; } } } - if g > one && &g < n { - return Some(g); + if current_gcd > one && ¤t_gcd < composite { + return Some(current_gcd); } } None } -//// Recursively factor n and append factors (primes or unfactored composites) to `factors`. -//// Returns true if full factorization succeeded, false otherwise. -fn gcd_biguint(a: &BigUint, b: &BigUint) -> BigUint { +/// Recursively factor n and append factors (primes or unfactored composites) to `factors`. +/// Returns true if full factorization succeeded, false otherwise. +fn gcd_biguint(lhs: &BigUint, rhs: &BigUint) -> BigUint { // Standard Euclidean algorithm using owned BigUint values to avoid lifetime issues. - let mut x = a.clone(); - let mut y = b.clone(); - while !y.is_zero() { - let r = &x % &y; - x = y; - y = r; + let mut dividend = lhs.clone(); + let mut divisor = rhs.clone(); + while !divisor.is_zero() { + let remainder = ÷nd % &divisor; + dividend = divisor; + divisor = remainder; } - x + dividend } fn factor_biguint_recursive(n: &BigUint, factors: &mut Vec) -> bool { @@ -427,7 +453,7 @@ fn factor_biguint_recursive(n: &BigUint, factors: &mut Vec) -> bool { // If n is small enough, use num_prime's factorize128 for speed. if n.bits() <= 128 { - if let Ok(x128) = n.to_string().parse::() { + if let Some(x128) = n.to_u128() { let pf = num_prime::nt_funcs::factorize128(x128); if !pf.is_empty() { for (p, e) in pf { From 0b50bcc62e06e0b9687ff57df213e33cfe87d1f1 Mon Sep 17 00:00:00 2001 From: mattsu Date: Wed, 18 Feb 2026 16:55:08 +0900 Subject: [PATCH 3/3] chore: keep CI and lint settings aligned with factor changes --- .github/workflows/freebsd.yml | 2 +- .vscode/cspell.dictionaries/jargon.wordlist.txt | 2 ++ Cargo.toml | 2 ++ deny.toml | 1 + 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index f963cf3ef13..49750c3bd88 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -41,7 +41,7 @@ jobs: sync: rsync copyback: false # We need jq and GNU coreutils to run show-utils.sh and bash to use inline shell string replacement - prepare: pkg install -y curl sudo jq coreutils bash + prepare: pkg install -y curl gmake sudo jq coreutils bash run: | ## Prepare, build, and test # implementation modelled after ref: diff --git a/.vscode/cspell.dictionaries/jargon.wordlist.txt b/.vscode/cspell.dictionaries/jargon.wordlist.txt index 2f26340acdc..4c436c5a7b7 100644 --- a/.vscode/cspell.dictionaries/jargon.wordlist.txt +++ b/.vscode/cspell.dictionaries/jargon.wordlist.txt @@ -188,6 +188,8 @@ wordlists xattrs xpass +jemalloc +jemallocator # * abbreviations AMPM ampm diff --git a/Cargo.toml b/Cargo.toml index 334cfe5ae2b..8415214957b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -344,6 +344,8 @@ icu_locale = "2.0.0" icu_provider = "2.0.0" indicatif = "0.18.0" itertools = "0.14.0" +jemalloc-ctl = "0.5" +jemallocator = "0.5" itoa = "1.0.15" jiff = "0.2.18" jiff-icu = "0.2.2" diff --git a/deny.toml b/deny.toml index abc0acbd25c..23b8f6f84f0 100644 --- a/deny.toml +++ b/deny.toml @@ -10,6 +10,7 @@ version = 2 yanked = "warn" ignore = [ #"RUSTSEC-0000-0000", + "RUSTSEC-2024-0436", # paste: unmaintained (via jemalloc-ctl in uu_factor bench) ] # This section is considered when running `cargo deny check licenses`