diff --git a/src/gleam_community/maths/predicates.gleam b/src/gleam_community/maths/predicates.gleam index f8d357c..ca81d33 100644 --- a/src/gleam_community/maths/predicates.gleam +++ b/src/gleam_community/maths/predicates.gleam @@ -61,7 +61,7 @@ import gleam_community/maths/arithmetics /// Example /// /// import gleeunit/should -/// import gleam_community/maths/tests +/// import gleam_community/maths/predicates /// /// pub fn example () { /// let val: Float = 99. @@ -115,7 +115,7 @@ fn float_absolute_difference(a: Float, b: Float) -> Float { /// /// import gleeunit/should /// import gleam/list -/// import gleam_community/maths/tests +/// import gleam_community/maths/predicates /// /// pub fn example () { /// let val: Float = 99. @@ -126,7 +126,7 @@ fn float_absolute_difference(a: Float, b: Float) -> Float { /// // if 'val' is within 1 percent of 'ref_val' +/- 0.1 /// let rtol: Float = 0.01 /// let atol: Float = 0.10 -/// tests.all_close(xarr, yarr, rtol, atol) +/// predicates.all_close(xarr, yarr, rtol, atol) /// |> fn(zarr: Result(List(Bool), String)) -> Result(Bool, Nil) { /// case zarr { /// Ok(arr) -> @@ -181,13 +181,13 @@ pub fn all_close( /// Example /// /// import gleeunit/should -/// import gleam_community/maths/tests +/// import gleam_community/maths/predicates /// /// pub fn example () { -/// tests.is_fractional(0.3333) +/// predicates.is_fractional(0.3333) /// |> should.equal(True) /// -/// tests.is_fractional(1.0) +/// predicates.is_fractional(1.0) /// |> should.equal(False) /// } /// @@ -218,15 +218,15 @@ fn do_ceiling(a: Float) -> Float /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/tests +/// import gleam_community/maths/predicates /// /// pub fn example() { /// // Check if 4 is a power of 2 (it is) -/// tests.is_power(4, 2) +/// predicates.is_power(4, 2) /// |> should.equal(True) /// /// // Check if 5 is a power of 2 (it is not) -/// tests.is_power(5, 2) +/// predicates.is_power(5, 2) /// |> should.equal(False) /// } /// @@ -266,13 +266,13 @@ pub fn is_power(x: Int, y: Int) -> Bool { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/tests +/// import gleam_community/maths/predicates /// /// pub fn example() { -/// tests.is_perfect(6) +/// predicates.is_perfect(6) /// |> should.equal(True) /// -/// tests.is_perfect(28) +/// predicates.is_perfect(28) /// |> should.equal(True) /// } /// @@ -308,13 +308,13 @@ fn do_sum(arr: List(Int)) -> Int { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/tests +/// import gleam_community/maths/predicates /// /// pub fn example() { -/// tests.is_even(-3) +/// predicates.is_even(-3) /// |> should.equal(False) /// -/// tests.is_even(-4) +/// predicates.is_even(-4) /// |> should.equal(True) /// } /// @@ -341,13 +341,13 @@ pub fn is_even(x: Int) -> Bool { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/tests +/// import gleam_community/maths/predicates /// /// pub fn example() { -/// tests.is_odd(-3) +/// predicates.is_odd(-3) /// |> should.equal(True) /// -/// tests.is_odd(-4) +/// predicates.is_odd(-4) /// |> should.equal(False) /// } /// @@ -361,3 +361,94 @@ pub fn is_even(x: Int) -> Bool { pub fn is_odd(x: Int) -> Bool { x % 2 != 0 } + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a prime number. +/// A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself. +/// +/// The function uses the Miller-Rabin primality test to assess if $$x$$ is prime. It is a probabilistic +/// test, so it can mistakenly identify a composite number as prime. However, the probability of such errors decreases +/// with more testing iterations (the function uses 64 iterations internally, which is typically more than sufficient). +/// The Miller-Rabin test is particularly useful for large numbers. +/// +///
+/// Details +/// +/// Examples of prime numbers: +/// - $$2$$ is a prime number since it has only two divisors: $$1$$ and $$2$$. +/// - $$7$$ is a prime number since it has only two divisors: $$1$$ and $$7$$. +/// - $$4$$ is not a prime number since it has divisors other than $$1$$ and itself, such as $$2$$. +/// +///
+/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/predicates +/// +/// pub fn example() { +/// predicates.is_prime(2) +/// |> should.equal(True) +/// +/// predicates.is_prime(4) +/// |> should.equal(False) +/// +/// // Test the 2nd Carmichael number +/// predicates.is_prime(1105) +/// |> should.equal(False) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn is_prime(x: Int) -> Bool { + case x { + x if x < 2 -> { + False + } + x if x == 2 -> { + True + } + _ -> { + miller_rabin_test(x, 64) + } + } +} + +fn miller_rabin_test(n: Int, k: Int) -> Bool { + case n, k { + _, 0 -> True + _, _ -> { + // Generate a random int in the range [2, n] + let random_candidate: Int = 2 + int.random(n - 2) + case powmod_with_check(random_candidate, n - 1, n) == 1 { + True -> miller_rabin_test(n, k - 1) + False -> False + } + } + } +} + +fn powmod_with_check(base: Int, exponent: Int, modulus: Int) -> Int { + case exponent, { exponent % 2 } == 0 { + 0, _ -> 1 + _, True -> { + let x: Int = powmod_with_check(base, exponent / 2, modulus) + case { x * x } % modulus, x != 1 && x != { modulus - 1 } { + 1, True -> 0 + _, _ -> { x * x } % modulus + } + } + _, _ -> { base * powmod_with_check(base, exponent - 1, modulus) } % modulus + } +} diff --git a/test/gleam_community/maths/predicates_test.gleam b/test/gleam_community/maths/predicates_test.gleam index a7a13d8..4130aab 100644 --- a/test/gleam_community/maths/predicates_test.gleam +++ b/test/gleam_community/maths/predicates_test.gleam @@ -136,3 +136,37 @@ pub fn int_is_perfect_test() { predicates.is_perfect(13) |> should.equal(False) } + +pub fn int_is_prime_test() { + predicates.is_prime(1) + |> should.equal(False) + + predicates.is_prime(2) + |> should.equal(True) + + predicates.is_prime(3) + |> should.equal(True) + + predicates.is_prime(5) + |> should.equal(True) + + predicates.is_prime(7) + |> should.equal(True) + + predicates.is_prime(11) + |> should.equal(True) + + predicates.is_prime(42) + |> should.equal(False) + + predicates.is_prime(7919) + |> should.equal(True) + + // Test 1st Carmichael number + predicates.is_prime(561) + |> should.equal(False) + + // Test 2nd Carmichael number + predicates.is_prime(1105) + |> should.equal(False) +}