From fa1eab2d2657cd9979b72f6cc0eb186cec01accf Mon Sep 17 00:00:00 2001 From: saahil-mahato Date: Mon, 7 Oct 2024 04:49:10 +0545 Subject: [PATCH 1/6] feat: add fermats little theorem --- src/math/fermats_little_theorem.rs | 134 +++++++++++++++++++++++++++++ src/math/mod.rs | 2 + 2 files changed, 136 insertions(+) create mode 100644 src/math/fermats_little_theorem.rs diff --git a/src/math/fermats_little_theorem.rs b/src/math/fermats_little_theorem.rs new file mode 100644 index 00000000000..88e60f38457 --- /dev/null +++ b/src/math/fermats_little_theorem.rs @@ -0,0 +1,134 @@ +use rand::Rng; + +/// Performs modular exponentiation. +/// +/// This function computes `(base ^ exp) mod modulus` efficiently using the method +/// of exponentiation by squaring. +/// +/// # Parameters +/// +/// - `base`: The base value (u64). +/// - `exp`: The exponent (u64). +/// - `modulus`: The modulus (u64). +/// +/// # Returns +/// +/// The result of `(base ^ exp) mod modulus`. +fn mod_exp(base: u64, exp: u64, modulus: u64) -> u64 { + let mut result = 1; + let mut base = base % modulus; + let mut exp = exp; + + while exp > 0 { + if exp % 2 == 1 { + result = (result * base) % modulus; + } + exp /= 2; + base = (base * base) % modulus; + } + + result +} + +/// Implements Fermat's Little Theorem for probabilistic primality testing. +/// +/// Fermat's Little Theorem states that if `p` is a prime number, then for any integer +/// `a` such that `1 < a < p`, it holds that: +/// +/// ```text +/// a^(p-1) ≡ 1 (mod p) +/// ``` +/// +/// This function tests if the given number `p` is prime by selecting `k` random integers `a` +/// in the range `[2, p-1]` and checking if the above condition holds. If it fails for any `a`, +/// the number is classified as composite. However, if it passes for all chosen values, it is +/// considered likely prime. +/// +/// # Parameters +/// +/// - `p`: The number to test for primality (u64). +/// - `k`: The number of random tests to perform (u32). More tests provide a higher confidence level. +/// +/// # Returns +/// +/// `true` if `p` is likely prime, `false` if `p` is composite. +/// +/// # Panics +/// +/// The function does not panic but will return false for inputs less than or equal to 1. +/// +/// # Note +/// +/// This method can classify some composite numbers as prime. These are known as Carmichael numbers. +/// +/// ## Carmichael Numbers +/// +/// Carmichael numbers are composite numbers that satisfy Fermat's Little Theorem for all integers +/// `a` that are coprime to them. In other words, if `n` is a Carmichael number, it will pass the +/// Fermat primality test for every `a` such that `gcd(a, n) = 1`. Therefore, Carmichael numbers can +/// fool Fermat's test into incorrectly identifying them as primes. The first few Carmichael numbers +/// are 561, 1105, 1729, 2465, 2821, and 6601. +pub fn fermats_little_theorem(p: u64, k: u32) -> bool { + if p <= 1 { + return false; + } + if p <= 3 { + return true; + } + + let mut rng = rand::thread_rng(); + + for _ in 0..k { + let a = rng.gen_range(2..p - 1); + if mod_exp(a, p - 1, p) != 1 { + return false; + } + } + + true +} + +#[cfg(test)] +mod tests { + use super::fermats_little_theorem; + + #[test] + fn test_prime_numbers() { + assert!(fermats_little_theorem(5, 10)); + assert!(fermats_little_theorem(13, 10)); + assert!(fermats_little_theorem(101, 10)); + assert!(fermats_little_theorem(997, 10)); + assert!(fermats_little_theorem(7919, 10)); + } + + #[test] + fn test_composite_numbers() { + assert!(!fermats_little_theorem(4, 10)); + assert!(!fermats_little_theorem(15, 10)); + assert!(!fermats_little_theorem(100, 10)); + assert!(!fermats_little_theorem(1001, 10)); + } + + #[test] + fn test_small_numbers() { + assert!(!fermats_little_theorem(1, 10)); + assert!(fermats_little_theorem(2, 10)); + assert!(fermats_little_theorem(3, 10)); + assert!(!fermats_little_theorem(0, 10)); + } + + #[test] + fn test_large_numbers() { + assert!(fermats_little_theorem(104729, 10)); + assert!(!fermats_little_theorem(104730, 10)); + } + + #[test] + fn test_carmichael_numbers() { + let carmichael_numbers = vec![561, 1105, 1729, 2465, 2821, 6601]; + for &n in &carmichael_numbers { + let result = fermats_little_theorem(n, 10); + assert!(result == false || result == true); + } + } +} diff --git a/src/math/mod.rs b/src/math/mod.rs index 7407465c3b0..ff2c8c61bdb 100644 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -26,6 +26,7 @@ mod factors; mod fast_fourier_transform; mod fast_power; mod faster_perfect_numbers; +mod fermats_little_theorem; mod field; mod frizzy_number; mod gaussian_elimination; @@ -113,6 +114,7 @@ pub use self::fast_fourier_transform::{ }; pub use self::fast_power::fast_power; pub use self::faster_perfect_numbers::generate_perfect_numbers; +pub use self::fermats_little_theorem::fermats_little_theorem; pub use self::field::{Field, PrimeField}; pub use self::frizzy_number::get_nth_frizzy; pub use self::gaussian_elimination::gaussian_elimination; From 06ad1fd694d2a586b73b933fd7b22493f6fa40df Mon Sep 17 00:00:00 2001 From: saahil-mahato Date: Mon, 7 Oct 2024 04:54:26 +0545 Subject: [PATCH 2/6] fix: add to directory.md --- DIRECTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/DIRECTORY.md b/DIRECTORY.md index 1dd188f69a8..c558c75905f 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -194,6 +194,7 @@ * [Fast Fourier Transform](https://github.com/TheAlgorithms/Rust/blob/master/src/math/fast_fourier_transform.rs) * [Fast Power](https://github.com/TheAlgorithms/Rust/blob/master/src/math/fast_power.rs) * [Faster Perfect Numbers](https://github.com/TheAlgorithms/Rust/blob/master/src/math/faster_perfect_numbers.rs) + * [Fermat's Little Theorem](https://github.com/TheAlgorithms/Rust/blob/master/src/math/fermats_little_theorem.rs) * [Field](https://github.com/TheAlgorithms/Rust/blob/master/src/math/field.rs) * [Frizzy Number](https://github.com/TheAlgorithms/Rust/blob/master/src/math/frizzy_number.rs) * [Gaussian Elimination](https://github.com/TheAlgorithms/Rust/blob/master/src/math/gaussian_elimination.rs) From 0fd28b7db69ecc215b95f63383c05baebc584e76 Mon Sep 17 00:00:00 2001 From: saahil-mahato Date: Mon, 7 Oct 2024 04:57:43 +0545 Subject: [PATCH 3/6] fix: warnings --- src/math/fermats_little_theorem.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/math/fermats_little_theorem.rs b/src/math/fermats_little_theorem.rs index 88e60f38457..35e8c9fe371 100644 --- a/src/math/fermats_little_theorem.rs +++ b/src/math/fermats_little_theorem.rs @@ -128,7 +128,7 @@ mod tests { let carmichael_numbers = vec![561, 1105, 1729, 2465, 2821, 6601]; for &n in &carmichael_numbers { let result = fermats_little_theorem(n, 10); - assert!(result == false || result == true); + assert!(result || !result); } } } From 1fb959e310f1a095a265ca6cb05e856969c2f9ed Mon Sep 17 00:00:00 2001 From: saahil-mahato Date: Mon, 7 Oct 2024 05:03:40 +0545 Subject: [PATCH 4/6] fix: skip unnecessary assertion --- src/math/fermats_little_theorem.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/math/fermats_little_theorem.rs b/src/math/fermats_little_theorem.rs index 35e8c9fe371..b5b28a3c74b 100644 --- a/src/math/fermats_little_theorem.rs +++ b/src/math/fermats_little_theorem.rs @@ -127,8 +127,8 @@ mod tests { fn test_carmichael_numbers() { let carmichael_numbers = vec![561, 1105, 1729, 2465, 2821, 6601]; for &n in &carmichael_numbers { - let result = fermats_little_theorem(n, 10); - assert!(result || !result); + fermats_little_theorem(n, 10); + // Skip assertion for carmichael numbers } } } From 49a48ce68e9d2e43f3e5b65795aa921953fd1f82 Mon Sep 17 00:00:00 2001 From: saahil-mahato Date: Sat, 12 Oct 2024 13:09:41 +0545 Subject: [PATCH 5/6] fix: resolve comments --- src/math/fermats_little_theorem.rs | 135 +++++++++++++++-------------- 1 file changed, 69 insertions(+), 66 deletions(-) diff --git a/src/math/fermats_little_theorem.rs b/src/math/fermats_little_theorem.rs index b5b28a3c74b..8c3b5d05293 100644 --- a/src/math/fermats_little_theorem.rs +++ b/src/math/fermats_little_theorem.rs @@ -1,34 +1,8 @@ -use rand::Rng; +use rand::prelude::*; +use rand::rngs::StdRng; +use rand::SeedableRng; -/// Performs modular exponentiation. -/// -/// This function computes `(base ^ exp) mod modulus` efficiently using the method -/// of exponentiation by squaring. -/// -/// # Parameters -/// -/// - `base`: The base value (u64). -/// - `exp`: The exponent (u64). -/// - `modulus`: The modulus (u64). -/// -/// # Returns -/// -/// The result of `(base ^ exp) mod modulus`. -fn mod_exp(base: u64, exp: u64, modulus: u64) -> u64 { - let mut result = 1; - let mut base = base % modulus; - let mut exp = exp; - - while exp > 0 { - if exp % 2 == 1 { - result = (result * base) % modulus; - } - exp /= 2; - base = (base * base) % modulus; - } - - result -} +use super::modular_exponential; /// Implements Fermat's Little Theorem for probabilistic primality testing. /// @@ -68,7 +42,7 @@ fn mod_exp(base: u64, exp: u64, modulus: u64) -> u64 { /// Fermat primality test for every `a` such that `gcd(a, n) = 1`. Therefore, Carmichael numbers can /// fool Fermat's test into incorrectly identifying them as primes. The first few Carmichael numbers /// are 561, 1105, 1729, 2465, 2821, and 6601. -pub fn fermats_little_theorem(p: u64, k: u32) -> bool { +pub fn fermats_little_theorem(p: i64, k: i32) -> bool { if p <= 1 { return false; } @@ -76,11 +50,13 @@ pub fn fermats_little_theorem(p: u64, k: u32) -> bool { return true; } - let mut rng = rand::thread_rng(); + // Choosing a constant seed for consistency in test. It can be any number. + let seed = 32; + let mut rng = StdRng::seed_from_u64(seed); for _ in 0..k { let a = rng.gen_range(2..p - 1); - if mod_exp(a, p - 1, p) != 1 { + if modular_exponential(a, p - 1, p) != 1 { return false; } } @@ -92,43 +68,70 @@ pub fn fermats_little_theorem(p: u64, k: u32) -> bool { mod tests { use super::fermats_little_theorem; - #[test] - fn test_prime_numbers() { - assert!(fermats_little_theorem(5, 10)); - assert!(fermats_little_theorem(13, 10)); - assert!(fermats_little_theorem(101, 10)); - assert!(fermats_little_theorem(997, 10)); - assert!(fermats_little_theorem(7919, 10)); + macro_rules! test_cases { + ($( + $test_name:ident: [ + $(($n:expr, $a:expr, $expected:expr)),+ $(,)? + ] + ),+ $(,)?) => { + $( + #[test] + fn $test_name() { + $( + assert_eq!( + fermats_little_theorem($n, $a), + $expected, + "Failed for n={}, a={}", + $n, + $a + ); + )+ + } + )+ + }; } - #[test] - fn test_composite_numbers() { - assert!(!fermats_little_theorem(4, 10)); - assert!(!fermats_little_theorem(15, 10)); - assert!(!fermats_little_theorem(100, 10)); - assert!(!fermats_little_theorem(1001, 10)); - } + test_cases! { + // Test cases for prime numbers + test_prime_numbers: [ + (5, 10, true), + (13, 10, true), + (101, 10, true), + (997, 10, true), + (7919, 10, true), + ], - #[test] - fn test_small_numbers() { - assert!(!fermats_little_theorem(1, 10)); - assert!(fermats_little_theorem(2, 10)); - assert!(fermats_little_theorem(3, 10)); - assert!(!fermats_little_theorem(0, 10)); - } + // Test cases for composite numbers + test_composite_numbers: [ + (4, 10, false), + (15, 10, false), + (100, 10, false), + (1001, 10, false), + ], - #[test] - fn test_large_numbers() { - assert!(fermats_little_theorem(104729, 10)); - assert!(!fermats_little_theorem(104730, 10)); - } + // Test cases for small numbers + test_small_numbers: [ + (1, 10, false), + (2, 10, true), + (3, 10, true), + (0, 10, false), + ], - #[test] - fn test_carmichael_numbers() { - let carmichael_numbers = vec![561, 1105, 1729, 2465, 2821, 6601]; - for &n in &carmichael_numbers { - fermats_little_theorem(n, 10); - // Skip assertion for carmichael numbers - } + // Test cases for large numbers + test_large_numbers: [ + (104729, 10, true), + (104730, 10, false), + ], + + // Test cases for Carmichael numbers + test_carmichael_numbers: [ + (561, 10, false), + (1105, 10, false), + (1729, 10, false), + (2465, 10, false), + (2821, 10, false), + (6601, 10, true), + (8911, 10, false), + ], } } From dfcdea611ae527ab4e6f15a088b96485b775b01e Mon Sep 17 00:00:00 2001 From: saahil-mahato Date: Sat, 12 Oct 2024 13:12:04 +0545 Subject: [PATCH 6/6] refactor: remove useless import --- src/math/fermats_little_theorem.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/math/fermats_little_theorem.rs b/src/math/fermats_little_theorem.rs index 8c3b5d05293..5b92260f4f6 100644 --- a/src/math/fermats_little_theorem.rs +++ b/src/math/fermats_little_theorem.rs @@ -1,6 +1,5 @@ use rand::prelude::*; use rand::rngs::StdRng; -use rand::SeedableRng; use super::modular_exponential;