🦀 Functional Rust

830: Euler's Totient Function φ(n)

Difficulty: 4 Level: Advanced Count integers in [1, n] coprime to n — and compute it for all values up to N in O(N log log N) with a sieve.

The Problem This Solves

Euler's totient function φ(n) counts how many integers in [1, n] share no common factor with n. For a prime p, φ(p) = p-1 (all integers below p are coprime to it). For n = p^k, φ(n) = p^(k-1)(p-1). For general n, φ is multiplicative: φ(mn) = φ(m)φ(n) when gcd(m,n) = 1. φ(n) appears everywhere in number theory and cryptography. RSA key generation relies on φ(n) where n = p·q — specifically, the fact that a^φ(n) ≡ 1 (mod n) for all a coprime to n (Euler's theorem). Diffie-Hellman and ElGamal work in groups whose order is φ(p) = p-1 for prime p. Primitive roots and discrete logarithms are defined relative to φ. This example implements both a single-value O(√n) computation and an O(N log log N) sieve for computing φ(k) for all k up to N simultaneously.

The Intuition

Single value: factor n into prime powers using trial division. Apply the formula: φ(n) = n · ∏(1 - 1/p) for each distinct prime factor p of n. In integer arithmetic: multiply by (p-1) and divide by p for each prime factor. Sieve: analogous to the Sieve of Eratosthenes. Start with `phi[i] = i`. For each prime p (those with `phi[p] == p` still, unmarked), apply the totient factor to all multiples: `phi[k] = phi[k] / p * (p - 1)`. This is O(N log log N), same as the prime sieve. The sieve approach is essential when you need φ for many values — computing each independently would cost O(N √N).

How It Works in Rust

// Single value: O(√n)
fn euler_totient(mut n: u64) -> u64 {
 let mut result = n;
 let mut p = 2u64;
 while p * p <= n {
     if n % p == 0 {
         // p is a prime factor — apply (1 - 1/p)
         while n % p == 0 { n /= p; }
         result -= result / p; // result = result * (p-1) / p
     }
     p += 1;
 }
 if n > 1 {
     // Remaining factor is prime
     result -= result / n;
 }
 result
}

// Sieve for all values up to limit: O(N log log N)
fn totient_sieve(limit: usize) -> Vec<u64> {
 let mut phi: Vec<u64> = (0..=limit as u64).collect(); // phi[i] = i initially

 for p in 2..=limit {
     if phi[p] as usize == p {
         // p is prime (not yet modified by any smaller prime)
         let mut k = p;
         while k <= limit {
             phi[k] -= phi[k] / p as u64; // phi[k] *= (p-1)/p
             k += p;
         }
     }
 }
 phi
}
The key insight in `euler_totient`: `result -= result / p` implements `result *= (p-1)/p` in integer arithmetic without fractions — divide first, then subtract, to maintain exactness. This works because `p | result` at that point (since `p | n` originally). In the sieve: `phi[p] as usize == p` detects primes exactly like the Sieve of Eratosthenes detects composites — but by checking if the initial value was preserved. For RSA: `phi[n] = (p-1) * (q-1)` directly, but understanding the general formula matters for understanding why RSA works with composite moduli.

What This Unlocks

Key Differences

ConceptOCamlRust
Integer division exactness`/` truncates (same as Rust)`/` truncates — `result -= result / p` works identically
Prime detection in sieve`phi.(p) = p` comparison`phi[p] as usize == p` — same logic, explicit cast
Mutable sieve array`Array.make (n+1) 0` with loop`(0..=limit as u64).collect()` initializes with identity values
Trial division loop`while p p <= n` with `ref n``while p p <= n` with `mut n` — shadowing the parameter
u64 vs int`Int64` or native int (63-bit)`u64` — explicit width, no overflow surprises
/// Euler's Totient Function φ(n).
///
/// φ(n) = count of k in [1,n] with gcd(k,n) = 1.
/// Formula: φ(n) = n × ∏_{p|n} (1 - 1/p).

/// Single value: O(√n).
fn totient(mut n: u64) -> u64 {
    let mut result = n;
    let mut p = 2u64;
    while p * p <= n {
        if n % p == 0 {
            while n % p == 0 {
                n /= p;
            }
            result -= result / p; // result *= (p-1)/p
        }
        p += 1;
    }
    if n > 1 {
        result -= result / n; // remaining prime factor
    }
    result
}

/// Totient sieve: compute φ(i) for all i in [0, limit]. O(n log log n).
fn totient_sieve(limit: usize) -> Vec<u64> {
    let mut phi: Vec<u64> = (0..=limit as u64).collect();
    for i in 2..=limit {
        if phi[i] == i as u64 {
            // i is prime — update all multiples
            let mut j = i;
            while j <= limit {
                phi[j] -= phi[j] / i as u64;
                j += i;
            }
        }
    }
    phi
}

/// Property: sum of φ(d) over all divisors d of n equals n.
fn sum_divisor_totients(n: u64) -> u64 {
    let phi = totient_sieve(n as usize);
    (1..=n).filter(|&d| n % d == 0).map(|d| phi[d as usize]).sum()
}

fn main() {
    let values = [1u64, 2, 6, 9, 10, 12, 36, 100];
    for &n in &values {
        println!("φ({n}) = {}", totient(n));
    }

    println!("\nTotient sieve [1..12]:");
    let phi = totient_sieve(12);
    for (i, &v) in phi.iter().enumerate().skip(1) {
        println!("  φ({i}) = {v}");
    }

    println!("\nProperty ∑_{{d|n}} φ(d) = n:");
    for n in [6u64, 12, 30] {
        let s = sum_divisor_totients(n);
        println!("  n={n}: sum={s}, match={}", s == n);
    }

    // φ(p) = p-1 for primes
    println!("\nPrime totients:");
    for p in [2u64, 3, 5, 7, 11, 13] {
        println!("  φ({p}) = {} (= p-1 = {})", totient(p), p - 1);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_totient_known() {
        assert_eq!(totient(1), 1);
        assert_eq!(totient(2), 1);
        assert_eq!(totient(6), 2);   // coprime: 1, 5
        assert_eq!(totient(9), 6);   // coprime: 1,2,4,5,7,8
        assert_eq!(totient(10), 4);  // coprime: 1,3,7,9
        assert_eq!(totient(12), 4);  // coprime: 1,5,7,11
    }

    #[test]
    fn test_prime_totient() {
        for p in [2u64, 3, 5, 7, 11, 13, 97] {
            assert_eq!(totient(p), p - 1, "φ({p}) should be p-1");
        }
    }

    #[test]
    fn test_prime_power() {
        // φ(p^k) = p^k - p^(k-1) = p^(k-1) * (p-1)
        assert_eq!(totient(8), 4);   // φ(2³) = 4
        assert_eq!(totient(9), 6);   // φ(3²) = 6
        assert_eq!(totient(25), 20); // φ(5²) = 20
    }

    #[test]
    fn test_sieve_matches_direct() {
        let phi = totient_sieve(100);
        for n in 1..=100u64 {
            assert_eq!(phi[n as usize], totient(n),
                "sieve[{n}] = {} but totient({n}) = {}", phi[n as usize], totient(n));
        }
    }

    #[test]
    fn test_divisor_sum_property() {
        for n in [6u64, 12, 30, 100] {
            assert_eq!(sum_divisor_totients(n), n, "sum_divisor_totients({n}) != {n}");
        }
    }

    #[test]
    fn test_multiplicative() {
        // φ is multiplicative: φ(mn) = φ(m)φ(n) when gcd(m,n)=1
        assert_eq!(totient(3 * 5), totient(3) * totient(5));
        assert_eq!(totient(4 * 9), totient(4) * totient(9));
    }
}
(* Euler's Totient Function in OCaml *)

(* Single value: φ(n) = n × ∏(1 - 1/p) over distinct prime factors *)
let totient (n : int) : int =
  let result = ref n in
  let n' = ref n in
  let p = ref 2 in
  while !p * !p <= !n' do
    if !n' mod !p = 0 then begin
      (* p is a prime factor — multiply result by (p-1)/p *)
      while !n' mod !p = 0 do n' := !n' / !p done;
      result := !result - !result / !p
    end;
    incr p
  done;
  (* Remaining prime factor > sqrt(n) *)
  if !n' > 1 then
    result := !result - !result / !n';
  !result

(* Totient sieve: compute φ(i) for all i in [0, n] in O(n log log n) *)
let totient_sieve (n : int) : int array =
  let phi = Array.init (n + 1) (fun i -> i) in
  for i = 2 to n do
    (* phi[i] = i means i hasn't been processed as prime factor yet *)
    if phi.(i) = i then begin
      (* i is prime *)
      let j = ref i in
      while !j <= n do
        phi.(!j) <- phi.(!j) - phi.(!j) / i;
        j := !j + i
      done
    end
  done;
  phi

(* Sum of totients: ∑φ(k) for k=1..n = (n*(n+1)/2 + 1)/... — test property *)
(* Property: ∑_{d|n} φ(d) = n *)
let sum_divisor_totients (n : int) : int =
  let sieve = totient_sieve n in
  (* Divisors of n *)
  let total = ref 0 in
  for d = 1 to n do
    if n mod d = 0 then total := !total + sieve.(d)
  done;
  !total

let () =
  let values = [1; 2; 6; 9; 10; 12; 36; 100] in
  List.iter (fun n ->
    Printf.printf "φ(%d) = %d\n" n (totient n)
  ) values;

  Printf.printf "\nTotient sieve up to 12:\n";
  let phi = totient_sieve 12 in
  Array.iteri (fun i v -> if i > 0 then Printf.printf "  φ(%d) = %d\n" i v) phi;

  Printf.printf "\nProperty ∑_{d|n} φ(d) = n:\n";
  List.iter (fun n ->
    Printf.printf "  n=%d: sum = %d (should be %d)\n" n (sum_divisor_totients n) n
  ) [6; 12; 30]