factor.rs

10.1 kB · rust · 345 lines

1/// Returns the greatest common divisor of two numbers by the Euclidean algorithm, zero for two zeroes.2pub fn gcd(a: usize, b: usize) -> usize {3    let (mut a, mut b) = (a, b);4    while b != 0 {5        (a, b) = (b, a % b);6    }7    a8}910/// Returns the least common multiple of two numbers, zero when either side is zero.11///12/// Wraps silently once the multiple passes the pointer width.13pub fn lcm(a: usize, b: usize) -> usize {14    if a == 0 || b == 0 {15        return 0;16    }17    a / gcd(a, b) * b18}1920/// Returns whether two numbers share no divisor above one.21pub fn coprime(a: usize, b: usize) -> bool {22    gcd(a, b) == 123}2425fn peel(prime: u64, rest: &mut u64, out: &mut Vec<(u64, u32)>) {26    let mut power = 0;27    while rest.is_multiple_of(prime) {28        *rest /= prime;29        power += 1;30    }31    if power > 0 {32        out.push((prime, power));33    }34}3536/// Returns the prime and exponent pairs of a wide number in ascending primes, by trial division on the six-step wheel.37///38/// ```39/// assert_eq!(mrlynum::factor::factorize_wide(999_999_999_989), vec![(999_999_999_989, 1)]);40/// ```41pub fn factorize_wide(number: u64) -> Vec<(u64, u32)> {42    let mut out = Vec::new();43    let mut rest = number;44    if rest < 2 {45        return out;46    }47    peel(2, &mut rest, &mut out);48    peel(3, &mut rest, &mut out);49    let mut step = 5;50    while step <= rest / step {51        peel(step, &mut rest, &mut out);52        peel(step + 2, &mut rest, &mut out);53        step += 6;54    }55    if rest > 1 {56        out.push((rest, 1));57    }58    out59}6061/// Returns the prime and exponent pairs of the number in ascending primes, by trial division on the six-step wheel.62///63/// ```64/// assert_eq!(mrlynum::factor::factorize(240), vec![(2, 4), (3, 1), (5, 1)]);65/// ```66pub fn factorize(number: usize) -> Vec<(usize, u32)> {67    factorize_wide(number as u64)68        .into_iter()69        .map(|(prime, power)| (prime as usize, power))70        .collect()71}7273/// Builds every divisor of the number from its factorization, ascending, empty for zero.74pub fn divisors(number: usize) -> Vec<usize> {75    if number == 0 {76        return Vec::new();77    }78    let mut out = vec![1];79    for (prime, power) in factorize(number) {80        let mut next = Vec::with_capacity(out.len() * (power as usize + 1));81        for &divisor in &out {82            let mut value = divisor;83            next.push(value);84            for _ in 0..power {85                value *= prime;86                next.push(value);87            }88        }89        out = next;90    }91    out.sort_unstable();92    out93}9495/// Returns the sum of every divisor of the number raised to the power, so power zero counts them.96///97/// Stays exact for a power of two or below, above which the sum can wrap past a hundred and twenty-eight bits.98pub fn sigma(number: usize, power: u32) -> u128 {99    divisors(number)100        .iter()101        .map(|&divisor| (divisor as u128).pow(power))102        .sum()103}104105/// Returns the sum of the proper divisors of the number, its divisor sum less itself, zero for zero and for one.106pub fn aliquot(number: usize) -> usize {107    (sigma(number, 1) - number as u128) as usize108}109110/// Returns the divisor sum with a periodic rhythm painted on each divisor, zero for zero and for an empty rhythm.111///112/// ```113/// assert_eq!(mrlynum::factor::twisted(5, &[0, 1, 0, -1]), 2);114/// ```115pub fn twisted(number: usize, rhythm: &[i8]) -> i64 {116    if rhythm.is_empty() {117        return 0;118    }119    divisors(number)120        .iter()121        .map(|&divisor| i64::from(rhythm[divisor % rhythm.len()]))122        .sum()123}124125/// Returns the Mobius value of the number: zero for zero or a squared factor, else minus one to the count of primes.126pub fn mobius(number: usize) -> i8 {127    if number == 0 {128        return 0;129    }130    let mut out = 1;131    for (_, power) in factorize(number) {132        if power > 1 {133            return 0;134        }135        out = -out;136    }137    out138}139140/// Sieves the Mobius values of zero through the limit in one pass.141pub fn mobius_sieve(limit: usize) -> Vec<i8> {142    let mut out = vec![1i8; limit + 1];143    let mut composite = vec![false; limit + 1];144    out[0] = 0;145    for p in 2..=limit {146        if composite[p] {147            continue;148        }149        for m in (p..=limit).step_by(p) {150            composite[m] = m != p;151            out[m] = -out[m];152        }153        if p <= limit / p {154            for m in (p * p..=limit).step_by(p * p) {155                out[m] = 0;156            }157        }158    }159    out160}161162/// Returns the Euler totient of the number from its factorization, zero for zero and one for one.163pub fn totient(number: usize) -> usize {164    if number == 0 {165        return 0;166    }167    let mut out = number;168    for (prime, _) in factorize(number) {169        out -= out / prime;170    }171    out172}173174/// Returns the radical of the number, the product of its distinct primes, zero for zero and one for one.175pub fn radical(number: usize) -> usize {176    if number == 0 {177        return 0;178    }179    factorize(number).iter().map(|&(prime, _)| prime).product()180}181182/// Returns whether no prime squares into the number, true for one and false for zero.183pub fn squarefree(number: usize) -> bool {184    number != 0 && factorize(number).iter().all(|&(_, power)| power < 2)185}186187#[cfg(test)]188mod tests {189    use super::*;190    use crate::lattice::totients;191    use crate::prime::rectangles;192    use crate::series::{chi4, chi8};193194    fn represented(m: usize, weight: usize) -> usize {195        let mut count = 0;196        for a in -20i64..=20 {197            for b in -20i64..=20 {198                if a * a + weight as i64 * b * b == m as i64 {199                    count += 1;200                }201            }202        }203        count204    }205206    #[test]207    fn factorize_pins_the_hand_checked_shapes() {208        assert_eq!(factorize(240), vec![(2, 4), (3, 1), (5, 1)]);209        assert!(factorize(1).is_empty());210        assert!(factorize(0).is_empty());211        assert_eq!(factorize(97), vec![(97, 1)]);212        assert_eq!(factorize(243), vec![(3, 5)]);213        assert_eq!(factorize(1_000_003), vec![(1_000_003, 1)]);214    }215216    #[test]217    fn a_factorization_multiplies_back_to_its_number() {218        for number in 2..=5_000usize {219            let parts = factorize(number);220            let rebuilt: usize = parts.iter().map(|&(p, e)| p.pow(e)).product();221            assert_eq!(rebuilt, number, "{number}");222            for pair in parts.windows(2) {223                assert!(pair[0].0 < pair[1].0, "{number}");224            }225        }226    }227228    #[test]229    fn the_divisor_count_is_sigma_zero_and_twice_the_rectangles_less_a_square() {230        assert!(divisors(0).is_empty());231        assert_eq!(divisors(1), vec![1]);232        assert_eq!(divisors(28), vec![1, 2, 4, 7, 14, 28]);233        for number in 1..=2_000usize {234            let count = divisors(number).len() as u128;235            assert_eq!(count, sigma(number, 0), "{number}");236            let root = number.isqrt();237            let square = u128::from(root * root == number);238            assert_eq!(239                count,240                2 * rectangles(number).len() as u128 - square,241                "{number}"242            );243        }244    }245246    #[test]247    fn sigma_pins_the_hand_checked_sums() {248        assert_eq!(sigma(6, 1), 12);249        assert_eq!(sigma(240, 0), 20);250        assert_eq!(sigma(1, 3), 1);251        assert_eq!(sigma(0, 1), 0);252    }253254    #[test]255    fn the_aliquot_sum_is_sigma_one_less_the_number() {256        assert_eq!(aliquot(0), 0);257        assert_eq!(aliquot(1), 0);258        assert_eq!(aliquot(6), 6);259        assert_eq!(aliquot(28), 28);260        assert_eq!(aliquot(12), 16);261        for number in 1..=2_000usize {262            assert_eq!(263                sigma(number, 1),264                aliquot(number) as u128 + number as u128,265                "{number}"266            );267        }268    }269270    #[test]271    fn the_mod_four_twist_counts_the_sums_of_two_squares() {272        let rhythm: Vec<i8> = (0..4).map(chi4).collect();273        for m in 1..=300usize {274            assert_eq!(represented(m, 1) as i64, 4 * twisted(m, &rhythm), "{m}");275        }276    }277278    #[test]279    fn the_mod_eight_twist_counts_a_square_plus_two_squares() {280        let rhythm: Vec<i8> = (0..8).map(chi8).collect();281        for m in 1..=300usize {282            assert_eq!(represented(m, 2) as i64, 2 * twisted(m, &rhythm), "{m}");283        }284    }285286    #[test]287    fn the_flat_twist_is_the_divisor_count() {288        assert_eq!(twisted(0, &[1]), 0);289        assert_eq!(twisted(12, &[]), 0);290        for number in 1..=1_000usize {291            assert_eq!(twisted(number, &[1]) as u128, sigma(number, 0), "{number}");292        }293    }294295    #[test]296    fn mobius_starts_the_known_sequence() {297        let start: Vec<i8> = (1..=10).map(mobius).collect();298        assert_eq!(start, vec![1, -1, -1, 0, -1, 1, -1, 0, 0, 1]);299        assert_eq!(mobius(0), 0);300        assert_eq!(mobius(240), 0);301        assert_eq!(mobius(30), -1);302    }303304    #[test]305    fn the_mobius_sieve_agrees_with_the_single_value() {306        let sieved = mobius_sieve(10_000);307        for (number, &value) in sieved.iter().enumerate() {308            assert_eq!(value, mobius(number), "{number}");309        }310    }311312    #[test]313    fn totient_agrees_with_the_lattice_sieve() {314        let sieved = totients(5_000);315        for (number, &value) in sieved.iter().enumerate() {316            assert_eq!(totient(number) as u64, value, "{number}");317        }318    }319320    #[test]321    fn the_radical_keeps_one_of_each_prime_and_equals_a_squarefree_number() {322        assert_eq!(radical(240), 30);323        assert_eq!(radical(0), 0);324        assert_eq!(radical(1), 1);325        assert!(squarefree(30));326        assert!(!squarefree(240));327        assert!(squarefree(1));328        assert!(!squarefree(0));329        for number in 1..=2_000usize {330            assert_eq!(squarefree(number), radical(number) == number, "{number}");331        }332    }333334    #[test]335    fn gcd_times_lcm_is_the_product_and_a_gcd_of_one_means_coprime() {336        assert_eq!(gcd(0, 0), 0);337        assert_eq!(lcm(0, 7), 0);338        for a in 1..=60usize {339            for b in 1..=60usize {340                assert_eq!(gcd(a, b) * lcm(a, b), a * b, "{a} {b}");341                assert_eq!(coprime(a, b), gcd(a, b) == 1, "{a} {b}");342            }343        }344    }345}