Skip to content

Commit

Permalink
Merge pull request #1525 from nbraud/factor/faster
Browse files Browse the repository at this point in the history
Performance improvements for `factor`
  • Loading branch information
rivy committed May 24, 2020
2 parents 75b7f76 + 4c3682a commit 09abcf8
Show file tree
Hide file tree
Showing 6 changed files with 331 additions and 201 deletions.
2 changes: 1 addition & 1 deletion src/uu/factor/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use std::path::Path;
use std::u64::MAX as MAX_U64;

#[cfg(test)]
use numeric::is_prime;
use miller_rabin::is_prime;

#[cfg(test)]
#[path = "src/numeric.rs"]
Expand Down
162 changes: 59 additions & 103 deletions src/uu/factor/src/factor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,139 +18,95 @@ extern crate rand;
#[macro_use]
extern crate uucore;

use numeric::*;
use rand::distributions::{Distribution, Uniform};
use rand::rngs::SmallRng;
use rand::{thread_rng, SeedableRng};
use std::cmp::{max, min};
use std::collections::HashMap;
use std::fmt;
use std::io::{stdin, BufRead};
use std::mem::swap;
use std::num::Wrapping;
use std::ops;

mod miller_rabin;
mod numeric;

include!(concat!(env!("OUT_DIR"), "/prime_table.rs"));
mod rho;
mod table;

static SYNTAX: &str = "[OPTION] [NUMBER]...";
static SUMMARY: &str = "Print the prime factors of the given number(s).
If none are specified, read from standard input.";
static LONG_HELP: &str = "";

fn rho_pollard_pseudorandom_function(x: u64, a: u64, b: u64, num: u64) -> u64 {
if num < 1 << 63 {
(sm_mul(a, sm_mul(x, x, num), num) + b) % num
} else {
big_add(big_mul(a, big_mul(x, x, num), num), b, num)
}
struct Factors {
f: HashMap<u64, u8>,
}

fn gcd(mut a: u64, mut b: u64) -> u64 {
while b > 0 {
a %= b;
swap(&mut a, &mut b);
impl Factors {
fn new() -> Factors {
Factors { f: HashMap::new() }
}

fn add(&mut self, prime: u64, exp: u8) {
assert!(exp > 0);
let n = *self.f.get(&prime).unwrap_or(&0);
self.f.insert(prime, exp + n);
}

fn push(&mut self, prime: u64) {
self.add(prime, 1)
}
a
}

fn rho_pollard_find_divisor(num: u64) -> u64 {
#![allow(clippy::many_single_char_names)]
let range = Uniform::new(1, num);
let mut rng = SmallRng::from_rng(&mut thread_rng()).unwrap();
let mut x = range.sample(&mut rng);
let mut y = x;
let mut a = range.sample(&mut rng);
let mut b = range.sample(&mut rng);

loop {
x = rho_pollard_pseudorandom_function(x, a, b, num);
y = rho_pollard_pseudorandom_function(y, a, b, num);
y = rho_pollard_pseudorandom_function(y, a, b, num);
let d = gcd(num, max(x, y) - min(x, y));
if d == num {
// Failure, retry with different function
x = range.sample(&mut rng);
y = x;
a = range.sample(&mut rng);
b = range.sample(&mut rng);
} else if d > 1 {
return d;
impl ops::MulAssign<Factors> for Factors {
fn mul_assign(&mut self, other: Factors) {
for (prime, exp) in &other.f {
self.add(*prime, *exp);
}
}
}

fn rho_pollard_factor(num: u64, factors: &mut Vec<u64>) {
if is_prime(num) {
factors.push(num);
return;
impl fmt::Display for Factors {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Use a representation with efficient in-order iteration
let mut primes: Vec<&u64> = self.f.keys().collect();
primes.sort();

for p in primes {
for _ in 0..self.f[&p] {
write!(f, " {}", p)?
}
}

Ok(())
}
let divisor = rho_pollard_find_divisor(num);
rho_pollard_factor(divisor, factors);
rho_pollard_factor(num / divisor, factors);
}

fn table_division(mut num: u64, factors: &mut Vec<u64>) {
if num < 2 {
return;
}
while num % 2 == 0 {
num /= 2;
factors.push(2);
fn factor(mut n: u64) -> Factors {
let mut factors = Factors::new();

if n < 2 {
factors.push(n);
return factors;
}
if num == 1 {
return;

let z = n.trailing_zeros();
if z > 0 {
factors.add(2, z as u8);
n >>= z;
}
if is_prime(num) {
factors.push(num);
return;

if n == 1 {
return factors;
}
for &(prime, inv, ceil) in P_INVS_U64 {
if num == 1 {
break;
}

// inv = prime^-1 mod 2^64
// ceil = floor((2^64-1) / prime)
// if (num * inv) mod 2^64 <= ceil, then prime divides num
// See http://math.stackexchange.com/questions/1251327/
// for a nice explanation.
loop {
let Wrapping(x) = Wrapping(num) * Wrapping(inv); // x = num * inv mod 2^64
if x <= ceil {
num = x;
factors.push(prime);
if is_prime(num) {
factors.push(num);
return;
}
} else {
break;
}
}
let (f, n) = table::factor(n);
factors *= f;

if n >= table::NEXT_PRIME {
factors *= rho::factor(n);
}

// do we still have more factoring to do?
// Decide whether to use Pollard Rho or slow divisibility based on
// number's size:
//if num >= 1 << 63 {
// number is too big to use rho pollard without overflowing
//trial_division_slow(num, factors);
//} else if num > 1 {
// number is still greater than 1, but not so big that we have to worry
rho_pollard_factor(num, factors);
//}
factors
}

fn print_factors(num: u64) {
print!("{}:", num);

let mut factors = Vec::new();
// we always start with table division, and go from there
table_division(num, &mut factors);
factors.sort();

for fac in &factors {
print!(" {}", fac);
}
print!("{}:{}", num, factor(num));
println!();
}

Expand Down
88 changes: 88 additions & 0 deletions src/uu/factor/src/miller_rabin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
use crate::numeric::*;

// Small set of bases for the Miller-Rabin prime test, valid for all 64b integers;
// discovered by Jim Sinclair on 2011-04-20, see miller-rabin.appspot.com
#[allow(clippy::unreadable_literal)]
const BASIS: [u64; 7] = [2, 325, 9375, 28178, 450775, 9780504, 1795265022];

#[derive(Eq, PartialEq)]
pub(crate) enum Result {
Prime,
Pseudoprime,
Composite(u64),
}

impl Result {
pub(crate) fn is_prime(&self) -> bool {
*self == Result::Prime
}
}

// Deterministic Miller-Rabin primality-checking algorithm, adapted to extract
// (some) dividers; it will fail to factor strong pseudoprimes.
#[allow(clippy::many_single_char_names)]
pub(crate) fn test<A: Arithmetic>(n: u64) -> Result {
use self::Result::*;

if n < 2 {
return Pseudoprime;
}
if n % 2 == 0 {
return if n == 2 { Prime } else { Composite(2) };
}

// n-1 = r 2ⁱ
let i = (n - 1).trailing_zeros();
let r = (n - 1) >> i;

for a in BASIS.iter() {
let a = a % n;
if a == 0 {
break;
}

// x = a^r mod n
let mut x = A::pow(a, r, n);

{
// y = ((x²)²...)² i times = x ^ (2ⁱ) = a ^ (r 2ⁱ) = x ^ (n - 1)
let mut y = x;
for _ in 0..i {
y = A::mul(y, y, n)
}
if y != 1 {
return Pseudoprime;
};
}

if x == 1 || x == n - 1 {
break;
}

loop {
let y = A::mul(x, x, n);
if y == 1 {
return Composite(gcd(x - 1, n));
}
if y == n - 1 {
// This basis element is not a witness of `n` being composite.
// Keep looking.
break;
}
x = y;
}
}

Prime
}

// Used by build.rs' tests
#[allow(dead_code)]
pub(crate) fn is_prime(n: u64) -> bool {
if n < 1 << 63 {
test::<Small>(n)
} else {
test::<Big>(n)
}
.is_prime()
}
Loading

0 comments on commit 09abcf8

Please sign in to comment.