Skip to main content

adele_ring/
primes.rs

1//! Prime utilities, factorization, and channel (base) selection.
2//!
3//! All functions here are pure and have no dependency on the rest of the crate.
4//!
5//! # The "natural base" concept
6//!
7//! The *natural base* of a computation is the LCM of the radicals of all
8//! denominators involved. This is the minimal base in which every fraction in
9//! the computation terminates exactly. For example,
10//! `natural_base(&[6, 10, 15]) == 30`, because `radical(6)=6`, `radical(10)=10`,
11//! `radical(15)=15` and `lcm(6,10,15)=30`.
12
13/// Sieve of Eratosthenes: all primes `<= n`.
14pub fn primes_up_to(n: usize) -> Vec<u64> {
15    if n < 2 {
16        return Vec::new();
17    }
18    let mut is_prime = vec![true; n + 1];
19    is_prime[0] = false;
20    is_prime[1] = false;
21    let mut i = 2usize;
22    while i * i <= n {
23        if is_prime[i] {
24            let mut j = i * i;
25            while j <= n {
26                is_prime[j] = false;
27                j += i;
28            }
29        }
30        i += 1;
31    }
32    (2..=n)
33        .filter(|&i| is_prime[i])
34        .map(|i| i as u64)
35        .collect()
36}
37
38/// The first `n` primes.
39///
40/// Uses the prime-counting upper bound `n * (ln n + ln ln n)` (scaled with a
41/// safety factor) to pick a sieve limit, then expands if necessary.
42pub fn first_n_primes(n: usize) -> Vec<u64> {
43    if n == 0 {
44        return Vec::new();
45    }
46    if n < 6 {
47        // ln ln n is undefined / unhelpful for tiny n; use a fixed small limit.
48        return primes_up_to(15).into_iter().take(n).collect();
49    }
50    let nf = n as f64;
51    let mut limit = (nf * (nf.ln() + nf.ln().ln()) * 1.3).ceil() as usize;
52    loop {
53        let primes = primes_up_to(limit);
54        if primes.len() >= n {
55            return primes.into_iter().take(n).collect();
56        }
57        limit *= 2;
58    }
59}
60
61/// Factorize `n` into `(prime, exponent)` pairs in ascending prime order.
62/// `factorize(0)` and `factorize(1)` return an empty vector.
63pub fn factorize(mut n: u64) -> Vec<(u64, u32)> {
64    let mut factors = Vec::new();
65    if n < 2 {
66        return factors;
67    }
68    let mut d = 2u64;
69    while d * d <= n {
70        if n % d == 0 {
71            let mut exp = 0u32;
72            while n % d == 0 {
73                n /= d;
74                exp += 1;
75            }
76            factors.push((d, exp));
77        }
78        d += if d == 2 { 1 } else { 2 };
79    }
80    if n > 1 {
81        factors.push((n, 1));
82    }
83    factors
84}
85
86/// Product of the *distinct* prime factors of `n` (the squarefree kernel).
87/// `radical(12) == 6`, `radical(1) == 1`, `radical(0) == 0`.
88pub fn radical(n: u64) -> u64 {
89    if n == 0 {
90        return 0;
91    }
92    factorize(n)
93        .into_iter()
94        .map(|(p, _)| p)
95        .product::<u64>()
96        .max(1)
97}
98
99/// The distinct prime factors of `n`, ascending.
100pub fn distinct_primes(n: u64) -> Vec<u64> {
101    factorize(n).into_iter().map(|(p, _)| p).collect()
102}
103
104/// Euclidean greatest common divisor.
105pub fn gcd(mut a: u64, mut b: u64) -> u64 {
106    while b != 0 {
107        let t = b;
108        b = a % b;
109        a = t;
110    }
111    a
112}
113
114/// Least common multiple. `lcm(0, x) == 0`.
115pub fn lcm(a: u64, b: u64) -> u64 {
116    if a == 0 || b == 0 {
117        return 0;
118    }
119    a / gcd(a, b) * b
120}
121
122/// Extended Euclidean algorithm.
123///
124/// Returns `(g, x, y)` such that `a*x + b*y == g == gcd(a, b)`.
125pub fn extended_gcd(a: i64, b: i64) -> (i64, i64, i64) {
126    if b == 0 {
127        // gcd(a,0) = |a|, keep sign info in the coefficient.
128        return (a.abs(), if a < 0 { -1 } else { 1 }, 0);
129    }
130    let (g, x, y) = extended_gcd(b, a % b);
131    (g, y, x - (a / b) * y)
132}
133
134/// Modular inverse of `a` modulo `m`, or `None` when `gcd(a, m) != 1`.
135pub fn mod_inverse(a: u64, m: u64) -> Option<u64> {
136    if m == 0 {
137        return None;
138    }
139    if m == 1 {
140        return Some(0);
141    }
142    let (g, x, _) = extended_gcd((a % m) as i64, m as i64);
143    if g != 1 {
144        return None;
145    }
146    let m_i = m as i64;
147    Some((((x % m_i) + m_i) % m_i) as u64)
148}
149
150/// Determine how `1/n` behaves when written in the given `base`.
151///
152/// Returns:
153/// - `Some(0)`  — the expansion terminates (every prime factor of `n` divides `base`),
154/// - `Some(k)`  — the expansion repeats with period `k`,
155/// - `None`     — degenerate input (`n == 0` or `base < 2`).
156///
157/// The period of the repeating part is the multiplicative order of `base`
158/// modulo the part of `n` coprime to `base`.
159pub fn termination_period(n: u64, base: u64) -> Option<u64> {
160    if n == 0 || base < 2 {
161        return None;
162    }
163    // Strip the factors shared with `base` — those produce the terminating prefix.
164    let mut coprime_part = n;
165    loop {
166        let g = gcd(coprime_part, base);
167        if g == 1 {
168            break;
169        }
170        coprime_part /= g;
171    }
172    if coprime_part == 1 {
173        return Some(0); // terminates
174    }
175    // Period = multiplicative order of `base` modulo `coprime_part`.
176    let b = base % coprime_part;
177    let mut order = 1u64;
178    let mut acc = b % coprime_part;
179    while acc != 1 {
180        acc = (acc * b) % coprime_part;
181        order += 1;
182    }
183    Some(order)
184}
185
186/// Minimal exact base for a set of denominators: the LCM of their radicals.
187///
188/// `natural_base(&[6, 10, 15]) == 30`.
189pub fn natural_base(denominators: &[u64]) -> u64 {
190    denominators
191        .iter()
192        .filter(|&&d| d != 0)
193        .map(|&d| radical(d))
194        .fold(1u64, lcm)
195        .max(1)
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201
202    #[test]
203    fn sieve_basic() {
204        assert_eq!(primes_up_to(11), vec![2, 3, 5, 7, 11]);
205        assert!(primes_up_to(1).is_empty());
206    }
207
208    #[test]
209    fn first_n() {
210        assert_eq!(first_n_primes(5), vec![2, 3, 5, 7, 11]);
211        let p = first_n_primes(32);
212        assert_eq!(p.len(), 32);
213        assert_eq!(*p.last().unwrap(), 131); // 32nd prime is 131
214    }
215
216    #[test]
217    fn factor() {
218        assert_eq!(factorize(12), vec![(2, 2), (3, 1)]);
219        assert_eq!(factorize(13), vec![(13, 1)]);
220        assert!(factorize(1).is_empty());
221    }
222
223    #[test]
224    fn radicals() {
225        assert_eq!(radical(12), 6);
226        assert_eq!(radical(1), 1);
227        assert_eq!(radical(30), 30);
228    }
229
230    #[test]
231    fn gcd_lcm() {
232        assert_eq!(gcd(12, 18), 6);
233        assert_eq!(lcm(4, 6), 12);
234    }
235
236    #[test]
237    fn egcd_and_inverse() {
238        let (g, x, y) = extended_gcd(240, 46);
239        assert_eq!(g, 2);
240        assert_eq!(240 * x + 46 * y, 2);
241        assert_eq!(mod_inverse(3, 11), Some(4)); // 3*4=12≡1 mod 11
242        assert_eq!(mod_inverse(2, 4), None);
243    }
244
245    #[test]
246    fn termination() {
247        assert_eq!(termination_period(8, 10), Some(0)); // 1/8 terminates in base 10
248        assert_eq!(termination_period(3, 10), Some(1)); // 1/3 = 0.333..., period 1
249        assert_eq!(termination_period(7, 10), Some(6)); // 1/7 period 6
250        assert_eq!(termination_period(6, 10), Some(1)); // 1/6 = 0.1666...
251    }
252
253    #[test]
254    fn nat_base() {
255        assert_eq!(natural_base(&[6, 10, 15]), 30);
256        assert_eq!(natural_base(&[12]), 6);
257    }
258}