Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add is_prime function + doc fixes #16

Merged
merged 1 commit into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 109 additions & 18 deletions src/gleam_community/maths/predicates.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ import gleam_community/maths/arithmetics
/// <summary>Example</summary>
///
/// import gleeunit/should
/// import gleam_community/maths/tests
/// import gleam_community/maths/predicates
///
/// pub fn example () {
/// let val: Float = 99.
Expand Down Expand Up @@ -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.
Expand All @@ -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) ->
Expand Down Expand Up @@ -181,13 +181,13 @@ pub fn all_close(
/// <summary>Example</summary>
///
/// 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)
/// }
/// </details>
Expand Down Expand Up @@ -218,15 +218,15 @@ fn do_ceiling(a: Float) -> Float
/// <summary>Example:</summary>
///
/// 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)
/// }
/// </details>
Expand Down Expand Up @@ -266,13 +266,13 @@ pub fn is_power(x: Int, y: Int) -> Bool {
/// <summary>Example:</summary>
///
/// 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)
/// }
/// </details>
Expand Down Expand Up @@ -308,13 +308,13 @@ fn do_sum(arr: List(Int)) -> Int {
/// <summary>Example:</summary>
///
/// 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)
/// }
/// </details>
Expand All @@ -341,13 +341,13 @@ pub fn is_even(x: Int) -> Bool {
/// <summary>Example:</summary>
///
/// 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)
/// }
/// </details>
Expand All @@ -361,3 +361,94 @@ pub fn is_even(x: Int) -> Bool {
pub fn is_odd(x: Int) -> Bool {
x % 2 != 0
}

/// <div style="text-align: right;">
/// <a href="https://github.com/gleam-community/maths/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// 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>
/// <summary>Details</summary>
///
/// 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$$.
///
/// </details>
///
/// <details>
/// <summary>Example:</summary>
///
/// 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)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top ↑</small>
/// </a>
/// </div>
///
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
}
}
34 changes: 34 additions & 0 deletions test/gleam_community/maths/predicates_test.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Loading