Skip to content

Commit

Permalink
Merge pull request #16 from NicklasXYZ/main
Browse files Browse the repository at this point in the history
Add is_prime function + doc fixes
  • Loading branch information
NicklasXYZ committed Mar 20, 2024
2 parents c3cef82 + 587a47a commit fb7ba54
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 18 deletions.
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)
}

0 comments on commit fb7ba54

Please sign in to comment.