Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance improvements for factor #1525

Merged
merged 15 commits into from
May 24, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
rivy marked this conversation as resolved.
Show resolved Hide resolved

{
// 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