From c7e703a3c495a58c4d31235e6adeb5aecbc1b4f7 Mon Sep 17 00:00:00 2001 From: NicklasXYZ Date: Sat, 16 Sep 2023 23:34:00 +0200 Subject: [PATCH 1/6] Refactor --- .github/workflows/release.yml | 21 +- .github/workflows/test.yml | 12 +- gleam.toml | 4 +- manifest.toml | 8 +- .../{int_list.gleam => arithmetics.gleam} | 553 ++-- src/gleam_community/maths/combinatorics.gleam | 411 +++ src/gleam_community/maths/conversion.gleam | 180 ++ src/gleam_community/maths/elementary.gleam | 1256 +++++++++ src/gleam_community/maths/float.gleam | 2421 ----------------- src/gleam_community/maths/float_list.gleam | 1108 -------- src/gleam_community/maths/int.gleam | 860 ------ src/gleam_community/maths/list.gleam | 220 -- src/gleam_community/maths/metrics.gleam | 618 +++++ src/gleam_community/maths/piecewise.gleam | 1168 ++++++++ src/gleam_community/maths/sequences.gleam | 299 ++ src/gleam_community/maths/special.gleam | 205 ++ src/gleam_community/maths/tests.gleam | 358 +++ .../gleam_community_maths_arithmetics.gleam | 203 ++ .../gleam_community_maths_combinatorics.gleam | 167 ++ .../gleam_community_maths_conversion.gleam | 44 + .../gleam_community_maths_elementary.gleam | 482 ++++ ...leam_community_maths_float_list_test.gleam | 519 ---- .../gleam_community_maths_float_test.gleam | 1069 -------- .../gleam_community_maths_int_list_test.gleam | 148 - .../gleam_community_maths_int_test.gleam | 327 --- .../gleam_community_maths_list_test.gleam | 83 - .../gleam/gleam_community_maths_metrics.gleam | 220 ++ .../gleam_community_maths_piecewise.gleam | 758 ++++++ .../gleam_community_maths_sequences.gleam | 234 ++ .../gleam/gleam_community_maths_special.gleam | 57 + test/gleam/gleam_community_maths_tests.gleam | 143 + test/gleam_community_maths_test.gleam | 12 +- test/gleam_community_maths_test_ffi.mjs | 98 +- 33 files changed, 7200 insertions(+), 7066 deletions(-) rename src/gleam_community/maths/{int_list.gleam => arithmetics.gleam} (51%) create mode 100644 src/gleam_community/maths/combinatorics.gleam create mode 100644 src/gleam_community/maths/conversion.gleam create mode 100644 src/gleam_community/maths/elementary.gleam delete mode 100644 src/gleam_community/maths/float.gleam delete mode 100644 src/gleam_community/maths/float_list.gleam delete mode 100644 src/gleam_community/maths/int.gleam delete mode 100644 src/gleam_community/maths/list.gleam create mode 100644 src/gleam_community/maths/metrics.gleam create mode 100644 src/gleam_community/maths/piecewise.gleam create mode 100644 src/gleam_community/maths/sequences.gleam create mode 100644 src/gleam_community/maths/special.gleam create mode 100644 src/gleam_community/maths/tests.gleam create mode 100644 test/gleam/gleam_community_maths_arithmetics.gleam create mode 100644 test/gleam/gleam_community_maths_combinatorics.gleam create mode 100644 test/gleam/gleam_community_maths_conversion.gleam create mode 100644 test/gleam/gleam_community_maths_elementary.gleam delete mode 100644 test/gleam/gleam_community_maths_float_list_test.gleam delete mode 100644 test/gleam/gleam_community_maths_float_test.gleam delete mode 100644 test/gleam/gleam_community_maths_int_list_test.gleam delete mode 100644 test/gleam/gleam_community_maths_int_test.gleam delete mode 100644 test/gleam/gleam_community_maths_list_test.gleam create mode 100644 test/gleam/gleam_community_maths_metrics.gleam create mode 100644 test/gleam/gleam_community_maths_piecewise.gleam create mode 100644 test/gleam/gleam_community_maths_sequences.gleam create mode 100644 test/gleam/gleam_community_maths_special.gleam create mode 100644 test/gleam/gleam_community_maths_tests.gleam diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8c2d770..626a484 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,29 +5,24 @@ on: tags: ["v*"] jobs: - publish: + build: runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - uses: erlef/setup-beam@v1 + - uses: actions/checkout@v3.1.0 + - uses: erlef/setup-beam@v1.16.0 with: - otp-version: "25.0" - gleam-version: "0.25.3" - + otp-version: "26.0.2" + gleam-version: "0.30.5" - run: cargo install tomlq - - run: | if [ "v$(tomlq version -f gleam.toml)" == "${{ github.ref_name }}" ]; then exit 0 fi echo "tag does not match version in gleam.toml, refusing to publish" exit 1 - - run: gleam format --check - - - run: gleam test - + - run: gleam format --check src test + - run: gleam test --target erlang + - run: gleam test --target javascript - run: gleam publish -y env: HEXPM_USER: ${{ secrets.HEX_USERNAME }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 92e0a54..00121a5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,14 +14,14 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: erlef/setup-beam@v1 + - uses: actions/checkout@v3.1.0 + - uses: erlef/setup-beam@v1.16.0 with: - otp-version: "25.0" - gleam-version: "0.28.3" + otp-version: "26.0.2" + gleam-version: "0.30.5" - uses: actions/setup-node@v3.5.1 with: node-version: "16.18.1" + - run: gleam format --check src test - run: gleam test --target erlang - - run: gleam test --target javascript - - run: gleam format --check src test \ No newline at end of file + - run: gleam test --target javascript \ No newline at end of file diff --git a/gleam.toml b/gleam.toml index 22022ea..0b8c82a 100644 --- a/gleam.toml +++ b/gleam.toml @@ -6,7 +6,7 @@ description = "A basic maths library" repository = { type = "github", user = "gleam-community", repo = "maths" } [dependencies] -gleam_stdlib = "~> 0.28" +gleam_stdlib = "~> 0.30" [dev-dependencies] -gleeunit = "~> 0.7" +gleeunit = "~> 0.10" diff --git a/manifest.toml b/manifest.toml index a960b59..6908dfc 100644 --- a/manifest.toml +++ b/manifest.toml @@ -2,10 +2,10 @@ # You typically do not need to edit this file packages = [ - { name = "gleam_stdlib", version = "0.28.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "73F0A89FADE5022CBEF6D6C3551F9ADCE7054AFCE0CB1DC4C6D5AB4CA62D0111" }, - { name = "gleeunit", version = "0.10.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "ECEA2DE4BE6528D36AFE74F42A21CDF99966EC36D7F25DEB34D47DD0F7977BAF" }, + { name = "gleam_stdlib", version = "0.30.2", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "8D8BF3790AA31176B1E1C0B517DD74C86DA8235CF3389EA02043EE4FD82AE3DC" }, + { name = "gleeunit", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "1397E5C4AC4108769EE979939AC39BF7870659C5AFB714630DEEEE16B8272AD5" }, ] [requirements] -gleam_stdlib = "~> 0.28" -gleeunit = "~> 0.7" +gleam_stdlib = { version = "~> 0.30" } +gleeunit = { version = "~> 0.10" } diff --git a/src/gleam_community/maths/int_list.gleam b/src/gleam_community/maths/arithmetics.gleam similarity index 51% rename from src/gleam_community/maths/int_list.gleam rename to src/gleam_community/maths/arithmetics.gleam index 75ea306..aee0d34 100644 --- a/src/gleam_community/maths/int_list.gleam +++ b/src/gleam_community/maths/arithmetics.gleam @@ -1,13 +1,15 @@ -//// -//// -//// +//// +//// +//// //// +//// +//// +//// +//// +//// --- +//// +//// Combinatorics: A module that offers mathematical functions related to counting, arrangements, and combinations. +//// +//// * **Combinatorial functions** +//// * [`combination`](#combination) +//// * [`factorial`](#factorial) +//// * [`permutation`](#permutation) +//// * [`list_combination`](#list_combination) +//// * [`list_permutation`](#list_permutation) +//// * [`cartesian_product`](#cartesian_product) +//// + +import gleam/list +import gleam/set + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// A combinatorial function for computing the number of a $$k$$-combinations of $$n$$ elements: +/// +/// \\[ +/// C(n, k) = \binom{n}{k} = \frac{n!}{k! (n-k)!} +/// \\] +/// Also known as "$$n$$ choose $$k$$" or the binomial coefficient. +/// +/// The implementation uses the effecient iterative multiplicative formula for the computation. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/int as intx +/// +/// pub fn example() { +/// // Invalid input gives an error +/// // Error on: n = -1 < 0 +/// intx.combination(-1, 1) +/// |> should.be_error() +/// +/// // Valid input returns a result +/// intx.combination(4, 0) +/// |> should.equal(Ok(1)) +/// +/// intx.combination(4, 4) +/// |> should.equal(Ok(1)) +/// +/// intx.combination(4, 2) +/// |> should.equal(Ok(6)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn combination(n: Int, k: Int) -> Result(Int, String) { + case n < 0 { + True -> + "Invalid input argument: n < 0. Valid input is n > 0." + |> Error + False -> + case k < 0 || k > n { + True -> + 0 + |> Ok + False -> + case k == 0 || k == n { + True -> + 1 + |> Ok + False -> { + let min = case k < n - k { + True -> k + False -> n - k + } + list.range(1, min) + |> list.fold( + 1, + fn(acc: Int, x: Int) -> Int { acc * { n + 1 - x } / x }, + ) + |> Ok + } + } + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// A combinatorial function for computing the total number of combinations of $$n$$ +/// elements, that is $$n!$$. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/int as intx +/// +/// pub fn example() { +/// // Invalid input gives an error +/// intx.factorial(-1) +/// |> should.be_error() +/// +/// // Valid input returns a result +/// intx.factorial(0) +/// |> should.equal(Ok(1)) +/// intx.factorial(1) +/// |> should.equal(Ok(1)) +/// intx.factorial(2) +/// |> should.equal(Ok(2)) +/// intx.factorial(3) +/// |> should.equal(Ok(6)) +/// intx.factorial(4) +/// |> should.equal(Ok(24)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn factorial(n) -> Result(Int, String) { + case n < 0 { + True -> + "Invalid input argument: n < 0. Valid input is n > 0." + |> Error + False -> + case n { + 0 -> + 1 + |> Ok + 1 -> + 1 + |> Ok + _ -> + list.range(1, n) + |> list.fold(1, fn(acc: Int, x: Int) { acc * x }) + |> Ok + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// A combinatorial function for computing the number of $$k$$-permuations (without repetitions) +/// of $$n$$ elements: +/// +/// \\[ +/// P(n, k) = \frac{n!}{(n - k)!} +/// \\] +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/int as intx +/// +/// pub fn example() { +/// // Invalid input gives an error +/// // Error on: n = -1 < 0 +/// intx.permutation(-1, 1) +/// |> should.be_error() +/// +/// // Valid input returns a result +/// intx.permutation(4, 0) +/// |> should.equal(Ok(1)) +/// +/// intx.permutation(4, 4) +/// |> should.equal(Ok(1)) +/// +/// intx.permutation(4, 2) +/// |> should.equal(Ok(12)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn permutation(n: Int, k: Int) -> Result(Int, String) { + case n < 0 { + True -> + "Invalid input argument: n < 0. Valid input is n > 0." + |> Error + False -> + case k < 0 || k > n { + True -> + 0 + |> Ok + False -> + case k == n { + True -> + 1 + |> Ok + False -> { + let assert Ok(v1) = factorial(n) + let assert Ok(v2) = factorial(n - k) + v1 / v2 + |> Ok + } + } + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Generate all $$k$$-combinations based on a given list. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam/list +/// import gleam_community/maths/list as listx +/// +/// pub fn example () { +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn list_combination(arr: List(a), k: Int) -> Result(List(List(a)), String) { + case k < 0 { + True -> + "Invalid input argument: k < 0. Valid input is k > 0." + |> Error + False -> { + case k > list.length(arr) { + True -> + "Invalid input argument: k > length(arr). Valid input is 0 < k <= length(arr)." + |> Error + False -> { + do_list_combination(arr, k, []) + |> Ok + } + } + } + } +} + +fn do_list_combination(arr: List(a), k: Int, prefix: List(a)) -> List(List(a)) { + case k { + 0 -> [list.reverse(prefix)] + _ -> + case arr { + [] -> [] + [x, ..xs] -> { + let with_x = do_list_combination(xs, k - 1, [x, ..prefix]) + let without_x = do_list_combination(xs, k, prefix) + list.append(with_x, without_x) + } + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Generate all permutations based on a given list. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam/list +/// import gleam_community/maths/list as listx +/// +/// pub fn example () { +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn list_permutation(arr: List(a)) -> List(List(a)) { + case arr { + [] -> [[]] + _ -> + flat_map( + arr, + fn(x) { + let remaining = list.filter(arr, fn(y) { x != y }) + list.map(list_permutation(remaining), fn(perm) { [x, ..perm] }) + }, + ) + } +} + +/// Flat map function +fn flat_map(list: List(a), f: fn(a) -> List(b)) -> List(b) { + list + |> list.map(f) + |> concat() +} + +/// Concatenate a list of lists +fn concat(lists: List(List(a))) -> List(a) { + lists + |> list.fold([], list.append) +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Generate a list containing all combinations of pairs of elements coming from two given lists. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam/list +/// import gleam_community/maths/list as listx +/// +/// pub fn example () { +/// [] +/// |> listx.cartesian_product([]) +/// |> should.equal([]) +/// +/// [1.0, 10.0] +/// |> listx.cartesian_product([1.0, 2.0]) +/// |> should.equal([#(1.0, 1.0), #(1.0, 2.0), #(10.0, 1.0), #(10.0, 2.0)]) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn cartesian_product(xarr: List(a), yarr: List(a)) -> List(#(a, a)) { + let xset: set.Set(a) = + xarr + |> set.from_list() + let yset: set.Set(a) = + yarr + |> set.from_list() + xset + |> set.fold( + set.new(), + fn(accumulator0: set.Set(#(a, a)), member0: a) -> set.Set(#(a, a)) { + set.fold( + yset, + accumulator0, + fn(accumulator1: set.Set(#(a, a)), member1: a) -> set.Set(#(a, a)) { + set.insert(accumulator1, #(member0, member1)) + }, + ) + }, + ) + |> set.to_list() +} diff --git a/src/gleam_community/maths/conversion.gleam b/src/gleam_community/maths/conversion.gleam new file mode 100644 index 0000000..d45a5f2 --- /dev/null +++ b/src/gleam_community/maths/conversion.gleam @@ -0,0 +1,180 @@ +//// +//// +//// +//// +//// +//// +//// --- +//// +//// Conversion: A module containing various functions for converting between types and different quantities. +//// +//// * **Misc. functions** +//// * [`float_to_int`](#float_to_int) +//// * [`int_to_float`](#int_to_float) +//// * [`degrees_to_radians`](#degrees_to_radians) +//// * [`radians_to_degrees`](#radians_to_degrees) + +import gleam/int + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// A function that produces a number of type `Float` from an `Int`. +/// +/// Note: The function is equivalent to the `int.to_float` function in the Gleam stdlib. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/int as intx +/// +/// pub fn example() { +/// intx.to_float(-1) +/// |> should.equal(-1.0) +/// +/// intx.to_float(1) +/// |> should.equal(1.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn int_to_float(x: Int) -> Float { + int.to_float(x) +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function returns the integral part of a given floating point value. +/// That is, everything after the decimal point of a given floating point value is discarded and only the integer value before the decimal point is returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam/option +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// floatx.to_int(12.0654) +/// |> should.equal(12) +/// +/// // Note: Making the following function call is equivalent +/// // but instead of returning a value of type 'Int' a value +/// // of type 'Float' is returned. +/// floatx.round(12.0654, option.Some(0), option.Some(floatx.RoundToZero)) +/// |> should.equal(Ok(12.0)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn float_to_int(x: Float) -> Int { + do_to_int(x) +} + +@external(erlang, "erlang", "trunc") +@external(javascript, "../../maths.mjs", "truncate") +fn do_to_int(a: Float) -> Int + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Convert a value in degrees to a value measured in radians. +/// That is, $$1 \text{ degrees } = \frac{\pi}{180} \text{ radians }$$. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// floatx.to_radian(360.) +/// |> should.equal(2. *. floatx.pi()) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn degrees_to_radians(x: Float) -> Float { + x *. do_pi() /. 180.0 +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Convert a value in degrees to a value measured in radians. +/// That is, $$1 \text{ radians } = \frac{180}{\pi} \text{ degrees }$$. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// floatx.to_degree(0.0) +/// |> should.equal(0.0) +/// +/// floatx.to_degree(2. *. floatx.pi()) +/// |> should.equal(360.) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn radians_to_degrees(x: Float) -> Float { + x *. 180.0 /. do_pi() +} + +@external(erlang, "math", "pi") +@external(javascript, "../../maths.mjs", "pi") +fn do_pi() -> Float diff --git a/src/gleam_community/maths/elementary.gleam b/src/gleam_community/maths/elementary.gleam new file mode 100644 index 0000000..88ffb92 --- /dev/null +++ b/src/gleam_community/maths/elementary.gleam @@ -0,0 +1,1256 @@ +//// +//// +//// +//// +//// +//// +//// --- +//// +//// Elementary: A module containing a comprehensive set of foundational mathematical functions and constants. +//// +//// * **Trigonometric and hyperbolic functions** +//// * [`acos`](#acos) +//// * [`acosh`](#acosh) +//// * [`asin`](#asin) +//// * [`asinh`](#asinh) +//// * [`atan`](#atan) +//// * [`atan2`](#atan2) +//// * [`atanh`](#atanh) +//// * [`cos`](#cos) +//// * [`cosh`](#cosh) +//// * [`sin`](#sin) +//// * [`sinh`](#sinh) +//// * [`tan`](#tan) +//// * [`tanh`](#tanh) +//// * **Powers, logs and roots** +//// * [`exponential`](#exponential) +//// * [`natural_logarithm`](#natural_logarithm) +//// * [`logarithm`](#logarithm) +//// * [`logarithm_2`](#logarithm_2) +//// * [`logarithm_10`](#logarithm_10) +//// * [`power`](#power) +//// * [`square_root`](#square_root) +//// * [`cube_root`](#cube_root) +//// * [`nth_root`](#nth_root) +//// * **Mathematical constants** +//// * [`pi`](#pi) +//// * [`tau`](#tau) +//// * [`e`](#e) +//// + +import gleam/int +import gleam/option + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The inverse cosine function: +/// +/// \\[ +/// \forall x \in \[-1, 1\], \\; \cos^{-1}{(x)} = y \in \[0, \pi \] +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\[-1, 1\]$$ as input and returns a +/// numeric value $$y$$ that lies in the range $$\[0, \pi \]$$ (an angle in radians). +/// If the input value is outside the domain of the function an error is returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// elementary.acos(1.0) +/// |> should.equal(Ok(0.0)) +/// +/// elementary.acos(1.1) +/// |> should.be_error() +/// +/// elementary.acos(-1.1) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn acos(x: Float) -> Result(Float, String) { + case x >=. -1.0 && x <=. 1.0 { + True -> + do_acos(x) + |> Ok + False -> + "Invalid input argument: x >= -1 or x <= 1. Valid input is -1. <= x <= 1." + |> Error + } +} + +@external(erlang, "math", "acos") +@external(javascript, "../../maths.mjs", "acos") +fn do_acos(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The inverse hyperbolic cosine function: +/// +/// \\[ +/// \forall x \in [1, +\infty\), \\; \cosh^{-1}{(x)} = y \in \[0, +\infty\) +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\[1, +\infty\)$$ as input and returns +/// a numeric value $$y$$ that lies in the range $$\[0, +\infty\)$$ (an angle in radians). +/// If the input value is outside the domain of the function an error is returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.acosh(1.0) +/// |> should.equal(Ok(0.0)) +/// +/// floatx.acosh(0.0) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn acosh(x: Float) -> Result(Float, String) { + case x >=. 1.0 { + True -> + do_acosh(x) + |> Ok + False -> + "Invalid input argument: x < 1. Valid input is x >= 1." + |> Error + } +} + +@external(erlang, "math", "acosh") +@external(javascript, "../../maths.mjs", "acosh") +fn do_acosh(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The inverse sine function: +/// +/// \\[ +/// \forall x \in \[-1, 1\], \\; \sin^{-1}{(x)} = y \in \(-\infty, +\infty\) +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\[-1, 1\]$$ as input and returns a numeric +/// value $$y$$ that lies in the range $$\[-\frac{\pi}{2}, \frac{\pi}{2}\]$$ (an angle in radians). +/// If the input value is outside the domain of the function an error is returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.asin(0.0) +/// |> should.equal(Ok(0.0)) +/// +/// floatx.asin(1.1) +/// |> should.be_error() +/// +/// floatx.asin(-1.1) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn asin(x: Float) -> Result(Float, String) { + case x >=. -1.0 && x <=. 1.0 { + True -> + do_asin(x) + |> Ok + False -> + "Invalid input argument: x >= -1 or x <= 1. Valid input is -1. <= x <= 1." + |> Error + } +} + +@external(erlang, "math", "asin") +@external(javascript, "../../maths.mjs", "asin") +fn do_asin(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The inverse hyperbolic sine function: +/// +/// \\[ +/// \forall x \in \(-\infty, \infty\), \\; \sinh^{-1}{(x)} = y \in \(-\infty, +\infty\) +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input and returns +/// a numeric value $$y$$ that lies in the range $$\(-\infty, +\infty\)$$ (an angle in radians). +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.asinh(0.0) +/// |> should.equal(0.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn asinh(x: Float) -> Float { + do_asinh(x) +} + +@external(erlang, "math", "asinh") +@external(javascript, "../../maths.mjs", "asinh") +fn do_asinh(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The inverse tangent function: +/// +/// \\[ +/// \forall x \in \(-\infty, \infty\), \\; \tan^{-1}{(x)} = y \in \[-\frac{\pi}{2}, \frac{\pi}{2}\] +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input and returns +/// a numeric value $$y$$ that lies in the range $$\[-\frac{\pi}{2}, \frac{\pi}{2}\]$$ (an angle in radians). +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.atan(0.0) +/// |> should.equal(0.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn atan(x: Float) -> Float { + do_atan(x) +} + +@external(erlang, "math", "atan") +@external(javascript, "../../maths.mjs", "atan") +fn do_atan(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The inverse 2-argument tangent function: +/// +/// \\[ +/// \text{atan2}(y, x) = +/// \begin{cases} +/// \tan^{-1}(\frac y x) &\text{if } x > 0, \\\\ +/// \tan^{-1}(\frac y x) + \pi &\text{if } x < 0 \text{ and } y \ge 0, \\\\ +/// \tan^{-1}(\frac y x) - \pi &\text{if } x < 0 \text{ and } y < 0, \\\\ +/// +\frac{\pi}{2} &\text{if } x = 0 \text{ and } y > 0, \\\\ +/// -\frac{\pi}{2} &\text{if } x = 0 \text{ and } y < 0, \\\\ +/// \text{undefined} &\text{if } x = 0 \text{ and } y = 0. +/// \end{cases} +/// \\] +/// +/// The function returns the angle in radians from the x-axis to the line containing the +/// origin $$\(0, 0\)$$ and a point given as input with coordinates $$\(x, y\)$$. The numeric value +/// returned by $$\text{atan2}(y, x)$$ is in the range $$\[-\pi, \pi\]$$. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.atan2(0.0, 0.0) +/// |> should.equal(0.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn atan2(y: Float, x: Float) -> Float { + do_atan2(y, x) +} + +@external(erlang, "math", "atan2") +@external(javascript, "../../maths.mjs", "atan2") +fn do_atan2(a: Float, b: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The inverse hyperbolic tangent function: +/// +/// \\[ +/// \forall x \in \(-1, 1\), \\; \tanh^{-1}{(x)} = y \in \(-\infty, +\infty\) +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(-1, 1\)$$ as input and returns +/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$ (an angle in radians). +/// If the input value is outside the domain of the function an error is returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.atanh(0.0) +/// |> should.equal(Ok(0.0)) +/// +/// floatx.atanh(1.0) +/// |> should.be_error() +/// +/// floatx.atanh(-1.0) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn atanh(x: Float) -> Result(Float, String) { + case x >. -1.0 && x <. 1.0 { + True -> + do_atanh(x) + |> Ok + False -> + "Invalid input argument: x > -1 or x < 1. Valid input is -1. < x < 1." + |> Error + } +} + +@external(erlang, "math", "atanh") +@external(javascript, "../../maths.mjs", "atanh") +fn do_atanh(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The cosine function: +/// +/// \\[ +/// \forall x \in \(-\infty, +\infty\), \\; \cos{(x)} = y \in \[-1, 1\] +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ (an angle in radians) +/// as input and returns a numeric value $$y$$ that lies in the range $$\[-1, 1\]$$. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.cos(0.0) +/// |> should.equal(1.0) +/// +/// floatx.cos(floatx.pi()) +/// |> should.equal(-1.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn cos(x: Float) -> Float { + do_cos(x) +} + +@external(erlang, "math", "cos") +@external(javascript, "../../maths.mjs", "cos") +fn do_cos(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The hyperbolic cosine function: +/// +/// \\[ +/// \forall x \in \(-\infty, \infty\), \\; \cosh{(x)} = y \in \(-\infty, +\infty\) +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ as input (an angle in radians) +/// and returns a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. +/// If the input value is too large an overflow error might occur. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.cosh(0.0) +/// |> should.equal(0.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn cosh(x: Float) -> Float { + do_cosh(x) +} + +@external(erlang, "math", "cosh") +@external(javascript, "../../maths.mjs", "cosh") +fn do_cosh(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The sine function: +/// +/// \\[ +/// \forall x \in \(-\infty, +\infty\), \\; \sin{(x)} = y \in \[-1, 1\] +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ (an angle in radians) +/// as input and returns a numeric value $$y$$ that lies in the range $$\[-1, 1\]$$. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.sin(0.0) +/// |> should.equal(0.0) +/// +/// floatx.sin(0.5 *. floatx.pi()) +/// |> should.equal(1.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn sin(x: Float) -> Float { + do_sin(x) +} + +@external(erlang, "math", "sin") +@external(javascript, "../../maths.mjs", "sin") +fn do_sin(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The hyperbolic sine function: +/// +/// \\[ +/// \forall x \in \(-\infty, +\infty\), \\; \sinh{(x)} = y \in \(-\infty, +\infty\) +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input +/// (an angle in radians) and returns a numeric value $$y$$ that lies in the range +/// $$\(-\infty, +\infty\)$$. If the input value is too large an overflow error might +/// occur. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.sinh(0.0) +/// |> should.equal(0.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn sinh(x: Float) -> Float { + do_sinh(x) +} + +@external(erlang, "math", "sinh") +@external(javascript, "../../maths.mjs", "sinh") +fn do_sinh(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The tangent function: +/// +/// \\[ +/// \forall x \in \(-\infty, +\infty\), \\; \tan{(x)} = y \in \(-\infty, +\infty\) +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input +/// (an angle in radians) and returns a numeric value $$y$$ that lies in the range +/// $$\(-\infty, +\infty\)$$. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.tan(0.0) +/// |> should.equal(0.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn tan(x: Float) -> Float { + do_tan(x) +} + +@external(erlang, "math", "tan") +@external(javascript, "../../maths.mjs", "tan") +fn do_tan(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The hyperbolic tangent function: +/// +/// \\[ +/// \forall x \in \(-\infty, \infty\), \\; \tanh{(x)} = y \in \[-1, 1\] +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ as input (an angle in radians) +/// and returns a numeric value $$y$$ that lies in the range $$\[-1, 1\]$$. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example () { +/// floatx.tanh(0.0) +/// |> should.equal(0.0) +/// +/// floatx.tanh(25.0) +/// |> should.equal(1.0) +/// +/// floatx.tanh(-25.0) +/// |> should.equal(-1.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn tanh(x: Float) -> Float { + do_tanh(x) +} + +@external(erlang, "math", "tanh") +@external(javascript, "../../maths.mjs", "tanh") +fn do_tanh(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The exponential function: +/// +/// \\[ +/// \forall x \in \(-\infty, \infty\), \\; e^{(x)} = y \in \(0, +\infty\) +/// \\] +/// +/// $$e \approx 2.71828\dots$$ is Eulers' number. +/// +/// Note: If the input value $$x$$ is too large an overflow error might occur. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.exponential(0.0) +/// |> should.equal(1.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn exponential(x: Float) -> Float { + do_exponential(x) +} + +@external(erlang, "math", "exp") +@external(javascript, "../../maths.mjs", "exponential") +fn do_exponential(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The natural logarithm function: +/// +/// \\[ +/// \forall x \in \(0, \infty\), \\; \log_{e}{(x)} = y \in \(-\infty, +\infty\) +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ as input and returns +/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. +/// If the input value is outside the domain of the function an error is returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example () { +/// floatx.natural_logarithm(1.0) +/// |> should.equal(Ok(0.0)) +/// +/// floatx.natural_logarithm(floatx.e()) +/// |> should.equal(Ok(1.0)) +/// +/// floatx.natural_logarithm(-1.0) +/// |> should.be_error() +/// } +///
+/// +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn natural_logarithm(x: Float) -> Result(Float, String) { + case x >. 0.0 { + True -> + do_natural_logarithm(x) + |> Ok + False -> + "Invalid input argument: x <= 0. Valid input is x > 0." + |> Error + } +} + +@external(erlang, "math", "log") +@external(javascript, "../../maths.mjs", "logarithm") +fn do_natural_logarithm(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The base $$b$$ logarithm function (computed through the "change of base" formula): +/// +/// \\[ +/// \forall x \in \(0, \infty\) \textnormal{ and } b > 1, \\; \log_{b}{(x)} = y \in \(-\infty, +\infty\) +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ and a base $$b > 1$$ as input and returns +/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. +/// If the input value is outside the domain of the function an error is returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam/option +/// import gleam_community/maths/elementary +/// +/// pub fn example () { +/// floatx.logarithm(1.0, option.Some(10.0)) +/// |> should.equal(Ok(0.0)) +/// +/// floatx.logarithm(floatx.e(), option.Some(floatx.e())) +/// |> should.equal(Ok(1.0)) +/// +/// floatx.logarithm(-1.0, option.Some(2.0)) +/// |> should.be_error() +/// } +///
+/// +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn logarithm(x: Float, base: option.Option(Float)) -> Result(Float, String) { + case x >. 0.0 { + True -> + case base { + option.Some(a) -> + case a >. 0.0 && a != 1.0 { + True -> { + // Apply the "change of base formula" + let assert Ok(numerator) = logarithm_10(x) + let assert Ok(denominator) = logarithm_10(a) + numerator /. denominator + |> Ok + } + False -> + "Invalid input argument: base <= 0 or base == 1. Valid input is base > 0 and base != 1." + |> Error + } + _ -> + "Invalid input argument: base <= 0 or base == 1. Valid input is base > 0 and base != 1." + |> Error + } + _ -> + "Invalid input argument: x <= 0. Valid input is x > 0." + |> Error + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The The base-2 logarithm function: +/// +/// \\[ +/// \forall x \in \(0, \infty), \\; \log_{2}{(x)} = y \in \(-\infty, +\infty\) +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ as input and returns +/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. +/// If the input value is outside the domain of the function an error is returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example () { +/// floatx.logarithm_2(1.0) +/// |> should.equal(Ok(0.0)) +/// +/// floatx.logarithm_2(2.0) +/// |> should.equal(Ok(1.0)) +/// +/// floatx.logarithm_2(-1.0) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn logarithm_2(x: Float) -> Result(Float, String) { + case x >. 0.0 { + True -> + do_logarithm_2(x) + |> Ok + False -> + "Invalid input argument: x <= 0. Valid input is x > 0." + |> Error + } +} + +@external(erlang, "math", "log2") +@external(javascript, "../../maths.mjs", "logarithm_2") +fn do_logarithm_2(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The base-10 logarithm function: +/// +/// \\[ +/// \forall x \in \(0, \infty), \\; \log_{10}{(x)} = y \in \(-\infty, +\infty\) +/// \\] +/// +/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ as input and returns +/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. +/// If the input value is outside the domain of the function an error is returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example () { +/// floatx.logarithm_10(1.0) +/// |> should.equal(Ok(0.0)) +/// +/// floatx.logarithm_10(10.0) +/// |> should.equal(Ok(1.0)) +/// +/// floatx.logarithm_10(-1.0) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn logarithm_10(x: Float) -> Result(Float, String) { + case x >. 0.0 { + True -> + do_logarithm_10(x) + |> Ok + False -> + "Invalid input argument: x <= 0. Valid input is x > 0." + |> Error + } +} + +@external(erlang, "math", "log10") +@external(javascript, "../../maths.mjs", "logarithm_10") +fn do_logarithm_10(a: Float) -> Float + +///
+/// +/// Back to top ↑ +/// +///
+/// +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The exponentiation function: $$y = x^{a}$$. +/// +/// Note that the function is not defined if: +/// 1. The base is negative ($$x < 0$$) and the exponent is fractional +/// ($$a = \frac{n}{m}$$ is an irrreducible fraction). An error will be returned +/// as an imaginary number will otherwise have to be returned. +/// 2. The base is zero ($$x = 0$$) and the exponent is negative ($$a < 0$$) then the +/// expression is equivalent to the exponent $$y$$ divided by $$0$$ and an +/// error will have to be returned as the expression is otherwise undefined. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.power(2., -1.) +/// |> should.equal(Ok(0.5)) +/// +/// floatx.power(2., 2.) +/// |> should.equal(Ok(4.0)) +/// +/// floatx.power(-1., 0.5) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn power(x: Float, y: Float) -> Result(Float, String) { + let fractional: Bool = do_ceiling(y) -. y >. 0.0 + // In the following check: + // 1. If the base (x) is negative and the exponent (y) is fractional + // then return an error as it will otherwise be an imaginary number + // 2. If the base (x) is 0 and the exponent (y) is negative then the + // expression is equivalent to the exponent (y) divided by 0 and an + // error should be returned + case x <. 0.0 && fractional || x == 0.0 && y <. 0.0 { + True -> + "Invalid input argument: x < 0 and y is fractional or x = 0 and y < 0." + |> Error + False -> + do_power(x, y) + |> Ok + } +} + +@external(erlang, "math", "pow") +@external(javascript, "../../maths.mjs", "power") +fn do_power(a: Float, b: Float) -> Float + +@external(erlang, "math", "ceil") +@external(javascript, "../../maths.mjs", "ceiling") +fn do_ceiling(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The square root function: $$y = \sqrt[2]{x} = x^{\frac{1}{2}}$$. +/// +/// Note that the function is not defined if: +/// 1. The input is negative ($$x < 0$$). An error will be returned +/// as an imaginary number will otherwise have to be returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.square_root(1.0) +/// |> should.equal(Ok(1.0)) +/// +/// floatx.square_root(4.0) +/// |> should.equal(Ok(2.0)) +/// +/// floatx.square_root(-1.0) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn square_root(x: Float) -> Result(Float, String) { + // In the following check: + // 1. If x is negative then return an error as it will otherwise be an + // imaginary number + case x <. 0.0 { + True -> + "Invalid input argument: x < 0." + |> Error + False -> { + let assert Ok(result) = power(x, 1.0 /. 2.0) + result + |> Ok + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The cube root function: $$y = \sqrt[3]{x} = x^{\frac{1}{3}}$$. +/// +/// Note that the function is not defined if: +/// 1. The input is negative ($$x < 0$$). An error will be returned +/// as an imaginary number will otherwise have to be returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.cube_root(1.0) +/// |> should.equal(Ok(1.0)) +/// +/// floatx.cube_root(27.0) +/// |> should.equal(Ok(3.0)) +/// +/// floatx.cube_root(-1.0) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn cube_root(x: Float) -> Result(Float, String) { + // In the following check: + // 1. If x is negative then return an error as it will otherwise be an + // imaginary number + case x <. 0.0 { + True -> + "Invalid input argument: x < 0." + |> Error + False -> { + let assert Ok(result) = power(x, 1.0 /. 3.0) + result + |> Ok + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The $$n$$'th root function: $$y = \sqrt[n]{x} = x^{\frac{1}{n}}$$. +/// +/// Note that the function is not defined if: +/// 1. The input is negative ($$x < 0$$). An error will be returned +/// as an imaginary number will otherwise have to be returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/elementary +/// +/// pub fn example() { +/// floatx.nth_root(1.0, 2) +/// |> should.equal(Ok(1.0)) +/// +/// floatx.nth_root(27.0, 3) +/// |> should.equal(Ok(3.0)) +/// +/// floatx.nth_root(256.0, 4) +/// |> should.equal(Ok(4.0)) +/// +/// floatx.nth_root(-1.0, 2) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn nth_root(x: Float, n: Int) -> Result(Float, String) { + // In the following check: + // 1. If x is negative then return an error as it will otherwise be an + // imaginary number + case x <. 0.0 { + True -> + "Invalid input argument: x < 0. Valid input is x > 0" + |> Error + False -> + case n >= 1 { + True -> { + let assert Ok(result) = power(x, 1.0 /. int.to_float(n)) + result + |> Ok + } + False -> + "Invalid input argument: n < 1. Valid input is n >= 2." + |> Error + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The mathematical constant pi: $$\pi \approx 3.1415\dots$$ +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn pi() -> Float { + do_pi() +} + +@external(erlang, "math", "pi") +@external(javascript, "../../maths.mjs", "pi") +fn do_pi() -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The mathematical constant tau: $$\tau = 2 \cdot \pi \approx 6.283\dots$$ +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn tau() -> Float { + 2.0 *. pi() +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Euler's number $$e \approx 2.71828\dots$$. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// // Test that the constant is approximately equal to 2.7128... +/// floatx.e() +/// |> floatx.is_close(2.7128, 0.0, 0.000001) +/// |> should.be_true() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn e() -> Float { + exponential(1.0) +} diff --git a/src/gleam_community/maths/float.gleam b/src/gleam_community/maths/float.gleam deleted file mode 100644 index ae6a4db..0000000 --- a/src/gleam_community/maths/float.gleam +++ /dev/null @@ -1,2421 +0,0 @@ -//// -//// -//// -//// -//// -//// -//// A module containing mathematical functions and constants that apply to floats. -//// -//// --- -//// -//// * **Rounding functions** -//// * [`ceiling`](#ceiling) -//// * [`floor`](#floor) -//// * [`truncate`](#truncate) -//// * [`round`](#round) -//// * **Sign and absolute value functions** -//// * [`absolute_difference`](#absolute_difference) -//// * [`sign`](#sign) -//// * [`copy_sign`](#copy_sign) -//// * [`flip_sign`](#flip_sign) -//// * **Powers, logs and roots** -//// * [`exponential`](#exponential) -//// * [`natural_logarithm`](#natural_logarithm) -//// * [`logarithm`](#logarithm) -//// * [`logarithm_2`](#logarithm_2) -//// * [`logarithm_10`](#logarithm_10) -//// * [`power`](#power) -//// * [`square_root`](#square_root) -//// * [`cube_root`](#cube_root) -//// * [`nth_root`](#nth_root) -//// * [`hypotenuse`](#hypotentuse) -//// * **Trigonometric and hyperbolic functions** -//// * [`acos`](#acos) -//// * [`acosh`](#acosh) -//// * [`asin`](#asin) -//// * [`asinh`](#asinh) -//// * [`atan`](#atan) -//// * [`atan2`](#atan2) -//// * [`atanh`](#atanh) -//// * [`cos`](#cos) -//// * [`cosh`](#cosh) -//// * [`sin`](#sin) -//// * [`sinh`](#sinh) -//// * [`tan`](#tan) -//// * [`tanh`](#tanh) -//// * [`to_radian`](#to_radian) -//// * [`to_degree`](#to_degree) -//// * **Misc. mathematical functions** -//// * [`minimum`](#minimum) -//// * [`maximum`](#maximum) -//// * [`minmax`](#minmax) -//// * **Special mathematical functions** -//// * [`beta`](#beta) -//// * [`error`](#erf) -//// * [`gamma`](#gamma) -//// * [`incomplete_gamma`](#incomplete_gamma) -//// * **Mathematical constants** -//// * [`pi`](#pi) -//// * [`tau`](#tau) -//// * [`e`](#e) -//// * **Tests** -//// * [`is_close`](#is_close) -//// * **Misc. functions** -//// * [`to_int`](#to_int) - -import gleam/list -import gleam/int -import gleam/float -import gleam/io -import gleam/option - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The ceiling function rounds a given input value $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is larger than or equal to the input $$x$$. -/// -/// Note: The ceiling function is used as an alias for the rounding function [`round`](#round) with rounding mode `RoundUp`. -/// -///
-/// Details -/// -/// For example, $$12.0654$$ is rounded to: -/// - $$13.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) -/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.066$$ for 3 digits after the decimal point (`digits = 3`) -/// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// For example, $$12.0654$$ is rounded to: -/// - $$20.0$$ for 1 digit places before the decimal point (`digit = -1`) -/// - $$100.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$1000.0$$ for 3 digits before the decimal point (`digits = -3`) -/// -///
-/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam/option -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.ceiling(12.0654, option.Some(1)) -/// |> should.equal(Ok(12.1)) -/// -/// floatx.ceiling(12.0654, option.Some(2)) -/// |> should.equal(Ok(12.07)) -/// -/// floatx.ceiling(12.0654, option.Some(3)) -/// |> should.equal(Ok(12.066)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn ceiling(x: Float, digits: option.Option(Int)) -> Result(Float, String) { - round(x, digits, option.Some(RoundUp)) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The floor function rounds input $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is less than or equal to the input $$x$$. -/// -/// Note: The floor function is used as an alias for the rounding function [`round`](#round) with rounding mode `RoundDown`. -/// -///
-/// Details -/// -/// For example, $$12.0654$$ is rounded to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.0$$ for 1 digits after the decimal point (`digits = 1`) -/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) -/// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) -/// -///
-/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam/option -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.floor(12.0654, option.Some(1)) -/// |> should.equal(Ok(12.0)) -/// -/// floatx.floor(12.0654, option.Some(2)) -/// |> should.equal(Ok(12.06)) -/// -/// floatx.floor(12.0654, option.Some(3)) -/// |> should.equal(Ok(12.065)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn floor(x: Float, digits: option.Option(Int)) -> Result(Float, String) { - round(x, digits, option.Some(RoundDown)) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The truncate function rounds a given input $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is less than or equal to the absolute value of the input $$x$$. -/// -/// Note: The truncate function is used as an alias for the rounding function [`round`](#round) with rounding mode `RoundToZero`. -/// -///
-/// Details -/// -/// For example, $$12.0654$$ is rounded to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.0$$ for 1 digits after the decimal point (`digits = 1`) -/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) -/// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) -/// -///
-/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam/option -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.truncate(12.0654, option.Some(1)) -/// |> should.equal(Ok(12.0)) -/// -/// floatx.truncate(12.0654, option.Some(2)) -/// |> should.equal(Ok(12.0)) -/// -/// floatx.truncate(12.0654, option.Some(3)) -/// |> should.equal(Ok(12.0)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn truncate(x: Float, digits: option.Option(Int)) -> Result(Float, String) { - round(x, digits, option.Some(RoundToZero)) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function rounds a float to a specific number of digits (after the decimal place or before if negative) using a specified rounding mode. -/// -/// Valid rounding modes include: -/// - `RoundNearest` (default): The input $$x$$ is rounded to the nearest integer value (at the specified digit) with ties (fractional values of 0.5) being rounded to the nearest even integer. -/// - `RoundTiesAway`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) with ties (fractional values of 0.5) being rounded away from zero (C/C++ rounding behavior). -/// - `RoundTiesUp`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) with ties (fractional values of 0.5) being rounded towards $$+\infty$$ (Java/JavaScript rounding behaviour). -/// - `RoundToZero`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) that is less than or equal to the absolute value of the input $$x$$. An alias for this rounding mode is [`truncate`](#truncate). -/// - `RoundDown`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) that is less than or equal to the input $$x$$. An alias for this rounding mode is [`floor`](#floor). -/// - `RoundUp`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) that is larger than or equal to the input $$x$$. An alias for this rounding mode is [`ceiling`](#ceiling). -/// -///
-/// Details -/// -/// The `RoundNearest` rounding mode, rounds $$12.0654$$ to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) -/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) -/// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) -/// -/// The `RoundTiesAway` rounding mode, rounds $$12.0654$$ to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) -/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) -/// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) -/// -/// The `RoundTiesUp` rounding mode, rounds $$12.0654$$ to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.1$$ for 1 digits after the decimal point (`digits = 1`) -/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) -/// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) -/// -/// The `RoundToZero` rounding mode, rounds $$12.0654$$ to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.0$$ for 1 digit after the decimal point (`digits = 1`) -/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) -/// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) -/// -/// The `RoundDown` rounding mode, rounds $$12.0654$$ to: -/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.0$$ for 1 digits after the decimal point (`digits = 1`) -/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) -/// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) -/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) -/// -/// The `RoundUp` rounding mode, rounds $$12.0654$$ to: -/// - $$13.0$$ for 0 digits after the decimal point (`digits = 0`) -/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) -/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) -/// - $$12.066$$ for 3 digits after the decimal point (`digits = 3`) -/// -/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. -/// - $$20.0$$ for 1 digit places before the decimal point (`digit = -1`) -/// - $$100.0$$ for 2 digits before the decimal point (`digits = -2`) -/// - $$1000.0$$ for 3 digits before the decimal point (`digits = -3`) -/// -///
-/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam/option -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// // The default number of digits is 0 if None is provided -/// floatx.round(12.0654, option.None, option.Some(floatx.RoundNearest)) -/// |> should.equal(Ok(12.0)) -/// -/// // The default rounding mode is "RoundNearest" if None is provided -/// floatx.round(12.0654, option.None, option.None) -/// |> should.equal(Ok(12.0)) -/// -/// // Try different rounding modes -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundNearest)) -/// |> should.equal(Ok(12.07)) -/// -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundTiesAway)) -/// |> should.equal(Ok(12.07)) -/// -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundTiesUp)) -/// |> should.equal(Ok(12.07)) -/// -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundToZero)) -/// |> should.equal(Ok(12.06)) -/// -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundDown)) -/// |> should.equal(Ok(12.06)) -/// -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundUp)) -/// |> should.equal(Ok(12.07)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn round( - x: Float, - digits: option.Option(Int), - mode: option.Option(RoundingMode), -) -> Result(Float, String) { - case digits { - option.Some(a) -> { - let assert Ok(p) = power(10.0, int.to_float(a)) - // Round the given input x using at the specified digit - do_round(p, x, mode) - } - // Round the given input x using at the default digit - option.None -> do_round(1.0, x, mode) - } -} - -pub type RoundingMode { - RoundNearest - RoundTiesAway - RoundTiesUp - RoundToZero - RoundDown - RoundUp -} - -fn do_round( - p: Float, - x: Float, - mode: option.Option(RoundingMode), -) -> Result(Float, String) { - case mode { - // Determine the rounding mode - option.Some(RoundNearest) -> - round_to_nearest(p, x) - |> Ok - option.Some(RoundTiesAway) -> - round_ties_away(p, x) - |> Ok - option.Some(RoundTiesUp) -> - round_ties_up(p, x) - |> Ok - option.Some(RoundToZero) -> - round_to_zero(p, x) - |> Ok - option.Some(RoundDown) -> - round_down(p, x) - |> Ok - option.Some(RoundUp) -> - round_up(p, x) - |> Ok - // Otherwise, use the Default rounding mode - option.None -> - round_to_nearest(p, x) - |> Ok - } -} - -fn round_to_nearest(p: Float, x: Float) -> Float { - let xabs: Float = float.absolute_value(x) *. p - let xabs_truncated: Float = truncate_float(xabs) - let remainder: Float = xabs -. xabs_truncated - case remainder { - _ if remainder >. 0.5 -> sign(x) *. truncate_float(xabs +. 1.0) /. p - _ if remainder == 0.5 -> { - let assert Ok(is_even) = int.modulo(to_int(xabs), 2) - case is_even == 0 { - True -> sign(x) *. xabs_truncated /. p - False -> sign(x) *. truncate_float(xabs +. 1.0) /. p - } - } - _ -> sign(x) *. xabs_truncated /. p - } -} - -fn round_ties_away(p: Float, x: Float) -> Float { - let xabs: Float = float.absolute_value(x) *. p - let remainder: Float = xabs -. truncate_float(xabs) - case remainder { - _ if remainder >=. 0.5 -> sign(x) *. truncate_float(xabs +. 1.0) /. p - _ -> sign(x) *. truncate_float(xabs) /. p - } -} - -fn round_ties_up(p: Float, x: Float) -> Float { - let xabs: Float = float.absolute_value(x) *. p - let xabs_truncated: Float = truncate_float(xabs) - let remainder: Float = xabs -. xabs_truncated - case remainder { - _ if remainder >=. 0.5 && x >=. 0.0 -> - sign(x) *. truncate_float(xabs +. 1.0) /. p - _ -> sign(x) *. xabs_truncated /. p - } -} - -// Rounding mode: ToZero / Truncate -fn round_to_zero(p: Float, x: Float) -> Float { - truncate_float(x *. p) /. p -} - -fn truncate_float(x: Float) -> Float { - do_truncate_float(x) -} - -if erlang { - external fn do_truncate_float(Float) -> Float = - "erlang" "trunc" -} - -if javascript { - external fn do_truncate_float(Float) -> Float = - "../../maths.mjs" "truncate" -} - -// Rounding mode: Down / Floor -fn round_down(p: Float, x: Float) -> Float { - do_floor(x *. p) /. p -} - -if erlang { - external fn do_floor(Float) -> Float = - "math" "floor" -} - -if javascript { - external fn do_floor(Float) -> Float = - "../../maths.mjs" "floor" -} - -// Rounding mode: Up / Ceiling -fn round_up(p: Float, x: Float) -> Float { - do_ceiling(x *. p) /. p -} - -if erlang { - external fn do_ceiling(Float) -> Float = - "math" "ceil" -} - -if javascript { - external fn do_ceiling(Float) -> Float = - "../../maths.mjs" "ceiling" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function returns the integral part of a given floating point value. -/// That is, everything after the decimal point of a given floating point value is discarded and only the integer value before the decimal point is returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam/option -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.to_int(12.0654) -/// |> should.equal(12) -/// -/// // Note: Making the following function call is equivalent -/// // but instead of returning a value of type 'Int' a value -/// // of type 'Float' is returned. -/// floatx.round(12.0654, option.Some(0), option.Some(floatx.RoundToZero)) -/// |> should.equal(Ok(12.0)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn to_int(x: Float) -> Int { - do_to_int(x) -} - -if erlang { - external fn do_to_int(Float) -> Int = - "erlang" "trunc" -} - -if javascript { - external fn do_to_int(Float) -> Int = - "../../maths.mjs" "truncate" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The inverse cosine function: -/// -/// \\[ -/// \forall x \in \[-1, 1\], \\; \cos^{-1}{(x)} = y \in \[0, \pi \] -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\[-1, 1\]$$ as input and returns a -/// numeric value $$y$$ that lies in the range $$\[0, \pi \]$$ (an angle in radians). -/// If the input value is outside the domain of the function an error is returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.acos(1.0) -/// |> should.equal(Ok(0.0)) -/// -/// floatx.acos(1.1) -/// |> should.be_error() -/// -/// floatx.acos(-1.1) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn acos(x: Float) -> Result(Float, String) { - case x >=. -1.0 && x <=. 1.0 { - True -> - do_acos(x) - |> Ok - False -> - "Invalid input argument: x >= -1 or x <= 1. Valid input is -1. <= x <= 1." - |> Error - } -} - -if erlang { - external fn do_acos(Float) -> Float = - "math" "acos" -} - -if javascript { - external fn do_acos(Float) -> Float = - "../../maths.mjs" "acos" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The inverse hyperbolic cosine function: -/// -/// \\[ -/// \forall x \in [1, +\infty\), \\; \cosh^{-1}{(x)} = y \in \[0, +\infty\) -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\[1, +\infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\[0, +\infty\)$$ (an angle in radians). -/// If the input value is outside the domain of the function an error is returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.acosh(1.0) -/// |> should.equal(Ok(0.0)) -/// -/// floatx.acosh(0.0) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn acosh(x: Float) -> Result(Float, String) { - case x >=. 1.0 { - True -> - do_acosh(x) - |> Ok - False -> - "Invalid input argument: x < 1. Valid input is x >= 1." - |> Error - } -} - -if erlang { - external fn do_acosh(Float) -> Float = - "math" "acosh" -} - -if javascript { - external fn do_acosh(Float) -> Float = - "../../maths.mjs" "acosh" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The inverse sine function: -/// -/// \\[ -/// \forall x \in \[-1, 1\], \\; \sin^{-1}{(x)} = y \in \(-\infty, +\infty\) -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\[-1, 1\]$$ as input and returns a numeric -/// value $$y$$ that lies in the range $$\[-\frac{\pi}{2}, \frac{\pi}{2}\]$$ (an angle in radians). -/// If the input value is outside the domain of the function an error is returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.asin(0.0) -/// |> should.equal(Ok(0.0)) -/// -/// floatx.asin(1.1) -/// |> should.be_error() -/// -/// floatx.asin(-1.1) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn asin(x: Float) -> Result(Float, String) { - case x >=. -1.0 && x <=. 1.0 { - True -> - do_asin(x) - |> Ok - False -> - "Invalid input argument: x >= -1 or x <= 1. Valid input is -1. <= x <= 1." - |> Error - } -} - -if erlang { - external fn do_asin(Float) -> Float = - "math" "asin" -} - -if javascript { - external fn do_asin(Float) -> Float = - "../../maths.mjs" "asin" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The inverse hyperbolic sine function: -/// -/// \\[ -/// \forall x \in \(-\infty, \infty\), \\; \sinh^{-1}{(x)} = y \in \(-\infty, +\infty\) -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, +\infty\)$$ (an angle in radians). -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.asinh(0.0) -/// |> should.equal(0.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn asinh(x: Float) -> Float { - do_asinh(x) -} - -if erlang { - external fn do_asinh(Float) -> Float = - "math" "asinh" -} - -if javascript { - external fn do_asinh(Float) -> Float = - "../../maths.mjs" "asinh" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The inverse tangent function: -/// -/// \\[ -/// \forall x \in \(-\infty, \infty\), \\; \tan^{-1}{(x)} = y \in \[-\frac{\pi}{2}, \frac{\pi}{2}\] -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\[-\frac{\pi}{2}, \frac{\pi}{2}\]$$ (an angle in radians). -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.atan(0.0) -/// |> should.equal(0.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn atan(x: Float) -> Float { - do_atan(x) -} - -if erlang { - external fn do_atan(Float) -> Float = - "math" "atan" -} - -if javascript { - external fn do_atan(Float) -> Float = - "../../maths.mjs" "atan" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The inverse 2-argument tangent function: -/// -/// \\[ -/// \text{atan2}(y, x) = -/// \begin{cases} -/// \tan^{-1}(\frac y x) &\text{if } x > 0, \\\\ -/// \tan^{-1}(\frac y x) + \pi &\text{if } x < 0 \text{ and } y \ge 0, \\\\ -/// \tan^{-1}(\frac y x) - \pi &\text{if } x < 0 \text{ and } y < 0, \\\\ -/// +\frac{\pi}{2} &\text{if } x = 0 \text{ and } y > 0, \\\\ -/// -\frac{\pi}{2} &\text{if } x = 0 \text{ and } y < 0, \\\\ -/// \text{undefined} &\text{if } x = 0 \text{ and } y = 0. -/// \end{cases} -/// \\] -/// -/// The function returns the angle in radians from the x-axis to the line containing the -/// origin $$\(0, 0\)$$ and a point given as input with coordinates $$\(x, y\)$$. The numeric value -/// returned by $$\text{atan2}(y, x)$$ is in the range $$\[-\pi, \pi\]$$. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.atan2(0.0, 0.0) -/// |> should.equal(0.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn atan2(y: Float, x: Float) -> Float { - do_atan2(y, x) -} - -if erlang { - external fn do_atan2(Float, Float) -> Float = - "math" "atan2" -} - -if javascript { - external fn do_atan2(Float, Float) -> Float = - "../../maths.mjs" "atan2" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The inverse hyperbolic tangent function: -/// -/// \\[ -/// \forall x \in \(-1, 1\), \\; \tanh^{-1}{(x)} = y \in \(-\infty, +\infty\) -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(-1, 1\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$ (an angle in radians). -/// If the input value is outside the domain of the function an error is returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.atanh(0.0) -/// |> should.equal(Ok(0.0)) -/// -/// floatx.atanh(1.0) -/// |> should.be_error() -/// -/// floatx.atanh(-1.0) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn atanh(x: Float) -> Result(Float, String) { - case x >. -1.0 && x <. 1.0 { - True -> - do_atanh(x) - |> Ok - False -> - "Invalid input argument: x > -1 or x < 1. Valid input is -1. < x < 1." - |> Error - } -} - -if erlang { - external fn do_atanh(Float) -> Float = - "math" "atanh" -} - -if javascript { - external fn do_atanh(Float) -> Float = - "../../maths.mjs" "atanh" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The cosine function: -/// -/// \\[ -/// \forall x \in \(-\infty, +\infty\), \\; \cos{(x)} = y \in \[-1, 1\] -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ (an angle in radians) -/// as input and returns a numeric value $$y$$ that lies in the range $$\[-1, 1\]$$. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.cos(0.0) -/// |> should.equal(1.0) -/// -/// floatx.cos(floatx.pi()) -/// |> should.equal(-1.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn cos(x: Float) -> Float { - do_cos(x) -} - -if erlang { - external fn do_cos(Float) -> Float = - "math" "cos" -} - -if javascript { - external fn do_cos(Float) -> Float = - "../../maths.mjs" "cos" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The hyperbolic cosine function: -/// -/// \\[ -/// \forall x \in \(-\infty, \infty\), \\; \cosh{(x)} = y \in \(-\infty, +\infty\) -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ as input (an angle in radians) -/// and returns a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. -/// If the input value is too large an overflow error might occur. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.cosh(0.0) -/// |> should.equal(0.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn cosh(x: Float) -> Float { - do_cosh(x) -} - -if erlang { - external fn do_cosh(Float) -> Float = - "math" "cosh" -} - -if javascript { - external fn do_cosh(Float) -> Float = - "../../maths.mjs" "cosh" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The exponential function: -/// -/// \\[ -/// \forall x \in \(-\infty, \infty\), \\; e^{(x)} = y \in \(0, +\infty\) -/// \\] -/// -/// $$e \approx 2.71828\dots$$ is Eulers' number. -/// -/// Note: If the input value $$x$$ is too large an overflow error might occur. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.exponential(0.0) -/// |> should.equal(1.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn exponential(x: Float) -> Float { - do_exponential(x) -} - -if erlang { - external fn do_exponential(Float) -> Float = - "math" "exp" -} - -if javascript { - external fn do_exponential(Float) -> Float = - "../../maths.mjs" "exponential" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The base $$b$$ logarithm function (computed through the "change of base" formula): -/// -/// \\[ -/// \forall x \in \(0, \infty\) \textnormal{ and } b > 1, \\; \log_{b}{(x)} = y \in \(-\infty, +\infty\) -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ and a base $$b > 1$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. -/// If the input value is outside the domain of the function an error is returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam/option -/// import gleam_community/maths/float as floatx -/// -/// pub fn example () { -/// floatx.logarithm(1.0, option.Some(10.0)) -/// |> should.equal(Ok(0.0)) -/// -/// floatx.logarithm(floatx.e(), option.Some(floatx.e())) -/// |> should.equal(Ok(1.0)) -/// -/// floatx.logarithm(-1.0, option.Some(2.0)) -/// |> should.be_error() -/// } -///
-/// -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn logarithm(x: Float, base: option.Option(Float)) -> Result(Float, String) { - case x >. 0.0 { - True -> - case base { - option.Some(a) -> - case a >. 0.0 && a != 1.0 { - True -> { - // Apply the "change of base formula" - let assert Ok(numerator) = logarithm_10(x) - let assert Ok(denominator) = logarithm_10(a) - numerator /. denominator - |> Ok - } - False -> - "Invalid input argument: base <= 0 or base == 1. Valid input is base > 0 and base != 1." - |> Error - } - _ -> - "Invalid input argument: base <= 0 or base == 1. Valid input is base > 0 and base != 1." - |> Error - } - _ -> - "Invalid input argument: x <= 0. Valid input is x > 0." - |> Error - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The natural logarithm function: -/// -/// \\[ -/// \forall x \in \(0, \infty\), \\; \log_{e}{(x)} = y \in \(-\infty, +\infty\) -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. -/// If the input value is outside the domain of the function an error is returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example () { -/// floatx.natural_logarithm(1.0) -/// |> should.equal(Ok(0.0)) -/// -/// floatx.natural_logarithm(floatx.e()) -/// |> should.equal(Ok(1.0)) -/// -/// floatx.natural_logarithm(-1.0) -/// |> should.be_error() -/// } -///
-/// -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn natural_logarithm(x: Float) -> Result(Float, String) { - case x >. 0.0 { - True -> - do_natural_logarithm(x) - |> Ok - False -> - "Invalid input argument: x <= 0. Valid input is x > 0." - |> Error - } -} - -if erlang { - external fn do_natural_logarithm(Float) -> Float = - "math" "log" -} - -if javascript { - external fn do_natural_logarithm(Float) -> Float = - "../../maths.mjs" "logarithm" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The base-10 logarithm function: -/// -/// \\[ -/// \forall x \in \(0, \infty), \\; \log_{10}{(x)} = y \in \(-\infty, +\infty\) -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. -/// If the input value is outside the domain of the function an error is returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example () { -/// floatx.logarithm_10(1.0) -/// |> should.equal(Ok(0.0)) -/// -/// floatx.logarithm_10(10.0) -/// |> should.equal(Ok(1.0)) -/// -/// floatx.logarithm_10(-1.0) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn logarithm_10(x: Float) -> Result(Float, String) { - case x >. 0.0 { - True -> - do_logarithm_10(x) - |> Ok - False -> - "Invalid input argument: x <= 0. Valid input is x > 0." - |> Error - } -} - -if erlang { - external fn do_logarithm_10(Float) -> Float = - "math" "log10" -} - -if javascript { - external fn do_logarithm_10(Float) -> Float = - "../../maths.mjs" "logarithm_10" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The The base-2 logarithm function: -/// -/// \\[ -/// \forall x \in \(0, \infty), \\; \log_{2}{(x)} = y \in \(-\infty, +\infty\) -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(0, \infty\)$$ as input and returns -/// a numeric value $$y$$ that lies in the range $$\(-\infty, \infty\)$$. -/// If the input value is outside the domain of the function an error is returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example () { -/// floatx.logarithm_2(1.0) -/// |> should.equal(Ok(0.0)) -/// -/// floatx.logarithm_2(2.0) -/// |> should.equal(Ok(1.0)) -/// -/// floatx.logarithm_2(-1.0) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn logarithm_2(x: Float) -> Result(Float, String) { - case x >. 0.0 { - True -> - do_logarithm_2(x) - |> Ok - False -> - "Invalid input argument: x <= 0. Valid input is x > 0." - |> Error - } -} - -if erlang { - external fn do_logarithm_2(Float) -> Float = - "math" "log2" -} - -if javascript { - external fn do_logarithm_2(Float) -> Float = - "../../maths.mjs" "logarithm_2" -} - -///
-/// -/// Back to top ↑ -/// -///
-/// -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The exponentiation function: $$y = x^{a}$$. -/// -/// Note that the function is not defined if: -/// 1. The base is negative ($$x < 0$$) and the exponent is fractional -/// ($$a = \frac{n}{m}$$ is an irrreducible fraction). An error will be returned -/// as an imaginary number will otherwise have to be returned. -/// 2. The base is zero ($$x = 0$$) and the exponent is negative ($$a < 0$$) then the -/// expression is equivalent to the exponent $$y$$ divided by $$0$$ and an -/// error will have to be returned as the expression is otherwise undefined. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.power(2., -1.) -/// |> should.equal(Ok(0.5)) -/// -/// floatx.power(2., 2.) -/// |> should.equal(Ok(4.0)) -/// -/// floatx.power(-1., 0.5) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn power(x: Float, y: Float) -> Result(Float, String) { - let fractional: Bool = do_ceiling(y) -. y >. 0.0 - // In the following check: - // 1. If the base (x) is negative and the exponent (y) is fractional - // then return an error as it will otherwise be an imaginary number - // 2. If the base (x) is 0 and the exponent (y) is negative then the - // expression is equivalent to the exponent (y) divided by 0 and an - // error should be returned - case x <. 0.0 && fractional || x == 0.0 && y <. 0.0 { - True -> - "Invalid input argument: x < 0 and y is fractional or x = 0 and y < 0." - |> Error - False -> - do_power(x, y) - |> Ok - } -} - -if erlang { - external fn do_power(Float, Float) -> Float = - "math" "pow" -} - -if javascript { - external fn do_power(Float, Float) -> Float = - "../../maths.mjs" "power" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The square root function: $$y = \sqrt[2]{x} = x^{\frac{1}{2}}$$. -/// -/// Note that the function is not defined if: -/// 1. The input is negative ($$x < 0$$). An error will be returned -/// as an imaginary number will otherwise have to be returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.square_root(1.0) -/// |> should.equal(Ok(1.0)) -/// -/// floatx.square_root(4.0) -/// |> should.equal(Ok(2.0)) -/// -/// floatx.square_root(-1.0) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn square_root(x: Float) -> Result(Float, String) { - // In the following check: - // 1. If x is negative then return an error as it will otherwise be an - // imaginary number - case x <. 0.0 { - True -> - "Invalid input argument: x < 0." - |> Error - False -> { - let assert Ok(result) = power(x, 1.0 /. 2.0) - result - |> Ok - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The cube root function: $$y = \sqrt[3]{x} = x^{\frac{1}{3}}$$. -/// -/// Note that the function is not defined if: -/// 1. The input is negative ($$x < 0$$). An error will be returned -/// as an imaginary number will otherwise have to be returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.cube_root(1.0) -/// |> should.equal(Ok(1.0)) -/// -/// floatx.cube_root(27.0) -/// |> should.equal(Ok(3.0)) -/// -/// floatx.cube_root(-1.0) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn cube_root(x: Float) -> Result(Float, String) { - // In the following check: - // 1. If x is negative then return an error as it will otherwise be an - // imaginary number - case x <. 0.0 { - True -> - "Invalid input argument: x < 0." - |> Error - False -> { - let assert Ok(result) = power(x, 1.0 /. 3.0) - result - |> Ok - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The $$n$$'th root function: $$y = \sqrt[n]{x} = x^{\frac{1}{n}}$$. -/// -/// Note that the function is not defined if: -/// 1. The input is negative ($$x < 0$$). An error will be returned -/// as an imaginary number will otherwise have to be returned. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.nth_root(1.0, 2) -/// |> should.equal(Ok(1.0)) -/// -/// floatx.nth_root(27.0, 3) -/// |> should.equal(Ok(3.0)) -/// -/// floatx.nth_root(256.0, 4) -/// |> should.equal(Ok(4.0)) -/// -/// floatx.nth_root(-1.0, 2) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn nth_root(x: Float, n: Int) -> Result(Float, String) { - // In the following check: - // 1. If x is negative then return an error as it will otherwise be an - // imaginary number - case x <. 0.0 { - True -> - "Invalid input argument: x < 0. Valid input is x > 0" - |> Error - False -> - case n >= 1 { - True -> { - let assert Ok(result) = power(x, 1.0 /. int.to_float(n)) - result - |> Ok - } - False -> - "Invalid input argument: n < 1. Valid input is n >= 2." - |> Error - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// A function to compute the hypotenuse of a right-angled triangle: $$\sqrt[2]{x^2 + y^2}$$. -/// The function can also be used to calculate the Euclidean distance in 2 dimensions. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.hypotenuse(0.0, 0.0) -/// |> should.equal(0.0) -/// -/// floatx.hypotenuse(1.0, 0.0) -/// |> should.equal(1.0) -/// -/// floatx.hypotenuse(0.0, 1.0) -/// |> should.equal(1.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn hypotenuse(x: Float, y: Float) -> Float { - let assert Ok(term1) = power(x, 2.0) - let assert Ok(term2) = power(y, 2.0) - let assert Ok(h) = square_root(term1 +. term2) - h -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The sine function: -/// -/// \\[ -/// \forall x \in \(-\infty, +\infty\), \\; \sin{(x)} = y \in \[-1, 1\] -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ (an angle in radians) -/// as input and returns a numeric value $$y$$ that lies in the range $$\[-1, 1\]$$. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.sin(0.0) -/// |> should.equal(0.0) -/// -/// floatx.sin(0.5 *. floatx.pi()) -/// |> should.equal(1.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn sin(x: Float) -> Float { - do_sin(x) -} - -if erlang { - external fn do_sin(Float) -> Float = - "math" "sin" -} - -if javascript { - external fn do_sin(Float) -> Float = - "../../maths.mjs" "sin" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The hyperbolic sine function: -/// -/// \\[ -/// \forall x \in \(-\infty, +\infty\), \\; \sinh{(x)} = y \in \(-\infty, +\infty\) -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input -/// (an angle in radians) and returns a numeric value $$y$$ that lies in the range -/// $$\(-\infty, +\infty\)$$. If the input value is too large an overflow error might -/// occur. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.sinh(0.0) -/// |> should.equal(0.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn sinh(x: Float) -> Float { - do_sinh(x) -} - -if erlang { - external fn do_sinh(Float) -> Float = - "math" "sinh" -} - -if javascript { - external fn do_sinh(Float) -> Float = - "../../maths.mjs" "sinh" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The tangent function: -/// -/// \\[ -/// \forall x \in \(-\infty, +\infty\), \\; \tan{(x)} = y \in \(-\infty, +\infty\) -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(-\infty, +\infty\)$$ as input -/// (an angle in radians) and returns a numeric value $$y$$ that lies in the range -/// $$\(-\infty, +\infty\)$$. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.tan(0.0) -/// |> should.equal(0.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn tan(x: Float) -> Float { - do_tan(x) -} - -if erlang { - external fn do_tan(Float) -> Float = - "math" "tan" -} - -if javascript { - external fn do_tan(Float) -> Float = - "../../maths.mjs" "tan" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The hyperbolic tangent function: -/// -/// \\[ -/// \forall x \in \(-\infty, \infty\), \\; \tanh{(x)} = y \in \[-1, 1\] -/// \\] -/// -/// The function takes a number $$x$$ in its domain $$\(-\infty, \infty\)$$ as input (an angle in radians) -/// and returns a numeric value $$y$$ that lies in the range $$\[-1, 1\]$$. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example () { -/// floatx.tanh(0.0) -/// |> should.equal(0.0) -/// -/// floatx.tanh(25.0) -/// |> should.equal(1.0) -/// -/// floatx.tanh(-25.0) -/// |> should.equal(-1.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn tanh(x: Float) -> Float { - do_tanh(x) -} - -if erlang { - external fn do_tanh(Float) -> Float = - "math" "tanh" -} - -if javascript { - external fn do_tanh(Float) -> Float = - "../../maths.mjs" "tanh" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Convert a value in degrees to a value measured in radians. -/// That is, $$1 \text{ radians } = \frac{180}{\pi} \text{ degrees }$$. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.to_degree(0.0) -/// |> should.equal(0.0) -/// -/// floatx.to_degree(2. *. floatx.pi()) -/// |> should.equal(360.) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn to_degree(x: Float) -> Float { - x *. 180.0 /. pi() -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Convert a value in degrees to a value measured in radians. -/// That is, $$1 \text{ degrees } = \frac{\pi}{180} \text{ radians }$$. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.to_radian(360.) -/// |> should.equal(2. *. floatx.pi()) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn to_radian(x: Float) -> Float { - x *. pi() /. 180.0 -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The minimum function that takes two arguments $$x, y \in \mathbb{R}$$ and returns the smallest of the two. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.minimum(2.0, 1.5) -/// |> should.equal(1.5) -/// -/// floatx.minimum(1.5, 2.0) -/// |> should.equal(1.5) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn minimum(x: Float, y: Float) -> Float { - case x <. y { - True -> x - False -> y - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The maximum function that takes two arguments $$x, y \in \mathbb{R}$$ and returns the largest of the two. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.maximum(2.0, 1.5) -/// |> should.equal(1.5) -/// -/// floatx.maximum(1.5, 2.0) -/// |> should.equal(1.5) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn maximum(x: Float, y: Float) -> Float { - case x >. y { - True -> x - False -> y - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The minmax function that takes two arguments $$x, y \in \mathbb{R}$$ and returns a tuple with the smallest value first and largest second. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.minmax(2.0, 1.5) -/// |> should.equal(#(1.5, 2.0)) -/// -/// floatx.minmax(1.5, 2.0) -/// |> should.equal(#(1.5, 2.0)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn minmax(x: Float, y: Float) -> #(Float, Float) { - #(minimum(x, y), maximum(x, y)) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The sign function that returns the sign of the input, indicating whether it is positive (+1), negative (-1), or zero (0). -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn sign(x: Float) -> Float { - do_sign(x) -} - -if erlang { - fn do_sign(x: Float) -> Float { - case x <. 0.0 { - True -> -1.0 - False -> - case x == 0.0 { - True -> 0.0 - False -> 1.0 - } - } - } -} - -if javascript { - external fn do_sign(Float) -> Float = - "../../maths.mjs" "sign" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function takes two arguments $$x$$ and $$y$$ and returns $$x$$ such that it has the same sign as $$y$$. -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn copy_sign(x: Float, y: Float) -> Float { - case sign(x) == sign(y) { - // x and y have the same sign, just return x - True -> x - // x and y have different signs: - // - x is positive and y is negative, then flip sign of x - // - x is negative and y is positive, then flip sign of x - False -> flip_sign(x) - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function flips the sign of a given input value. -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn flip_sign(x: Float) -> Float { - -1.0 *. x -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The beta function over the real numbers: -/// -/// \\[ -/// \text{B}(x, y) = \frac{\Gamma(x) \cdot \Gamma(y)}{\Gamma(x + y)} -/// \\] -/// -/// The beta function is evaluated through the use of the gamma function. -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn beta(x: Float, y: Float) -> Float { - gamma(x) *. gamma(y) /. gamma(x +. y) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The error function. -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn erf(x: Float) -> Float { - let [a1, a2, a3, a4, a5]: List(Float) = [ - 0.254829592, -0.284496736, 1.421413741, -1.453152027, 1.061405429, - ] - let p: Float = 0.3275911 - - let sign: Float = sign(x) - let x: Float = float.absolute_value(x) - - // Formula 7.1.26 given in Abramowitz and Stegun. - let t: Float = 1.0 /. { 1.0 +. p *. x } - let y: Float = - 1.0 -. { { { { a5 *. t +. a4 } *. t +. a3 } *. t +. a2 } *. t +. a1 } *. t *. exponential( - -1.0 *. x *. x, - ) - sign *. y -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The gamma function over the real numbers. The function is essentially equal to the -/// factorial for any positive integer argument: $$\Gamma(n) = (n - 1)!$$ -/// -/// The implemented gamma function is approximated through Lanczos approximation -/// using the same coefficients used by the GNU Scientific Library. -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn gamma(x: Float) -> Float { - gamma_lanczos(x) -} - -const lanczos_g: Float = 7.0 - -const lanczos_p: List(Float) = [ - 0.99999999999980993, 676.5203681218851, -1259.1392167224028, - 771.32342877765313, -176.61502916214059, 12.507343278686905, - -0.13857109526572012, 0.0000099843695780195716, 0.00000015056327351493116, -] - -fn gamma_lanczos(x: Float) -> Float { - case x <. 0.5 { - True -> pi() /. { sin(pi() *. x) *. gamma_lanczos(1.0 -. x) } - False -> { - let z = x -. 1.0 - let x: Float = - list.index_fold( - lanczos_p, - 0.0, - fn(acc: Float, v: Float, index: Int) { - case index > 0 { - True -> acc +. v /. { z +. int.to_float(index) } - False -> v - } - }, - ) - let t: Float = z +. lanczos_g +. 0.5 - let assert Ok(v1) = power(2.0 *. pi(), 0.5) - let assert Ok(v2) = power(t, z +. 0.5) - v1 *. v2 *. exponential(-1.0 *. t) *. x - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The lower incomplete gamma function over the real numbers. -/// -/// The implemented incomplete gamma function is evaluated through a power series -/// expansion. -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn incomplete_gamma(a: Float, x: Float) -> Result(Float, String) { - case a >. 0.0 && x >=. 0.0 { - True -> { - let assert Ok(v) = power(x, a) - v *. exponential(-1.0 *. x) *. incomplete_gamma_sum( - a, - x, - 1.0 /. a, - 0.0, - 1.0, - ) - |> Ok - } - - False -> - "Invlaid input argument: a <= 0 or x < 0. Valid input is a > 0 and x >= 0." - |> Error - } -} - -fn incomplete_gamma_sum( - a: Float, - x: Float, - t: Float, - s: Float, - n: Float, -) -> Float { - case t { - 0.0 -> s - _ -> { - let ns: Float = s +. t - let nt: Float = t *. { x /. { a +. n } } - incomplete_gamma_sum(a, x, nt, ns, n +. 1.0) - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The mathematical constant pi: $$\pi \approx 3.1415\dots$$ -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn pi() -> Float { - do_pi() -} - -if erlang { - external fn do_pi() -> Float = - "math" "pi" -} - -if javascript { - external fn do_pi() -> Float = - "../../maths.mjs" "pi" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The mathematical constant tau: $$\tau = 2 \cdot \pi \approx 6.283\dots$$ -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn tau() -> Float { - 2.0 *. pi() -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Euler's number $$e \approx 2.71828\dots$$. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// // Test that the constant is approximately equal to 2.7128... -/// floatx.e() -/// |> floatx.is_close(2.7128, 0.0, 0.000001) -/// |> should.be_true() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn e() -> Float { - exponential(1.0) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The absolute difference: -/// -/// \\[ -/// \forall x, y \in \mathbb{R}, \\; |x - y| \in \mathbb{R}_{+}. -/// \\] -/// -/// The function takes two inputs $$x$$ and $$y$$ and returns a positive float -/// value which is the the absolute difference of the inputs. -/// -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example() { -/// floatx.absolute_difference(-10.0, 10.0) -/// |> should.equal(20.0) -/// -/// floatx.absolute_difference(0.0, -2.0) -/// |> should.equal(2.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn absolute_difference(a: Float, b: Float) -> Float { - a -. b - |> float.absolute_value() -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Determine if a given value $$a$$ is close to or equivalent to a reference value -/// $$b$$ based on supplied relative $$r_{tol}$$ and absolute $$a_{tol}$$ tolerance values. -/// The equivalance of the two given values are then determined based on the equation: -/// -/// \\[ -/// \|a - b\| \leq (a_{tol} + r_{tol} \cdot \|b\|) -/// \\] -/// -/// `True` is returned if statement holds, otherwise `False` is returned. -///
-/// Example -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// -/// pub fn example () { -/// let val: Float = 99. -/// let ref_val: Float = 100. -/// // We set 'atol' and 'rtol' such that the values are equivalent -/// // if 'val' is within 1 percent of 'ref_val' +/- 0.1 -/// let rtol: Float = 0.01 -/// let atol: Float = 0.10 -/// floatx.is_close(val, ref_val, rtol, atol) -/// |> should.be_true() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn is_close(a: Float, b: Float, rtol: Float, atol: Float) -> Bool { - let x: Float = absolute_difference(a, b) - let y: Float = atol +. rtol *. float.absolute_value(b) - case x <=. y { - True -> True - False -> False - } -} diff --git a/src/gleam_community/maths/float_list.gleam b/src/gleam_community/maths/float_list.gleam deleted file mode 100644 index e8aa16b..0000000 --- a/src/gleam_community/maths/float_list.gleam +++ /dev/null @@ -1,1108 +0,0 @@ -//// -//// -//// -//// -//// -//// -//// A module containing mathematical functions applying to one or more lists of floats. -//// -//// --- -//// -//// * **Distances, sums, and products** -//// * [`sum`](#sum) -//// * [`product`](#product) -//// * [`cumulative_sum`](#cumulative_sum) -//// * [`cumulative_product`](#cumulative_product) -//// * [`norm`](#norm) -//// * [`minkowski_distance`](#minkowski_distance) -//// * [`euclidean_distance`](#euclidean_distance) -//// * [`manhatten_distance`](#manhatten_distance) -//// * **Ranges and intervals** -//// * [`arange`](#arange) -//// * [`linear_space`](#linear_space) -//// * [`logarithmic_space`](#logarithmic_space) -//// * [`geometric_space`](#geometric_space) -//// * **Misc. mathematical functions** -//// * [`maximum`](#maximum) -//// * [`minimum`](#minimum) -//// * [`extrema`](#extrema) -//// * [`arg_maximum`](#arg_maximum) -//// * [`arg_minimum`](#arg_minimum) -//// * **Tests** -//// * [`all_close`](#all_close) - -import gleam/list -import gleam/int -import gleam/float -import gleam/pair -import gleam/option -import gleam_community/maths/float as floatx -import gleam_community/maths/int as intx -import gleam/io -import gleam/iterator - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Calculcate the $$p$$-norm of a list (representing a vector): -/// -/// \\[ -/// \left( \sum_{i=1}^n \left|x_i\right|^{p} \right)^{\frac{1}{p}} -/// \\] -/// -/// In the formula, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) -/// -/// [1.0, 1.0, 1.0] -/// |> float_list.norm(1.0) -/// |> floatx.is_close(3.0, 0.0, tol) -/// |> should.be_true() -/// -/// [1.0, 1.0, 1.0] -/// |> float_list.norm(-1.0) -/// |> floatx.is_close(0.3333333333333333, 0.0, tol) -/// |> should.be_true() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn norm(arr: List(Float), p: Float) -> Float { - case arr { - [] -> 0.0 - _ -> { - let agg: Float = - arr - |> list.fold( - 0.0, - fn(acc: Float, a: Float) -> Float { - let assert Ok(result) = floatx.power(float.absolute_value(a), p) - result +. acc - }, - ) - let assert Ok(result) = floatx.power(agg, 1.0 /. p) - result - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Calculcate the Minkowski distance between two lists (representing vectors): -/// -/// \\[ -/// \left( \sum_{i=1}^n \left|x_i - y_i \right|^{p} \right)^{\frac{1}{p}} -/// \\] -/// -/// In the formula, $$p >= 1$$ is the order, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i, j$$. -/// -/// The Minkowski distance is a generalization of both the Euclidean distance ($$p=2$$) and the Manhattan distance ($$p = 1$$). -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) -/// -/// // Empty lists returns 0.0 -/// float_list.minkowski_distance([], [], 1.0) -/// |> should.equal(Ok(0.0)) -/// -/// // Differing lengths returns error -/// float_list.minkowski_distance([], [1.0], 1.0) -/// |> should.be_error() -/// -/// // Test order < 1 -/// float_list.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0) -/// |> should.be_error() -/// -/// let assert Ok(result) = float_list.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0) -/// result -/// |> floatx.is_close(3.0, 0.0, tol) -/// |> should.be_true() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn minkowski_distance( - xarr: List(Float), - yarr: List(Float), - p: Float, -) -> Result(Float, String) { - let xlen: Int = list.length(xarr) - let ylen: Int = list.length(yarr) - case xlen == ylen { - False -> - "Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)." - |> Error - True -> - case p <. 1.0 { - True -> - "Invalid input argument: p < 1. Valid input is p >= 1." - |> Error - False -> - list.zip(xarr, yarr) - |> list.map(fn(tuple: #(Float, Float)) -> Float { - pair.first(tuple) -. pair.second(tuple) - }) - |> norm(p) - |> Ok - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Calculcate the Euclidean distance between two lists (representing vectors): -/// -/// \\[ -/// \left( \sum_{i=1}^n \left|x_i - y_i \right|^{2} \right)^{\frac{1}{2}} -/// \\] -/// -/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i, j$$. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) -/// -/// // Empty lists returns 0.0 -/// float_list.euclidean_distance([], []) -/// |> should.equal(Ok(0.0)) -/// -/// // Differing lengths returns error -/// float_list.euclidean_distance([], [1.0]) -/// |> should.be_error() -/// -/// let assert Ok(result) = float_list.euclidean_distance([0.0, 0.0], [1.0, 2.0]) -/// result -/// |> floatx.is_close(2.23606797749979, 0.0, tol) -/// |> should.be_true() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn euclidean_distance( - xarr: List(Float), - yarr: List(Float), -) -> Result(Float, String) { - minkowski_distance(xarr, yarr, 2.0) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Calculcate the Manhatten distance between two lists (representing vectors): -/// -/// \\[ -/// \sum_{i=1}^n \left|x_i - y_i \right| -/// \\] -/// -/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i, j$$. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) -/// -/// // Empty lists returns 0.0 -/// float_list.manhatten_distance([], []) -/// |> should.equal(Ok(0.0)) -/// -/// // Differing lengths returns error -/// float_list.manhatten_distance([], [1.0]) -/// |> should.be_error() -/// -/// let assert Ok(result) = float_list.manhatten_distance([0.0, 0.0], [1.0, 2.0]) -/// result -/// |> floatx.is_close(3.0, 0.0, tol) -/// |> should.be_true() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn manhatten_distance( - xarr: List(Float), - yarr: List(Float), -) -> Result(Float, String) { - minkowski_distance(xarr, yarr, 1.0) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Generate a linearly spaced list of points over a specified interval. The endpoint of the interval can optionally be included/excluded. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) -/// let assert Ok(linspace) = float_list.linear_space(10.0, 50.0, 5, True) -/// let assert Ok(result) = -/// float_list.all_close(linspace, [10.0, 20.0, 30.0, 40.0, 50.0], 0.0, tol) -/// result -/// |> list.all(fn(x) { x == True }) -/// |> should.be_true() -/// -/// // A negative number of points (-5) does not work -/// float_list.linear_space(10.0, 50.0, -5, True) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn linear_space( - start: Float, - stop: Float, - num: Int, - endpoint: Bool, -) -> Result(List(Float), String) { - let direction: Float = case start <=. stop { - True -> 1.0 - False -> -1.0 - } - case num > 0 { - True -> - case endpoint { - True -> { - let increment: Float = - float.absolute_value(start -. stop) /. intx.to_float(num - 1) - list.range(0, num - 1) - |> list.map(fn(i: Int) -> Float { - start +. intx.to_float(i) *. increment *. direction - }) - |> Ok - } - False -> { - let increment: Float = - float.absolute_value(start -. stop) /. intx.to_float(num) - list.range(0, num - 1) - |> list.map(fn(i: Int) -> Float { - start +. intx.to_float(i) *. increment *. direction - }) - |> Ok - } - } - - False -> - "Invalid input: num < 0. Valid input is num > 0." - |> Error - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Generate a logarithmically spaced list of points over a specified interval. The endpoint of the interval can optionally be included/excluded. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) -/// let assert Ok(logspace) = float_list.logarithmic_space(1.0, 3.0, 3, True, 10.0) -/// let assert Ok(result) = -/// float_list.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) -/// result -/// |> list.all(fn(x) { x == True }) -/// |> should.be_true() -/// -/// // A negative number of points (-3) does not work -/// float_list.logarithmic_space(1.0, 3.0, -3, False, 10.0) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn logarithmic_space( - start: Float, - stop: Float, - num: Int, - endpoint: Bool, - base: Float, -) -> Result(List(Float), String) { - case num > 0 { - True -> { - let assert Ok(linspace) = linear_space(start, stop, num, endpoint) - linspace - |> list.map(fn(i: Float) -> Float { - let assert Ok(result) = floatx.power(base, i) - result - }) - |> Ok - } - False -> - "Invalid input: num < 0. Valid input is num > 0." - |> Error - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function returns a list of numbers spaced evenly on a log scale (a geometric progression). Each point in the list is a constant multiple of the previous. -/// The function is similar to the [`logarithmic_space`](#logarithmic_space) function, but with endpoints specified directly. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) -/// let assert Ok(logspace) = float_list.geometric_space(10.0, 1000.0, 3, True) -/// let assert Ok(result) = -/// float_list.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) -/// result -/// |> list.all(fn(x) { x == True }) -/// |> should.be_true() -/// -/// // Input (start and stop can't be equal to 0.0) -/// float_list.geometric_space(0.0, 1000.0, 3, False) -/// |> should.be_error() -/// -/// float_list.geometric_space(-1000.0, 0.0, 3, False) -/// |> should.be_error() -/// -/// // A negative number of points (-3) does not work -/// float_list.geometric_space(10.0, 1000.0, -3, False) -/// |> should.be_error() -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn geometric_space( - start: Float, - stop: Float, - num: Int, - endpoint: Bool, -) -> Result(List(Float), String) { - case start == 0.0 || stop == 0.0 { - True -> - "" - |> Error - False -> - case num > 0 { - True -> { - let assert Ok(log_start) = floatx.logarithm_10(start) - let assert Ok(log_stop) = floatx.logarithm_10(stop) - logarithmic_space(log_start, log_stop, num, endpoint, 10.0) - } - False -> - "Invalid input: num < 0. Valid input is num > 0." - |> Error - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function returns a list with evenly spaced values within a given interval based on a start, stop value and a given increment (step-length) between consecutive values. -/// The list returned includes the given start value but excludes the stop value. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// float_list.arange(1.0, 5.0, 1.0) -/// |> should.equal([1.0, 2.0, 3.0, 4.0]) -/// -/// // No points returned since -/// // start smaller than stop and positive step -/// float_list.arange(5.0, 1.0, 1.0) -/// |> should.equal([]) -/// -/// // Points returned since -/// // start smaller than stop but negative step -/// float_list.arange(5.0, 1.0, -1.0) -/// |> should.equal([5.0, 4.0, 3.0, 2.0]) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn arange(start: Float, stop: Float, step: Float) -> List(Float) { - case start >=. stop && step >. 0.0 || start <=. stop && step <. 0.0 { - True -> [] - False -> { - let direction: Float = case start <=. stop { - True -> 1.0 - False -> -1.0 - } - let step_abs: Float = float.absolute_value(step) - let num: Float = float.absolute_value(start -. stop) /. step_abs - - list.range(0, floatx.to_int(num) - 1) - |> list.map(fn(i: Int) -> Float { - start +. intx.to_float(i) *. step_abs *. direction - }) - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Calculcate the sum of the elements in a list: -/// -/// \\[ -/// \sum_{i=1}^n x_i -/// \\] -/// -/// In the formula, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// // An empty list returns an error -/// [] -/// |> float_list.sum() -/// |> should.equal(0.0) -/// -/// // Valid input returns a result -/// [1.0, 2.0, 3.0] -/// |> float_list.sum() -/// |> should.equal(6.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn sum(arr: List(Float)) -> Float { - case arr { - [] -> 0.0 - _ -> - arr - |> list.fold(0.0, fn(acc: Float, a: Float) -> Float { a +. acc }) - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Calculcate the product of the elements in a list: -/// -/// \\[ -/// \prod_{i=1}^n x_i -/// \\] -/// -/// In the formula, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// // An empty list returns 0.0 -/// [] -/// |> float_list.sum() -/// |> should.equal(0.0) -/// -/// // Valid input returns a result -/// [1.0, 2.0, 3.0] -/// |> float_list.product() -/// |> should.equal(6.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn product(arr: List(Float)) -> Float { - case arr { - [] -> 1.0 - _ -> - arr - |> list.fold(1.0, fn(acc: Float, a: Float) -> Float { a *. acc }) - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Calculcate the cumulative sum of the elements in a list: -/// -/// \\[ -/// v_j = \sum_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n -/// \\] -/// -/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$ elements. -/// That is, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. -/// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// [] -/// |> float_list.cumulative_sum() -/// |> should.equal([]) -/// -/// // Valid input returns a result -/// [1.0, 2.0, 3.0] -/// |> float_list.cumulative_sum() -/// |> should.equal([1.0, 3.0, 6.0]) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn cumulative_sum(arr: List(Float)) -> List(Float) { - case arr { - [] -> [] - _ -> - arr - |> list.scan(0.0, fn(acc: Float, a: Float) -> Float { a +. acc }) - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Calculcate the cumulative product of the elements in a list: -/// -/// \\[ -/// v_j = \prod_{i=1}^j x_i \\;\\; \forall j = 1,\dots, n -/// \\] -/// -/// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of $$n$$ elements. -/// That is, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. -/// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// // An empty list returns an error -/// [] -/// |> float_list.cumulative_product() -/// |> should.equal([]) -/// -/// // Valid input returns a result -/// [1.0, 2.0, 3.0] -/// |> float_list.cumulative_product() -/// |> should.equal([1.0, 2.0, 6.0]) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn cumumlative_product(arr: List(Float)) -> List(Float) { - case arr { - [] -> [] - _ -> - arr - |> list.scan(1.0, fn(acc: Float, a: Float) -> Float { a *. acc }) - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Returns the maximum value of a list. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// // An empty lists returns an error -/// [] -/// |> float_list.maximum() -/// |> should.be_error() -/// -/// // Valid input returns a result -/// [4.0, 4.0, 3.0, 2.0, 1.0] -/// |> float_list.maximum() -/// |> should.equal(Ok(4.0)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn maximum(arr: List(Float)) -> Result(Float, String) { - case arr { - [] -> - "Invalid input argument: The list is empty." - |> Error - _ -> { - let assert Ok(val0) = list.at(arr, 0) - arr - |> list.fold( - val0, - fn(acc: Float, a: Float) { - case a >. acc { - True -> a - False -> acc - } - }, - ) - |> Ok - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Returns the minimum value of a list. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// // An empty lists returns an error -/// [] -/// |> float_list.minimum() -/// |> should.be_error() -/// -/// // Valid input returns a result -/// [4.0, 4.0, 3.0, 2.0, 1.0] -/// |> float_list.minimum() -/// |> should.equal(Ok(1.0)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn minimum(arr: List(Float)) -> Result(Float, String) { - case arr { - [] -> - "Invalid input argument: The list is empty." - |> Error - _ -> { - let assert Ok(val0) = list.at(arr, 0) - arr - |> list.fold( - val0, - fn(acc: Float, a: Float) { - case a <. acc { - True -> a - False -> acc - } - }, - ) - |> Ok - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Returns the indices of the maximum values in a list. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// // An empty lists returns an error -/// [] -/// |> float_list.arg_maximum() -/// |> should.be_error() -/// -/// // Valid input returns a result -/// [4.0, 4.0, 3.0, 2.0, 1.0] -/// |> float_list.arg_maximum() -/// |> should.equal(Ok([0, 1])) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn arg_maximum(arr: List(Float)) -> Result(List(Int), String) { - case arr { - [] -> - "Invalid input argument: The list is empty." - |> Error - _ -> { - let assert Ok(max) = - arr - |> maximum() - arr - |> list.index_map(fn(index: Int, a: Float) -> Int { - case a -. max { - 0.0 -> index - _ -> -1 - } - }) - |> list.filter(fn(index: Int) -> Bool { - case index { - -1 -> False - _ -> True - } - }) - |> Ok - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Returns the indices of the minimum values in a list. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// // An empty lists returns an error -/// [] -/// |> float_list.arg_minimum() -/// |> should.be_error() -/// -/// // Valid input returns a result -/// [4.0, 4.0, 3.0, 2.0, 1.0] -/// |> float_list.arg_minimum() -/// |> should.equal(Ok([4])) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn arg_minimum(arr: List(Float)) -> Result(List(Int), String) { - case arr { - [] -> - "Invalid input argument: The list is empty." - |> Error - _ -> { - let assert Ok(min) = - arr - |> minimum() - arr - |> list.index_map(fn(index: Int, a: Float) -> Int { - case a -. min { - 0.0 -> index - _ -> -1 - } - }) - |> list.filter(fn(index: Int) -> Bool { - case index { - -1 -> False - _ -> True - } - }) - |> Ok - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Returns a tuple consisting of the minimum and maximum value of a list. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// // An empty lists returns an error -/// [] -/// |> float_list.extrema() -/// |> should.be_error() -/// -/// // Valid input returns a result -/// [4.0, 4.0, 3.0, 2.0, 1.0] -/// |> float_list.extrema() -/// |> should.equal(Ok(#(1.0, 4.0))) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn extrema(arr: List(Float)) -> Result(#(Float, Float), String) { - case arr { - [] -> - "Invalid input argument: The list is empty." - |> Error - _ -> { - let assert Ok(val_max) = list.at(arr, 0) - let assert Ok(val_min) = list.at(arr, 0) - arr - |> list.fold( - #(val_min, val_max), - fn(acc: #(Float, Float), a: Float) { - let first: Float = pair.first(acc) - let second: Float = pair.second(acc) - case a <. first, a >. second { - True, True -> #(a, a) - True, False -> #(a, second) - False, True -> #(first, a) - False, False -> #(first, second) - } - }, - ) - |> Ok - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Determine if a list of values are close to or equivalent to a another list of reference values. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam/list -/// import gleam_community/maths/float_list -/// -/// pub fn example () { -/// let val: Float = 99. -/// let ref_val: Float = 100. -/// let xarr: List(Float) = list.repeat(val, 42) -/// let yarr: List(Float) = list.repeat(ref_val, 42) -/// // We set 'atol' and 'rtol' such that the values are equivalent -/// // if 'val' is within 1 percent of 'ref_val' +/- 0.1 -/// let rtol: Float = 0.01 -/// let atol: Float = 0.10 -/// float_list.all_close(xarr, yarr, rtol, atol) -/// |> fn(zarr: Result(List(Bool), String)) -> Result(Bool, Nil) { -/// case zarr { -/// Ok(arr) -> -/// arr -/// |> list.all(fn(a: Bool) -> Bool { a }) -/// |> Ok -/// _ -> Nil |> Error -/// } -/// } -/// |> should.equal(Ok(True)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn all_close( - xarr: List(Float), - yarr: List(Float), - rtol: Float, - atol: Float, -) -> Result(List(Bool), String) { - let xlen: Int = list.length(xarr) - let ylen: Int = list.length(yarr) - case xlen == ylen { - False -> - "Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)." - |> Error - True -> - list.zip(xarr, yarr) - |> list.map(fn(z: #(Float, Float)) -> Bool { - floatx.is_close(pair.first(z), pair.second(z), rtol, atol) - }) - |> Ok - } -} diff --git a/src/gleam_community/maths/int.gleam b/src/gleam_community/maths/int.gleam deleted file mode 100644 index 0571387..0000000 --- a/src/gleam_community/maths/int.gleam +++ /dev/null @@ -1,860 +0,0 @@ -//// -//// -//// -//// -//// -//// -//// A module containing mathematical functions applying to integers. -//// -//// --- -//// -//// * **Sign and absolute value functions** -//// * [`absolute_difference`](#absolute_difference) -//// * [`sign`](#sign) -//// * [`copy_sign`](#copysign) -//// * [`flip_sign`](#flipsign) -//// * **Misc. mathematical functions** -//// * [`minimum`](#min) -//// * [`maximum`](#max) -//// * [`minmax`](#minmax) -//// * **Division functions** -//// * [`gcd`](#gcd) -//// * [`lcm`](#lcm) -//// * [`divisors`](#divisors) -//// * [`proper_divisors`](#proper_divisors) -//// * **Combinatorial functions** -//// * [`combination`](#combination) -//// * [`factorial`](#factorial) -//// * [`permutation`](#permutation) -//// * **Tests** -//// * [`is_power`](#is_power) -//// * [`is_perfect`](#is_perfect) -//// * [`is_even`](#is_even) -//// * [`is_odd`](#isodd) -//// * **Misc. functions** -//// * [`to_float`](#to_float) - -import gleam/list -import gleam/int -import gleam/float -import gleam/io -import gleam/option -import gleam_community/maths/float as floatx - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The minimum function that takes two arguments $$x, y \in \mathbb{Z}$$ and returns the smallest of the two. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.minimum(2, 1) -/// |> should.equal(1) -/// -/// intx.minimum(1, 2) -/// |> should.equal(1) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn minimum(x: Int, y: Int) -> Int { - case x < y { - True -> x - False -> y - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The maximum function that takes two arguments $$x, y \in \mathbb{Z}$$ and returns the largest of the two. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.maximum(2, 1) -/// |> should.equal(1) -/// -/// intx.maximum(1, 2) -/// |> should.equal(1) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn maximum(x: Int, y: Int) -> Int { - case x > y { - True -> x - False -> y - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The minmax function that takes two arguments $$x, y \in \mathbb{Z}$$ and returns a tuple with the smallest value first and largest second. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.minmax(2, 1) -/// |> should.equal(#(1, 2)) -/// -/// intx.minmax(1, 2) -/// |> should.equal(#(1, 2)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn minmax(x: Int, y: Int) -> #(Int, Int) { - #(minimum(x, y), maximum(x, y)) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The sign function which returns the sign of the input, indicating -/// whether it is positive (+1), negative (-1), or zero (0). -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn sign(x: Int) -> Int { - do_sign(x) -} - -if erlang { - fn do_sign(x: Int) -> Int { - case x < 0 { - True -> -1 - False -> - case x == 0 { - True -> 0 - False -> 1 - } - } - } -} - -if javascript { - external fn do_sign(Int) -> Int = - "../../maths.mjs" "sign" -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function flips the sign of a given input value. -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn flip_sign(x: Int) -> Int { - -1 * x -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function takes two arguments $$x, y \in \mathbb{Z}$$ and returns $$x$$ such that it has the same sign as $$y$$. -/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn copy_sign(x: Int, y: Int) -> Int { - case sign(x) == sign(y) { - // x and y have the same sign, just return x - True -> x - // x and y have different signs: - // - x is positive and y is negative, then flip sign of x - // - x is negative and y is positive, then flip sign of x - False -> flip_sign(x) - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// A combinatorial function for computing the number of a $$k$$-combinations of $$n$$ elements: -/// -/// \\[ -/// C(n, k) = \binom{n}{k} = \frac{n!}{k! (n-k)!} -/// \\] -/// Also known as "$$n$$ choose $$k$$" or the binomial coefficient. -/// -/// The implementation uses the effecient iterative multiplicative formula for the computation. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// // Invalid input gives an error -/// // Error on: n = -1 < 0 -/// intx.combination(-1, 1) -/// |> should.be_error() -/// -/// // Valid input returns a result -/// intx.combination(4, 0) -/// |> should.equal(Ok(1)) -/// -/// intx.combination(4, 4) -/// |> should.equal(Ok(1)) -/// -/// intx.combination(4, 2) -/// |> should.equal(Ok(6)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn combination(n: Int, k: Int) -> Result(Int, String) { - case n < 0 { - True -> - "Invalid input argument: n < 0. Valid input is n > 0." - |> Error - False -> - case k < 0 || k > n { - True -> - 0 - |> Ok - False -> - case k == 0 || k == n { - True -> - 1 - |> Ok - False -> { - let min = case k < n - k { - True -> k - False -> n - k - } - list.range(1, min) - |> list.fold( - 1, - fn(acc: Int, x: Int) -> Int { acc * { n + 1 - x } / x }, - ) - |> Ok - } - } - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// A combinatorial function for computing the total number of combinations of $$n$$ -/// elements, that is $$n!$$. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// // Invalid input gives an error -/// intx.factorial(-1) -/// |> should.be_error() -/// -/// // Valid input returns a result -/// intx.factorial(0) -/// |> should.equal(Ok(1)) -/// intx.factorial(1) -/// |> should.equal(Ok(1)) -/// intx.factorial(2) -/// |> should.equal(Ok(2)) -/// intx.factorial(3) -/// |> should.equal(Ok(6)) -/// intx.factorial(4) -/// |> should.equal(Ok(24)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn factorial(n) -> Result(Int, String) { - case n < 0 { - True -> - "Invalid input argument: n < 0. Valid input is n > 0." - |> Error - False -> - case n { - 0 -> - 1 - |> Ok - 1 -> - 1 - |> Ok - _ -> - list.range(1, n) - |> list.fold(1, fn(acc: Int, x: Int) { acc * x }) - |> Ok - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// A combinatorial function for computing the number of $$k$$-permuations (without repetitions) -/// of $$n$$ elements: -/// -/// \\[ -/// P(n, k) = \frac{n!}{(n - k)!} -/// \\] -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// // Invalid input gives an error -/// // Error on: n = -1 < 0 -/// intx.permutation(-1, 1) -/// |> should.be_error() -/// -/// // Valid input returns a result -/// intx.permutation(4, 0) -/// |> should.equal(Ok(1)) -/// -/// intx.permutation(4, 4) -/// |> should.equal(Ok(1)) -/// -/// intx.permutation(4, 2) -/// |> should.equal(Ok(12)) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn permutation(n: Int, k: Int) -> Result(Int, String) { - case n < 0 { - True -> - "Invalid input argument: n < 0. Valid input is n > 0." - |> Error - False -> - case k < 0 || k > n { - True -> - 0 - |> Ok - False -> - case k == n { - True -> - 1 - |> Ok - False -> { - let assert Ok(v1) = factorial(n) - let assert Ok(v2) = factorial(n - k) - v1 / v2 - |> Ok - } - } - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The absolute difference: -/// -/// \\[ -/// \forall x, y \in \mathbb{Z}, \\; |x - y| \in \mathbb{Z}_{+}. -/// \\] -/// -/// The function takes two inputs $$x$$ and $$y$$ and returns a positive integer value which is the the absolute difference of the inputs. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.absolute_difference(-10, 10) -/// |> should.equal(20) -/// -/// intx.absolute_difference(0, -2) -/// |> should.equal(2) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn absolute_difference(a: Int, b: Int) -> Int { - a - b - |> int.absolute_value() -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a power of another integer value $$y \in \mathbb{Z}$$. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// // Check if 4 is a power of 2 (it is) -/// intx.is_power(4, 2) -/// |> should.equal(True) -/// -/// // Check if 5 is a power of 2 (it is not) -/// intx.is_power(5, 2) -/// |> should.equal(False) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn is_power(x: Int, y: Int) -> Bool { - let assert Ok(value) = - floatx.logarithm(int.to_float(x), option.Some(int.to_float(y))) - let assert Ok(truncated) = floatx.truncate(value, option.Some(0)) - let rem = value -. truncated - rem == 0.0 -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// A function that tests whether a given integer value $$n \in \mathbb{Z}$$ is a perfect number. A number is perfect if it is equal to the sum of its proper positive divisors. -/// -///
-/// Details -/// -/// For example: -/// - $$6$$ is a perfect number since the divisors of 6 are $$1 + 2 + 3 = 6$$. -/// - $$28$$ is a perfect number since the divisors of 28 are $$1 + 2 + 4 + 7 + 14 = 28$$ -/// -///
-/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.is_perfect(6) -/// |> should.equal(True) -/// -/// intx.is_perfect(28) -/// |> should.equal(True) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn is_perfect(n: Int) -> Bool { - do_sum(proper_divisors(n)) == n -} - -fn do_sum(arr: List(Int)) -> Int { - case arr { - [] -> 0 - _ -> - arr - |> list.fold(0, fn(acc: Int, a: Int) -> Int { a + acc }) - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function returns all the positive divisors of an integer, excluding the number iteself. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.proper_divisors(4) -/// |> should.equal([1, 2]) -/// -/// intx.proper_divisors(6) -/// |> should.equal([1, 2, 3]) -/// -/// intx.proper_divisors(13) -/// |> should.equal([1]) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn proper_divisors(n: Int) -> List(Int) { - let divisors: List(Int) = find_divisors(n) - divisors - |> list.take(list.length(divisors) - 1) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function returns all the positive divisors of an integer, including the number iteself. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.divisors(4) -/// |> should.equal([1, 2, 4]) -/// -/// intx.divisors(6) -/// |> should.equal([1, 2, 3, 6]) -/// -/// intx.proper_divisors(13) -/// |> should.equal([1, 13]) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn divisors(n: Int) -> List(Int) { - find_divisors(n) -} - -pub fn find_divisors(n: Int) -> List(Int) { - let nabs: Float = float.absolute_value(to_float(n)) - let assert Ok(sqrt_result) = floatx.square_root(nabs) - let max: Int = floatx.to_int(sqrt_result) + 1 - list.range(2, max) - |> list.fold( - [1, n], - fn(acc: List(Int), i: Int) -> List(Int) { - case n % i == 0 { - True -> [i, n / i, ..acc] - False -> acc - } - }, - ) - |> list.unique() - |> list.sort(int.compare) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function calculates the greatest common multiple of two integers $$x, y \in \mathbb{Z}$$. -/// The greatest common multiple is the largest positive integer that is divisible by both $$x$$ and $$y$$. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.lcm(1, 1) -/// |> should.equal(1) -/// -/// intx.lcm(100, 10) -/// |> should.equal(10) -/// -/// intx.gcd(-36, -17) -/// |> should.equal(1) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn gcd(x: Int, y: Int) -> Int { - let absx: Int = int.absolute_value(x) - let absy: Int = int.absolute_value(y) - do_gcd(absx, absy) -} - -pub fn do_gcd(x: Int, y: Int) -> Int { - case x == 0 { - True -> y - False -> { - let assert Ok(z) = int.modulo(y, x) - do_gcd(z, x) - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// The function calculates the least common multiple of two integers $$x, y \in \mathbb{Z}$$. -/// The least common multiple is the smallest positive integer that has both $$x$$ and $$y$$ as factors. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.lcm(1, 1) -/// |> should.equal(1) -/// -/// intx.lcm(100, 10) -/// |> should.equal(100) -/// -/// intx.lcm(-36, -17) -/// |> should.equal(612) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn lcm(x: Int, y: Int) -> Int { - let absx: Int = int.absolute_value(x) - let absy: Int = int.absolute_value(y) - absx * absy / do_gcd(absx, absy) -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is odd. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.is_odd(-3) -/// |> should.equal(True) -/// -/// intx.is_odd(-4) -/// |> should.equal(False) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -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 even. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.is_even(-3) -/// |> should.equal(False) -/// -/// intx.is_even(-4) -/// |> should.equal(True) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn is_even(x: Int) -> Bool { - x % 2 == 0 -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// A function that produces a number of type `Float` from an `Int`. -/// -/// Note: The function is equivalent to the similar function in the Gleam stdlib. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int as intx -/// -/// pub fn example() { -/// intx.to_float(-1) -/// |> should.equal(-1.0) -/// -/// intx.to_float(1) -/// |> should.equal(1.0) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn to_float(x: Int) -> Float { - int.to_float(x) -} diff --git a/src/gleam_community/maths/list.gleam b/src/gleam_community/maths/list.gleam deleted file mode 100644 index e274c03..0000000 --- a/src/gleam_community/maths/list.gleam +++ /dev/null @@ -1,220 +0,0 @@ -//// -//// -//// -//// -//// -//// -//// A module containing general functions applying to lists. -//// -//// --- -//// -//// * **Combinatorial functions** -//// * [`combination`](#combination) -//// * [`permutation`](#permutation) -//// * [`cartesian_product`](#cartesian_product) -//// * **Miscellaneous functions** -//// * [`trim`](#trim) - -import gleam/list -import gleam/int -import gleam/float -import gleam/set -import gleam/io -import gleam/iterator - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Trim a list to a certain size given min/max indices. The min/max indices are inclusive. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/list as listx -/// -/// pub fn example () { -/// // An empty lists returns an error -/// [] -/// |> listx.trim(0, 0) -/// |> should.be_error() -/// -/// // Trim the list to only the middle part of list -/// [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] -/// |> listx.trim(1, 4) -/// |> should.equal(Ok([2.0, 3.0, 4.0, 5.0])) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn trim(arr: List(a), min: Int, max: Int) -> Result(List(a), String) { - case arr { - [] -> - "Invalid input argument: The list is empty." - |> Error - _ -> - case min >= 0 && max < list.length(arr) { - False -> - "Invalid input argument: min < 0 or max < length(arr). Valid input is min > 0 and max < length(arr)." - |> Error - True -> - arr - |> list.drop(min) - |> list.take(max - min + 1) - |> Ok - } - } -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Generate all $$k$$-combinations based on a given list. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam/list -/// import gleam_community/maths/list as listx -/// -/// pub fn example () { -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn combination(arr: List(a), k: Int) -> List(a) { - todo -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Generate all permutations based on a given list. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam/list -/// import gleam_community/maths/list as listx -/// -/// pub fn example () { -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn permutation(arr: List(a)) -> List(List(a)) { - do_permutation(arr, [], []) -} - -fn do_permutation( - arr: List(a), - pick_acc: List(a), - acc: List(a), -) -> List(List(a)) { - // arr - // |> iterator.unfold(fn(xarr: List(a)) -> iterator.Step(List(a), List(a)) { - // case xarr { - // [head, ..tail] -> - // iterator.Next(element: [head, ..tail], accumulator: [head, ..tail]) - // _ -> iterator.Done - // } - // }) - // |> iterator.to_list() - todo -} - -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Generate a list containing all combinations of pairs of elements coming from two given lists. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam/list -/// import gleam_community/maths/list as listx -/// -/// pub fn example () { -/// [] -/// |> listx.cartesian_product([]) -/// |> should.equal([]) -/// -/// [1.0, 10.0] -/// |> listx.cartesian_product([1.0, 2.0]) -/// |> should.equal([#(1.0, 1.0), #(1.0, 2.0), #(10.0, 1.0), #(10.0, 2.0)]) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn cartesian_product(xarr: List(a), yarr: List(a)) -> List(#(a, a)) { - let xset: set.Set(a) = - xarr - |> set.from_list() - let yset: set.Set(a) = - yarr - |> set.from_list() - xset - |> set.fold( - set.new(), - fn(accumulator0: set.Set(#(a, a)), member0: a) -> set.Set(#(a, a)) { - set.fold( - yset, - accumulator0, - fn(accumulator1: set.Set(#(a, a)), member1: a) -> set.Set(#(a, a)) { - set.insert(accumulator1, #(member0, member1)) - }, - ) - }, - ) - |> set.to_list() -} diff --git a/src/gleam_community/maths/metrics.gleam b/src/gleam_community/maths/metrics.gleam new file mode 100644 index 0000000..8d07c8f --- /dev/null +++ b/src/gleam_community/maths/metrics.gleam @@ -0,0 +1,618 @@ +//// +//// +//// +//// +//// +//// +//// --- +//// +//// Metrics: A module offering functions for calculating distances and types of metrics. +//// +//// * **Distances** +//// * [`norm`](#norm) +//// * [`float_manhatten_distance`](#float_manhatten_distance) +//// * [`int_manhatten_distance`](#int_manhatten_distance) +//// * [`minkowski_distance`](#minkowski_distance) +//// * [`euclidean_distance`](#euclidean_distance) +//// * **Basic statistical measures** +//// * [`mean`](#mean) +//// * [`median`](#median) +//// * [`variance`](#variance) +//// * [`standard_deviation`](#standard_deviation) +//// + +import gleam_community/maths/elementary +import gleam_community/maths/piecewise +import gleam_community/maths/arithmetics +import gleam_community/maths/tests +import gleam_community/maths/conversion +import gleam/list +import gleam/pair +import gleam/float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Calculcate the $$p$$-norm of a list (representing a vector): +/// +/// \\[ +/// \left( \sum_{i=1}^n \left|x_i\right|^{p} \right)^{\frac{1}{p}} +/// \\] +/// +/// In the formula, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// let assert Ok(tol) = floatx.power(-10.0, -6.0) +/// +/// [1.0, 1.0, 1.0] +/// |> float_list.norm(1.0) +/// |> floatx.is_close(3.0, 0.0, tol) +/// |> should.be_true() +/// +/// [1.0, 1.0, 1.0] +/// |> float_list.norm(-1.0) +/// |> floatx.is_close(0.3333333333333333, 0.0, tol) +/// |> should.be_true() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn norm(arr: List(Float), p: Float) -> Float { + case arr { + [] -> 0.0 + _ -> { + let agg: Float = + arr + |> list.fold( + 0.0, + fn(acc: Float, a: Float) -> Float { + let assert Ok(result) = + elementary.power(piecewise.float_absolute_value(a), p) + result +. acc + }, + ) + let assert Ok(result) = elementary.power(agg, 1.0 /. p) + result + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Calculcate the Manhatten distance between two lists (representing vectors): +/// +/// \\[ +/// \sum_{i=1}^n \left|x_i - y_i \right| +/// \\] +/// +/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i, j$$. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// let assert Ok(tol) = floatx.power(-10.0, -6.0) +/// +/// // Empty lists returns 0.0 +/// float_list.manhatten_distance([], []) +/// |> should.equal(Ok(0.0)) +/// +/// // Differing lengths returns error +/// float_list.manhatten_distance([], [1.0]) +/// |> should.be_error() +/// +/// let assert Ok(result) = float_list.manhatten_distance([0.0, 0.0], [1.0, 2.0]) +/// result +/// |> floatx.is_close(3.0, 0.0, tol) +/// |> should.be_true() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn float_manhatten_distance( + xarr: List(Float), + yarr: List(Float), +) -> Result(Float, String) { + minkowski_distance(xarr, yarr, 1.0) +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Calculcate the Manhatten distance between two lists (representing vectors): +/// +/// \\[ +/// \sum_{i=1}^n \left|x_i - y_i \right| +/// \\] +/// +/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i, j$$. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/int_list +/// +/// pub fn example () { +/// // Empty lists returns 0 +/// int_list.manhatten_distance([], []) +/// |> should.equal(Ok(0)) +/// +/// // Differing lengths returns error +/// int_list.manhatten_distance([], [1]) +/// |> should.be_error() +/// +/// let assert Ok(result) = int_list.manhatten_distance([0, 0], [1, 2]) +/// result +/// |> should.equal(3) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn int_manhatten_distance( + xarr: List(Int), + yarr: List(Int), +) -> Result(Int, String) { + let xlen: Int = list.length(xarr) + let ylen: Int = list.length(yarr) + case xlen == ylen { + False -> + "Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)." + |> Error + True -> + list.zip(xarr, yarr) + |> list.map(fn(tuple: #(Int, Int)) -> Int { + piecewise.int_absolute_value(pair.first(tuple) - pair.second(tuple)) + }) + |> arithmetics.int_sum() + |> Ok + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Calculcate the Minkowski distance between two lists (representing vectors): +/// +/// \\[ +/// \left( \sum_{i=1}^n \left|x_i - y_i \right|^{p} \right)^{\frac{1}{p}} +/// \\] +/// +/// In the formula, $$p >= 1$$ is the order, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i, j$$. +/// +/// The Minkowski distance is a generalization of both the Euclidean distance ($$p=2$$) and the Manhattan distance ($$p = 1$$). +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// let assert Ok(tol) = floatx.power(-10.0, -6.0) +/// +/// // Empty lists returns 0.0 +/// float_list.minkowski_distance([], [], 1.0) +/// |> should.equal(Ok(0.0)) +/// +/// // Differing lengths returns error +/// float_list.minkowski_distance([], [1.0], 1.0) +/// |> should.be_error() +/// +/// // Test order < 1 +/// float_list.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0) +/// |> should.be_error() +/// +/// let assert Ok(result) = float_list.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0) +/// result +/// |> floatx.is_close(3.0, 0.0, tol) +/// |> should.be_true() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn minkowski_distance( + xarr: List(Float), + yarr: List(Float), + p: Float, +) -> Result(Float, String) { + let xlen: Int = list.length(xarr) + let ylen: Int = list.length(yarr) + case xlen == ylen { + False -> + "Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)." + |> Error + True -> + case p <. 1.0 { + True -> + "Invalid input argument: p < 1. Valid input is p >= 1." + |> Error + False -> + list.zip(xarr, yarr) + |> list.map(fn(tuple: #(Float, Float)) -> Float { + pair.first(tuple) -. pair.second(tuple) + }) + |> norm(p) + |> Ok + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Calculcate the Euclidean distance between two lists (representing vectors): +/// +/// \\[ +/// \left( \sum_{i=1}^n \left|x_i - y_i \right|^{2} \right)^{\frac{1}{2}} +/// \\] +/// +/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i, j$$. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// let assert Ok(tol) = floatx.power(-10.0, -6.0) +/// +/// // Empty lists returns 0.0 +/// float_list.euclidean_distance([], []) +/// |> should.equal(Ok(0.0)) +/// +/// // Differing lengths returns error +/// float_list.euclidean_distance([], [1.0]) +/// |> should.be_error() +/// +/// let assert Ok(result) = float_list.euclidean_distance([0.0, 0.0], [1.0, 2.0]) +/// result +/// |> floatx.is_close(2.23606797749979, 0.0, tol) +/// |> should.be_true() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn euclidean_distance( + xarr: List(Float), + yarr: List(Float), +) -> Result(Float, String) { + minkowski_distance(xarr, yarr, 2.0) +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Calculcate the arithmetic mean of the elements in a list: +/// +/// \\[ +/// \bar{x} = \frac{1}{n}\sum_{i=1}^n x_i +/// \\] +/// +/// In the formula, $$n$$ is the sample size (the length of the list) and +/// $$x_i$$ is the sample point in the input list indexed by $$i$$. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_stats/stats +/// +/// pub fn example () { +/// // An empty list returns an error +/// [] +/// |> stats.mean() +/// |> should.be_error() +/// +/// // Valid input returns a result +/// [1., 2., 3.] +/// |> stats.mean() +/// |> should.equal(Ok(2.)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn mean(arr: List(Float)) -> Result(Float, String) { + case arr { + [] -> + "Invalid input argument: The list is empty." + |> Error + _ -> + arr + |> arithmetics.float_sum() + |> fn(a: Float) -> Float { + a /. conversion.int_to_float(list.length(arr)) + } + |> Ok + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Calculcate the median of the elements in a list. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_stats/stats +/// +/// pub fn example () { +/// // An empty list returns an error +/// [] +/// |> stats.median() +/// |> should.be_error() +/// +/// // Valid input returns a result +/// [1., 2., 3.] +/// |> stats.median() +/// |> should.equal(Ok(2.)) +/// +/// [1., 2., 3., 4.] +/// |> stats.median() +/// |> should.equal(Ok(2.5)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn median(arr: List(Float)) -> Result(Float, String) { + case arr { + [] -> + "Invalid input argument: The list is empty." + |> Error + _ -> { + let count: Int = list.length(arr) + let mid: Int = list.length(arr) / 2 + let sorted: List(Float) = list.sort(arr, float.compare) + case tests.is_odd(count) { + // If there is an odd number of elements in the list, then the median + // is just the middle value + True -> { + let assert Ok(val0) = list.at(sorted, mid) + val0 + |> Ok + } + // If there is an even number of elements in the list, then the median + // is the mean of the two middle values + False -> { + let assert Ok(val0) = list.at(sorted, mid - 1) + let assert Ok(val1) = list.at(sorted, mid) + [val0, val1] + |> mean() + } + } + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Calculcate the sample variance of the elements in a list: +/// \\[ +/// s^{2} = \frac{1}{n - d} \sum_{i=1}^{n}(x_i - \bar{x}) +/// \\] +/// +/// In the formula, $$n$$ is the sample size (the length of the list) and +/// $$x_i$$ is the sample point in the input list indexed by $$i$$. +/// Furthermore, $$\bar{x}$$ is the sample mean and $$d$$ is the "Delta +/// Degrees of Freedom", and is by default set to $$d = 0$$, which gives a biased +/// estimate of the sample variance. Setting $$d = 1$$ gives an unbiased estimate. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_stats/stats +/// +/// pub fn example () { +/// // Degrees of freedom +/// let ddof: Int = 1 +/// +/// // An empty list returns an error +/// [] +/// |> stats.var(ddof) +/// |> should.be_error() +/// +/// // Valid input returns a result +/// [1., 2., 3.] +/// |> stats.var(ddof) +/// |> should.equal(Ok(1.)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) { + case arr { + [] -> + "Invalid input argument: The list is empty." + |> Error + _ -> + case ddof < 0 { + True -> + "Invalid input argument: ddof < 0. Valid input is ddof >= 0." + |> Error + False -> { + let assert Ok(mean) = mean(arr) + arr + |> list.map(fn(a: Float) -> Float { + let assert Ok(result) = elementary.power(a -. mean, 2.0) + result + }) + |> arithmetics.float_sum() + |> fn(a: Float) -> Float { + a /. { + conversion.int_to_float(list.length(arr)) -. conversion.int_to_float( + ddof, + ) + } + } + |> Ok + } + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Calculcate the sample standard deviation of the elements in a list: +/// \\[ +/// s = \left(\frac{1}{n - d} \sum_{i=1}^{n}(x_i - \bar{x}))\right)^{\frac{1}{2}} +/// \\] +/// +/// In the formula, $$n$$ is the sample size (the length of the list) and +/// $$x_i$$ is the sample point in the input list indexed by $$i$$. +/// Furthermore, $$\bar{x}$$ is the sample mean and $$d$$ is the "Delta +/// Degrees of Freedom", and is by default set to $$d = 0$$, which gives a biased +/// estimate of the sample standard deviation. Setting $$d = 1$$ gives an unbiased estimate. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_stats/stats +/// +/// pub fn example () { +/// // Degrees of freedom +/// let ddof: Int = 1 +/// +/// // An empty list returns an error +/// [] +/// |> stats.std(ddof) +/// |> should.be_error() +/// +/// // Valid input returns a result +/// [1., 2., 3.] +/// |> stats.std(ddof) +/// |> should.equal(Ok(1.)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn standard_deviation(arr: List(Float), ddof: Int) -> Result(Float, String) { + case arr { + [] -> + "Invalid input argument: The list is empty." + |> Error + _ -> + case ddof < 0 { + True -> + "Invalid input argument: ddof < 0. Valid input is ddof >= 0." + |> Error + False -> { + let assert Ok(variance) = variance(arr, ddof) + // The computed variance will always be positive + // So an error should never be returned + let assert Ok(stdev) = elementary.square_root(variance) + stdev + |> Ok + } + } + } +} diff --git a/src/gleam_community/maths/piecewise.gleam b/src/gleam_community/maths/piecewise.gleam new file mode 100644 index 0000000..3345f20 --- /dev/null +++ b/src/gleam_community/maths/piecewise.gleam @@ -0,0 +1,1168 @@ +//// +//// +//// +//// +//// +//// +//// --- +//// +//// Piecewise: A module containing functions that have different definitions depending on conditions or intervals of their domain. +//// +//// * **Rounding functions** +//// * [`ceiling`](#ceiling) +//// * [`floor`](#floor) +//// * [`truncate`](#truncate) +//// * [`round`](#round) +//// * **Sign and absolute value functions** +//// * [`float_absolute_value`](#float_absolute_value) +//// * [`int_absolute_value`](#int_absolute_value) +//// * [`float_absolute_difference`](#float_absolute_difference) +//// * [`int_absolute_difference`](#int_absolute_difference) +//// * [`float_sign`](#float_sign) +//// * [`int_sign`](#int_sign) +//// * [`float_copy_sign`](#float_copy_sign) +//// * [`int_copy_sign`](#float_copy_sign) +//// * [`float_flip_sign`](#float_flip_sign) +//// * [`int_flip_sign`](#int_flip_sign) +//// * **Misc. mathematical functions** +//// * [`minimum`](#minimum) +//// * [`maximum`](#maximum) +//// * [`minmax`](#minmax) +//// * [`list_minimum`](#list_minimum) +//// * [`list_maximum`](#list_maximum) +//// * [`list_minmax`](#list_minmax) +//// * [`extrema`](#extrema) +//// * [`arg_minimum`](#arg_minimum) +//// * [`arg_maximum`](#arg_maximum) +//// + +import gleam/option +import gleam/list +import gleam/order +import gleam/pair +import gleam/int +import gleam_community/maths/conversion +import gleam_community/maths/elementary + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The ceiling function rounds a given input value $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is larger than or equal to the input $$x$$. +/// +/// Note: The ceiling function is used as an alias for the rounding function [`round`](#round) with rounding mode `RoundUp`. +/// +///
+/// Details +/// +/// For example, $$12.0654$$ is rounded to: +/// - $$13.0$$ for 0 digits after the decimal point (`digits = 0`) +/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) +/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) +/// - $$12.066$$ for 3 digits after the decimal point (`digits = 3`) +/// +/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. +/// For example, $$12.0654$$ is rounded to: +/// - $$20.0$$ for 1 digit places before the decimal point (`digit = -1`) +/// - $$100.0$$ for 2 digits before the decimal point (`digits = -2`) +/// - $$1000.0$$ for 3 digits before the decimal point (`digits = -3`) +/// +///
+/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam/option +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// floatx.ceiling(12.0654, option.Some(1)) +/// |> should.equal(Ok(12.1)) +/// +/// floatx.ceiling(12.0654, option.Some(2)) +/// |> should.equal(Ok(12.07)) +/// +/// floatx.ceiling(12.0654, option.Some(3)) +/// |> should.equal(Ok(12.066)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn ceiling(x: Float, digits: option.Option(Int)) -> Result(Float, String) { + round(x, digits, option.Some(RoundUp)) +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The floor function rounds input $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is less than or equal to the input $$x$$. +/// +/// Note: The floor function is used as an alias for the rounding function [`round`](#round) with rounding mode `RoundDown`. +/// +///
+/// Details +/// +/// For example, $$12.0654$$ is rounded to: +/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) +/// - $$12.0$$ for 1 digits after the decimal point (`digits = 1`) +/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) +/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// +/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. +/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) +/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) +/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// +///
+/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam/option +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// floatx.floor(12.0654, option.Some(1)) +/// |> should.equal(Ok(12.0)) +/// +/// floatx.floor(12.0654, option.Some(2)) +/// |> should.equal(Ok(12.06)) +/// +/// floatx.floor(12.0654, option.Some(3)) +/// |> should.equal(Ok(12.065)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn floor(x: Float, digits: option.Option(Int)) -> Result(Float, String) { + round(x, digits, option.Some(RoundDown)) +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The truncate function rounds a given input $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is less than or equal to the absolute value of the input $$x$$. +/// +/// Note: The truncate function is used as an alias for the rounding function [`round`](#round) with rounding mode `RoundToZero`. +/// +///
+/// Details +/// +/// For example, $$12.0654$$ is rounded to: +/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) +/// - $$12.0$$ for 1 digits after the decimal point (`digits = 1`) +/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) +/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// +/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. +/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) +/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) +/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// +///
+/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam/option +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// floatx.truncate(12.0654, option.Some(1)) +/// |> should.equal(Ok(12.0)) +/// +/// floatx.truncate(12.0654, option.Some(2)) +/// |> should.equal(Ok(12.0)) +/// +/// floatx.truncate(12.0654, option.Some(3)) +/// |> should.equal(Ok(12.0)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn truncate(x: Float, digits: option.Option(Int)) -> Result(Float, String) { + round(x, digits, option.Some(RoundToZero)) +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function rounds a float to a specific number of digits (after the decimal place or before if negative) using a specified rounding mode. +/// +/// Valid rounding modes include: +/// - `RoundNearest` (default): The input $$x$$ is rounded to the nearest integer value (at the specified digit) with ties (fractional values of 0.5) being rounded to the nearest even integer. +/// - `RoundTiesAway`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) with ties (fractional values of 0.5) being rounded away from zero (C/C++ rounding behavior). +/// - `RoundTiesUp`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) with ties (fractional values of 0.5) being rounded towards $$+\infty$$ (Java/JavaScript rounding behaviour). +/// - `RoundToZero`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) that is less than or equal to the absolute value of the input $$x$$. An alias for this rounding mode is [`truncate`](#truncate). +/// - `RoundDown`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) that is less than or equal to the input $$x$$. An alias for this rounding mode is [`floor`](#floor). +/// - `RoundUp`: The input $$x$$ is rounded to the nearest integer value (at the specified digit) that is larger than or equal to the input $$x$$. An alias for this rounding mode is [`ceiling`](#ceiling). +/// +///
+/// Details +/// +/// The `RoundNearest` rounding mode, rounds $$12.0654$$ to: +/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) +/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) +/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) +/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// +/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. +/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) +/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) +/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// +/// The `RoundTiesAway` rounding mode, rounds $$12.0654$$ to: +/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) +/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) +/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) +/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// +/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. +/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) +/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) +/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// +/// The `RoundTiesUp` rounding mode, rounds $$12.0654$$ to: +/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) +/// - $$12.1$$ for 1 digits after the decimal point (`digits = 1`) +/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) +/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// +/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. +/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) +/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) +/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// +/// The `RoundToZero` rounding mode, rounds $$12.0654$$ to: +/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) +/// - $$12.0$$ for 1 digit after the decimal point (`digits = 1`) +/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) +/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// +/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. +/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) +/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) +/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// +/// The `RoundDown` rounding mode, rounds $$12.0654$$ to: +/// - $$12.0$$ for 0 digits after the decimal point (`digits = 0`) +/// - $$12.0$$ for 1 digits after the decimal point (`digits = 1`) +/// - $$12.06$$ for 2 digits after the decimal point (`digits = 2`) +/// - $$12.065$$ for 3 digits after the decimal point (`digits = 3`) +/// +/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. +/// - $$10.0$$ for 1 digit before the decimal point (`digits = -1`) +/// - $$0.0$$ for 2 digits before the decimal point (`digits = -2`) +/// - $$0.0$$ for 3 digits before the decimal point (`digits = -3`) +/// +/// The `RoundUp` rounding mode, rounds $$12.0654$$ to: +/// - $$13.0$$ for 0 digits after the decimal point (`digits = 0`) +/// - $$12.1$$ for 1 digit after the decimal point (`digits = 1`) +/// - $$12.07$$ for 2 digits after the decimal point (`digits = 2`) +/// - $$12.066$$ for 3 digits after the decimal point (`digits = 3`) +/// +/// It is also possible to specify a negative number of digits. In that case, the negative number refers to the digits before the decimal point. +/// - $$20.0$$ for 1 digit places before the decimal point (`digit = -1`) +/// - $$100.0$$ for 2 digits before the decimal point (`digits = -2`) +/// - $$1000.0$$ for 3 digits before the decimal point (`digits = -3`) +/// +///
+/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam/option +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// // The default number of digits is 0 if None is provided +/// floatx.round(12.0654, option.None, option.Some(floatx.RoundNearest)) +/// |> should.equal(Ok(12.0)) +/// +/// // The default rounding mode is "RoundNearest" if None is provided +/// floatx.round(12.0654, option.None, option.None) +/// |> should.equal(Ok(12.0)) +/// +/// // Try different rounding modes +/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundNearest)) +/// |> should.equal(Ok(12.07)) +/// +/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundTiesAway)) +/// |> should.equal(Ok(12.07)) +/// +/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundTiesUp)) +/// |> should.equal(Ok(12.07)) +/// +/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundToZero)) +/// |> should.equal(Ok(12.06)) +/// +/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundDown)) +/// |> should.equal(Ok(12.06)) +/// +/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundUp)) +/// |> should.equal(Ok(12.07)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn round( + x: Float, + digits: option.Option(Int), + mode: option.Option(RoundingMode), +) -> Result(Float, String) { + case digits { + option.Some(a) -> { + let assert Ok(p) = elementary.power(10.0, conversion.int_to_float(a)) + // Round the given input x using at the specified digit + do_round(p, x, mode) + } + // Round the given input x using at the default digit + option.None -> do_round(1.0, x, mode) + } +} + +pub type RoundingMode { + RoundNearest + RoundTiesAway + RoundTiesUp + RoundToZero + RoundDown + RoundUp +} + +fn do_round( + p: Float, + x: Float, + mode: option.Option(RoundingMode), +) -> Result(Float, String) { + case mode { + // Determine the rounding mode + option.Some(RoundNearest) -> + round_to_nearest(p, x) + |> Ok + option.Some(RoundTiesAway) -> + round_ties_away(p, x) + |> Ok + option.Some(RoundTiesUp) -> + round_ties_up(p, x) + |> Ok + option.Some(RoundToZero) -> + round_to_zero(p, x) + |> Ok + option.Some(RoundDown) -> + round_down(p, x) + |> Ok + option.Some(RoundUp) -> + round_up(p, x) + |> Ok + // Otherwise, use the default rounding mode + option.None -> + round_to_nearest(p, x) + |> Ok + } +} + +fn round_to_nearest(p: Float, x: Float) -> Float { + let xabs: Float = float_absolute_value(x) *. p + let xabs_truncated: Float = truncate_float(xabs) + let remainder: Float = xabs -. xabs_truncated + case remainder { + _ if remainder >. 0.5 -> float_sign(x) *. truncate_float(xabs +. 1.0) /. p + _ if remainder == 0.5 -> { + let assert Ok(is_even) = int.modulo(conversion.float_to_int(xabs), 2) + case is_even == 0 { + True -> float_sign(x) *. xabs_truncated /. p + False -> float_sign(x) *. truncate_float(xabs +. 1.0) /. p + } + } + _ -> float_sign(x) *. xabs_truncated /. p + } +} + +fn round_ties_away(p: Float, x: Float) -> Float { + let xabs: Float = float_absolute_value(x) *. p + let remainder: Float = xabs -. truncate_float(xabs) + case remainder { + _ if remainder >=. 0.5 -> float_sign(x) *. truncate_float(xabs +. 1.0) /. p + _ -> float_sign(x) *. truncate_float(xabs) /. p + } +} + +fn round_ties_up(p: Float, x: Float) -> Float { + let xabs: Float = float_absolute_value(x) *. p + let xabs_truncated: Float = truncate_float(xabs) + let remainder: Float = xabs -. xabs_truncated + case remainder { + _ if remainder >=. 0.5 && x >=. 0.0 -> + float_sign(x) *. truncate_float(xabs +. 1.0) /. p + _ -> float_sign(x) *. xabs_truncated /. p + } +} + +// Rounding mode: ToZero / Truncate +fn round_to_zero(p: Float, x: Float) -> Float { + truncate_float(x *. p) /. p +} + +fn truncate_float(x: Float) -> Float { + do_truncate_float(x) +} + +@external(erlang, "erlang", "trunc") +@external(javascript, "../../maths.mjs", "truncate") +fn do_truncate_float(a: Float) -> Float + +// Rounding mode: Down / Floor +fn round_down(p: Float, x: Float) -> Float { + do_floor(x *. p) /. p +} + +@external(erlang, "math", "floor") +@external(javascript, "../../maths.mjs", "floor") +fn do_floor(a: Float) -> Float + +// Rounding mode: Up / Ceiling +fn round_up(p: Float, x: Float) -> Float { + do_ceiling(x *. p) /. p +} + +@external(erlang, "math", "ceil") +@external(javascript, "../../maths.mjs", "ceiling") +fn do_ceiling(a: Float) -> Float + +pub fn float_absolute_value(x: Float) -> Float { + case x >. 0.0 { + True -> x + False -> -1.0 *. x + } +} + +pub fn int_absolute_value(x: Int) -> Int { + case x > 0 { + True -> x + False -> -1 * x + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The absolute difference: +/// +/// \\[ +/// \forall x, y \in \mathbb{R}, \\; |x - y| \in \mathbb{R}_{+}. +/// \\] +/// +/// The function takes two inputs $$x$$ and $$y$$ and returns a positive float +/// value which is the the absolute difference of the inputs. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// floatx.absolute_difference(-10.0, 10.0) +/// |> should.equal(20.0) +/// +/// floatx.absolute_difference(0.0, -2.0) +/// |> should.equal(2.0) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn float_absolute_difference(a: Float, b: Float) -> Float { + a -. b + |> float_absolute_value() +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The absolute difference: +/// +/// \\[ +/// \forall x, y \in \mathbb{Z}, \\; |x - y| \in \mathbb{Z}_{+}. +/// \\] +/// +/// The function takes two inputs $$x$$ and $$y$$ and returns a positive integer value which is the the absolute difference of the inputs. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/int as intx +/// +/// pub fn example() { +/// intx.absolute_difference(-10, 10) +/// |> should.equal(20) +/// +/// intx.absolute_difference(0, -2) +/// |> should.equal(2) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn int_absolute_difference(a: Int, b: Int) -> Int { + a - b + |> int_absolute_value() +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The sign function that returns the sign of the input, indicating whether it is positive (+1), negative (-1), or zero (0). +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn float_sign(x: Float) -> Float { + do_float_sign(x) +} + +@target(erlang) +fn do_float_sign(x: Float) -> Float { + case x <. 0.0 { + True -> -1.0 + False -> + case x == 0.0 { + True -> 0.0 + False -> 1.0 + } + } +} + +@target(javascript) +@external(javascript, "../../maths.mjs", "sign") +fn do_float_sign(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The sign function which returns the sign of the input, indicating +/// whether it is positive (+1), negative (-1), or zero (0). +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn int_sign(x: Int) -> Int { + do_int_sign(x) +} + +@target(erlang) +fn do_int_sign(x: Int) -> Int { + case x < 0 { + True -> -1 + False -> + case x == 0 { + True -> 0 + False -> 1 + } + } +} + +@target(javascript) +@external(javascript, "../../maths.mjs", "sign") +fn do_int_sign(a: Int) -> Int + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function takes two arguments $$x, y \in \mathbb{R}$$ and returns $$x$$ such that it has the same sign as $$y$$. +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn float_copy_sign(x: Float, y: Float) -> Float { + case float_sign(x) == float_sign(y) { + // x and y have the same sign, just return x + True -> x + // x and y have different signs: + // - x is positive and y is negative, then flip sign of x + // - x is negative and y is positive, then flip sign of x + False -> float_flip_sign(x) + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function takes two arguments $$x, y \in \mathbb{Z}$$ and returns $$x$$ such that it has the same sign as $$y$$. +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn int_copy_sign(x: Int, y: Int) -> Int { + case int_sign(x) == int_sign(y) { + // x and y have the same sign, just return x + True -> x + // x and y have different signs: + // - x is positive and y is negative, then flip sign of x + // - x is negative and y is positive, then flip sign of x + False -> int_flip_sign(x) + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function flips the sign of a given input value $$x \in \mathbb{R}$$. +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn float_flip_sign(x: Float) -> Float { + -1.0 *. x +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function flips the sign of a given input value $$x \in \mathbb{Z}$$. +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn int_flip_sign(x: Int) -> Int { + -1 * x +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The minimum function that takes two arguments $$x, y \in \mathbb{R}$$ and returns the smallest of the two. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// floatx.minimum(2.0, 1.5) +/// |> should.equal(1.5) +/// +/// floatx.minimum(1.5, 2.0) +/// |> should.equal(1.5) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn minimum(x: a, y: a, compare: fn(a, a) -> order.Order) -> a { + case compare(x, y) { + order.Lt -> x + order.Eq -> x + order.Gt -> y + } +} + +///
+/// +/// Back to top ↑ +/// +///
+/// +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The maximum function that takes two arguments $$x, y \in \mathbb{R}$$ and returns the largest of the two. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// floatx.maximum(2.0, 1.5) +/// |> should.equal(1.5) +/// +/// floatx.maximum(1.5, 2.0) +/// |> should.equal(1.5) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn maximum(x: a, y: a, compare: fn(a, a) -> order.Order) -> a { + case compare(x, y) { + order.Lt -> y + order.Eq -> y + order.Gt -> x + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The minmax function that takes two arguments $$x, y \in \mathbb{R}$$ and returns a tuple with the smallest value first and largest second. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// floatx.minmax(2.0, 1.5) +/// |> should.equal(#(1.5, 2.0)) +/// +/// floatx.minmax(1.5, 2.0) +/// |> should.equal(#(1.5, 2.0)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn minmax(x: a, y: a, compare: fn(a, a) -> order.Order) -> #(a, a) { + #(minimum(x, y, compare), maximum(x, y, compare)) +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Returns the minimum value of a list. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/int_list +/// +/// pub fn example () { +/// // An empty lists returns an error +/// [] +/// |> int_list.minimum() +/// |> should.be_error() +/// +/// // Valid input returns a result +/// [4, 4, 3, 2, 1] +/// |> int_list.minimum() +/// |> should.equal(Ok(1)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+pub fn list_minimum( + arr: List(a), + compare: fn(a, a) -> order.Order, +) -> Result(a, String) { + case arr { + [] -> + "Invalid input argument: The list is empty." + |> Error + _ -> { + let assert Ok(val0) = list.at(arr, 0) + arr + |> list.fold( + val0, + fn(acc: a, element: a) { + case compare(element, acc) { + order.Lt -> element + _ -> acc + } + }, + ) + |> Ok + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Returns the maximum value of a list. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// // An empty lists returns an error +/// [] +/// |> float_list.maximum() +/// |> should.be_error() +/// +/// // Valid input returns a result +/// [4.0, 4.0, 3.0, 2.0, 1.0] +/// |> float_list.maximum() +/// |> should.equal(Ok(4.0)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn list_maximum( + arr: List(a), + compare: fn(a, a) -> order.Order, +) -> Result(a, String) { + case arr { + [] -> + "Invalid input argument: The list is empty." + |> Error + _ -> { + let assert Ok(val0) = list.at(arr, 0) + arr + |> list.fold( + val0, + fn(acc: a, element: a) { + case compare(acc, element) { + order.Lt -> element + _ -> acc + } + }, + ) + |> Ok + } + } +} + +///
+/// +/// Back to top ↑ +/// +///
+/// +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Returns the indices of the minimum values in a list. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// // An empty lists returns an error +/// [] +/// |> float_list.arg_minimum() +/// |> should.be_error() +/// +/// // Valid input returns a result +/// [4.0, 4.0, 3.0, 2.0, 1.0] +/// |> float_list.arg_minimum() +/// |> should.equal(Ok([4])) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn arg_minimum( + arr: List(a), + compare: fn(a, a) -> order.Order, +) -> Result(List(Int), String) { + case arr { + [] -> + "Invalid input argument: The list is empty." + |> Error + _ -> { + let assert Ok(min) = + arr + |> list_minimum(compare) + arr + |> list.index_map(fn(index: Int, element: a) -> Int { + case compare(element, min) { + order.Eq -> index + _ -> -1 + } + }) + |> list.filter(fn(index: Int) -> Bool { + case index { + -1 -> False + _ -> True + } + }) + |> Ok + } + } +} + +///
+/// +/// Back to top ↑ +/// +///
+/// +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Returns the indices of the maximum values in a list. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// // An empty lists returns an error +/// [] +/// |> float_list.arg_maximum() +/// |> should.be_error() +/// +/// // Valid input returns a result +/// [4.0, 4.0, 3.0, 2.0, 1.0] +/// |> float_list.arg_maximum() +/// |> should.equal(Ok([0, 1])) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn arg_maximum( + arr: List(a), + compare: fn(a, a) -> order.Order, +) -> Result(List(Int), String) { + case arr { + [] -> + "Invalid input argument: The list is empty." + |> Error + _ -> { + let assert Ok(max) = + arr + |> list_maximum(compare) + arr + |> list.index_map(fn(index: Int, element: a) -> Int { + case compare(element, max) { + order.Eq -> index + _ -> -1 + } + }) + |> list.filter(fn(index: Int) -> Bool { + case index { + -1 -> False + _ -> True + } + }) + |> Ok + } + } +} + +///
+/// +/// Back to top ↑ +/// +///
+/// +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Returns a tuple consisting of the minimum and maximum value of a list. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// // An empty lists returns an error +/// [] +/// |> float_list.extrema() +/// |> should.be_error() +/// +/// // Valid input returns a result +/// [4.0, 4.0, 3.0, 2.0, 1.0] +/// |> float_list.extrema() +/// |> should.equal(Ok(#(1.0, 4.0))) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn extrema( + arr: List(a), + compare: fn(a, a) -> order.Order, +) -> Result(#(a, a), String) { + case arr { + [] -> + "Invalid input argument: The list is empty." + |> Error + _ -> { + let assert Ok(val_max) = list.at(arr, 0) + let assert Ok(val_min) = list.at(arr, 0) + arr + |> list.fold( + #(val_min, val_max), + fn(acc: #(a, a), element: a) { + let first: a = pair.first(acc) + let second: a = pair.second(acc) + case compare(element, first), compare(second, element) { + order.Lt, order.Lt -> #(element, element) + order.Lt, _ -> #(element, second) + _, order.Lt -> #(first, element) + _, _ -> #(first, second) + } + }, + ) + |> Ok + } + } +} diff --git a/src/gleam_community/maths/sequences.gleam b/src/gleam_community/maths/sequences.gleam new file mode 100644 index 0000000..4ed0812 --- /dev/null +++ b/src/gleam_community/maths/sequences.gleam @@ -0,0 +1,299 @@ +//// +//// +//// +//// +//// +//// +//// --- +//// +//// Sequences: A module containing functions for generating various types of sequences, ranges and intervals. +//// +//// * **Ranges and intervals** +//// * [`arange`](#arange) +//// * [`linear_space`](#linear_space) +//// * [`logarithmic_space`](#logarithmic_space) +//// * [`geometric_space`](#geometric_space) + +import gleam_community/maths/elementary +import gleam_community/maths/piecewise +import gleam_community/maths/conversion +import gleam/list + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function returns a list with evenly spaced values within a given interval based on a start, stop value and a given increment (step-length) between consecutive values. +/// The list returned includes the given start value but excludes the stop value. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// float_list.arange(1.0, 5.0, 1.0) +/// |> should.equal([1.0, 2.0, 3.0, 4.0]) +/// +/// // No points returned since +/// // start smaller than stop and positive step +/// float_list.arange(5.0, 1.0, 1.0) +/// |> should.equal([]) +/// +/// // Points returned since +/// // start smaller than stop but negative step +/// float_list.arange(5.0, 1.0, -1.0) +/// |> should.equal([5.0, 4.0, 3.0, 2.0]) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn arange(start: Float, stop: Float, step: Float) -> List(Float) { + case start >=. stop && step >. 0.0 || start <=. stop && step <. 0.0 { + True -> [] + False -> { + let direction: Float = case start <=. stop { + True -> 1.0 + False -> -1.0 + } + let step_abs: Float = piecewise.float_absolute_value(step) + let num: Float = piecewise.float_absolute_value(start -. stop) /. step_abs + + list.range(0, conversion.float_to_int(num) - 1) + |> list.map(fn(i: Int) -> Float { + start +. conversion.int_to_float(i) *. step_abs *. direction + }) + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Generate a linearly spaced list of points over a specified interval. The endpoint of the interval can optionally be included/excluded. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// let assert Ok(tol) = floatx.power(-10.0, -6.0) +/// let assert Ok(linspace) = float_list.linear_space(10.0, 50.0, 5, True) +/// let assert Ok(result) = +/// float_list.all_close(linspace, [10.0, 20.0, 30.0, 40.0, 50.0], 0.0, tol) +/// result +/// |> list.all(fn(x) { x == True }) +/// |> should.be_true() +/// +/// // A negative number of points (-5) does not work +/// float_list.linear_space(10.0, 50.0, -5, True) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn linear_space( + start: Float, + stop: Float, + num: Int, + endpoint: Bool, +) -> Result(List(Float), String) { + let direction: Float = case start <=. stop { + True -> 1.0 + False -> -1.0 + } + case num > 0 { + True -> + case endpoint { + True -> { + let increment: Float = + piecewise.float_absolute_value(start -. stop) /. conversion.int_to_float( + num - 1, + ) + list.range(0, num - 1) + |> list.map(fn(i: Int) -> Float { + start +. conversion.int_to_float(i) *. increment *. direction + }) + |> Ok + } + False -> { + let increment: Float = + piecewise.float_absolute_value(start -. stop) /. conversion.int_to_float( + num, + ) + list.range(0, num - 1) + |> list.map(fn(i: Int) -> Float { + start +. conversion.int_to_float(i) *. increment *. direction + }) + |> Ok + } + } + + False -> + "Invalid input: num < 0. Valid input is num > 0." + |> Error + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Generate a logarithmically spaced list of points over a specified interval. The endpoint of the interval can optionally be included/excluded. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// let assert Ok(tol) = floatx.power(-10.0, -6.0) +/// let assert Ok(logspace) = float_list.logarithmic_space(1.0, 3.0, 3, True, 10.0) +/// let assert Ok(result) = +/// float_list.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) +/// result +/// |> list.all(fn(x) { x == True }) +/// |> should.be_true() +/// +/// // A negative number of points (-3) does not work +/// float_list.logarithmic_space(1.0, 3.0, -3, False, 10.0) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn logarithmic_space( + start: Float, + stop: Float, + num: Int, + endpoint: Bool, + base: Float, +) -> Result(List(Float), String) { + case num > 0 { + True -> { + let assert Ok(linspace) = linear_space(start, stop, num, endpoint) + linspace + |> list.map(fn(i: Float) -> Float { + let assert Ok(result) = elementary.power(base, i) + result + }) + |> Ok + } + False -> + "Invalid input: num < 0. Valid input is num > 0." + |> Error + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function returns a list of numbers spaced evenly on a log scale (a geometric progression). Each point in the list is a constant multiple of the previous. +/// The function is similar to the [`logarithmic_space`](#logarithmic_space) function, but with endpoints specified directly. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// let assert Ok(tol) = floatx.power(-10.0, -6.0) +/// let assert Ok(logspace) = float_list.geometric_space(10.0, 1000.0, 3, True) +/// let assert Ok(result) = +/// float_list.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) +/// result +/// |> list.all(fn(x) { x == True }) +/// |> should.be_true() +/// +/// // Input (start and stop can't be equal to 0.0) +/// float_list.geometric_space(0.0, 1000.0, 3, False) +/// |> should.be_error() +/// +/// float_list.geometric_space(-1000.0, 0.0, 3, False) +/// |> should.be_error() +/// +/// // A negative number of points (-3) does not work +/// float_list.geometric_space(10.0, 1000.0, -3, False) +/// |> should.be_error() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn geometric_space( + start: Float, + stop: Float, + num: Int, + endpoint: Bool, +) -> Result(List(Float), String) { + case start == 0.0 || stop == 0.0 { + True -> + "" + |> Error + False -> + case num > 0 { + True -> { + let assert Ok(log_start) = elementary.logarithm_10(start) + let assert Ok(log_stop) = elementary.logarithm_10(stop) + logarithmic_space(log_start, log_stop, num, endpoint, 10.0) + } + False -> + "Invalid input: num < 0. Valid input is num > 0." + |> Error + } + } +} diff --git a/src/gleam_community/maths/special.gleam b/src/gleam_community/maths/special.gleam new file mode 100644 index 0000000..6709b66 --- /dev/null +++ b/src/gleam_community/maths/special.gleam @@ -0,0 +1,205 @@ +//// +//// +//// +//// +//// +//// +//// --- +//// +//// Special: A module containing special mathematical functions. +//// +//// * **Special mathematical functions** +//// * [`beta`](#beta) +//// * [`erf`](#erf) +//// * [`gamma`](#gamma) +//// * [`incomplete_gamma`](#incomplete_gamma) +//// + +import gleam_community/maths/conversion +import gleam_community/maths/elementary +import gleam_community/maths/piecewise +import gleam/list + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The beta function over the real numbers: +/// +/// \\[ +/// \text{B}(x, y) = \frac{\Gamma(x) \cdot \Gamma(y)}{\Gamma(x + y)} +/// \\] +/// +/// The beta function is evaluated through the use of the gamma function. +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn beta(x: Float, y: Float) -> Float { + gamma(x) *. gamma(y) /. gamma(x +. y) +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The error function. +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn erf(x: Float) -> Float { + let [a1, a2, a3, a4, a5]: List(Float) = [ + 0.254829592, -0.284496736, 1.421413741, -1.453152027, 1.061405429, + ] + let p: Float = 0.3275911 + + let sign: Float = piecewise.float_sign(x) + let x: Float = piecewise.float_absolute_value(x) + + // Formula 7.1.26 given in Abramowitz and Stegun. + let t: Float = 1.0 /. { 1.0 +. p *. x } + let y: Float = + 1.0 -. { { { { a5 *. t +. a4 } *. t +. a3 } *. t +. a2 } *. t +. a1 } *. t *. elementary.exponential( + -1.0 *. x *. x, + ) + sign *. y +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The gamma function over the real numbers. The function is essentially equal to the +/// factorial for any positive integer argument: $$\Gamma(n) = (n - 1)!$$ +/// +/// The implemented gamma function is approximated through Lanczos approximation +/// using the same coefficients used by the GNU Scientific Library. +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn gamma(x: Float) -> Float { + gamma_lanczos(x) +} + +const lanczos_g: Float = 7.0 + +const lanczos_p: List(Float) = [ + 0.99999999999980993, 676.5203681218851, -1259.1392167224028, + 771.32342877765313, -176.61502916214059, 12.507343278686905, + -0.13857109526572012, 0.0000099843695780195716, 0.00000015056327351493116, +] + +fn gamma_lanczos(x: Float) -> Float { + case x <. 0.5 { + True -> + elementary.pi() /. { + elementary.sin(elementary.pi() *. x) *. gamma_lanczos(1.0 -. x) + } + False -> { + let z = x -. 1.0 + let x: Float = + list.index_fold( + lanczos_p, + 0.0, + fn(acc: Float, v: Float, index: Int) { + case index > 0 { + True -> acc +. v /. { z +. conversion.int_to_float(index) } + False -> v + } + }, + ) + let t: Float = z +. lanczos_g +. 0.5 + let assert Ok(v1) = elementary.power(2.0 *. elementary.pi(), 0.5) + let assert Ok(v2) = elementary.power(t, z +. 0.5) + v1 *. v2 *. elementary.exponential(-1.0 *. t) *. x + } + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The lower incomplete gamma function over the real numbers. +/// +/// The implemented incomplete gamma function is evaluated through a power series +/// expansion. +/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn incomplete_gamma(a: Float, x: Float) -> Result(Float, String) { + case a >. 0.0 && x >=. 0.0 { + True -> { + let assert Ok(v) = elementary.power(x, a) + v *. elementary.exponential(-1.0 *. x) *. incomplete_gamma_sum( + a, + x, + 1.0 /. a, + 0.0, + 1.0, + ) + |> Ok + } + + False -> + "Invlaid input argument: a <= 0 or x < 0. Valid input is a > 0 and x >= 0." + |> Error + } +} + +fn incomplete_gamma_sum( + a: Float, + x: Float, + t: Float, + s: Float, + n: Float, +) -> Float { + case t { + 0.0 -> s + _ -> { + let ns: Float = s +. t + let nt: Float = t *. { x /. { a +. n } } + incomplete_gamma_sum(a, x, nt, ns, n +. 1.0) + } + } +} diff --git a/src/gleam_community/maths/tests.gleam b/src/gleam_community/maths/tests.gleam new file mode 100644 index 0000000..8ff3107 --- /dev/null +++ b/src/gleam_community/maths/tests.gleam @@ -0,0 +1,358 @@ +//// +//// +//// +//// +//// +//// +//// --- +//// +//// Tests: A module containing functions for testing various mathematical properties of numbers. +//// +//// * **Tests** +//// * [`is_close`](#is_close) +//// * [`list_all_close`](#all_close) +//// * [`is_fractional`](#is_fractional) +//// * [`is_power`](#is_power) +//// * [`is_perfect`](#is_perfect) +//// * [`is_even`](#is_even) +//// * [`is_odd`](#isodd) + +import gleam/pair +import gleam/int +import gleam/list +import gleam/option +import gleam_community/maths/elementary +import gleam_community/maths/piecewise +import gleam_community/maths/arithmetics + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Determine if a given value $$a$$ is close to or equivalent to a reference value +/// $$b$$ based on supplied relative $$r_{tol}$$ and absolute $$a_{tol}$$ tolerance values. +/// The equivalance of the two given values are then determined based on the equation: +/// +/// \\[ +/// \|a - b\| \leq (a_{tol} + r_{tol} \cdot \|b\|) +/// \\] +/// +/// `True` is returned if statement holds, otherwise `False` is returned. +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// +/// pub fn example () { +/// let val: Float = 99. +/// let ref_val: Float = 100. +/// // We set 'atol' and 'rtol' such that the values are equivalent +/// // if 'val' is within 1 percent of 'ref_val' +/- 0.1 +/// let rtol: Float = 0.01 +/// let atol: Float = 0.10 +/// floatx.is_close(val, ref_val, rtol, atol) +/// |> should.be_true() +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn is_close(a: Float, b: Float, rtol: Float, atol: Float) -> Bool { + let x: Float = float_absolute_difference(a, b) + let y: Float = atol +. rtol *. float_absolute_value(b) + case x <=. y { + True -> True + False -> False + } +} + +fn float_absolute_value(x: Float) -> Float { + case x >. 0.0 { + True -> x + False -> -1.0 *. x + } +} + +fn float_absolute_difference(a: Float, b: Float) -> Float { + a -. b + |> float_absolute_value() +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Determine if a list of values are close to or equivalent to a another list of reference values. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam/list +/// import gleam_community/maths/float_list +/// +/// pub fn example () { +/// let val: Float = 99. +/// let ref_val: Float = 100. +/// let xarr: List(Float) = list.repeat(val, 42) +/// let yarr: List(Float) = list.repeat(ref_val, 42) +/// // We set 'atol' and 'rtol' such that the values are equivalent +/// // if 'val' is within 1 percent of 'ref_val' +/- 0.1 +/// let rtol: Float = 0.01 +/// let atol: Float = 0.10 +/// float_list.all_close(xarr, yarr, rtol, atol) +/// |> fn(zarr: Result(List(Bool), String)) -> Result(Bool, Nil) { +/// case zarr { +/// Ok(arr) -> +/// arr +/// |> list.all(fn(a: Bool) -> Bool { a }) +/// |> Ok +/// _ -> Nil |> Error +/// } +/// } +/// |> should.equal(Ok(True)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn all_close( + xarr: List(Float), + yarr: List(Float), + rtol: Float, + atol: Float, +) -> Result(List(Bool), String) { + let xlen: Int = list.length(xarr) + let ylen: Int = list.length(yarr) + case xlen == ylen { + False -> + "Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)." + |> Error + True -> + list.zip(xarr, yarr) + |> list.map(fn(z: #(Float, Float)) -> Bool { + is_close(pair.first(z), pair.second(z), rtol, atol) + }) + |> Ok + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// Determine if a given value $$a$$ is fractional. +/// +/// `True` is returned if the given value is fractional, otherwise `False` is returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam_community/maths/float as floatx +/// +/// pub fn example () { +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn is_fractional(x: Float) -> Bool { + do_ceiling(x) -. x >. 0.0 +} + +@external(erlang, "math", "ceil") +@external(javascript, "../../maths.mjs", "ceiling") +fn do_ceiling(a: Float) -> Float + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is a power of another integer value $$y \in \mathbb{Z}$$. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/int as intx +/// +/// pub fn example() { +/// // Check if 4 is a power of 2 (it is) +/// intx.is_power(4, 2) +/// |> should.equal(True) +/// +/// // Check if 5 is a power of 2 (it is not) +/// intx.is_power(5, 2) +/// |> should.equal(False) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn is_power(x: Int, y: Int) -> Bool { + let assert Ok(value) = + elementary.logarithm(int.to_float(x), option.Some(int.to_float(y))) + let assert Ok(truncated) = piecewise.truncate(value, option.Some(0)) + let rem = value -. truncated + rem == 0.0 +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// A function that tests whether a given integer value $$n \in \mathbb{Z}$$ is a perfect number. A number is perfect if it is equal to the sum of its proper positive divisors. +/// +///
+/// Details +/// +/// For example: +/// - $$6$$ is a perfect number since the divisors of 6 are $$1 + 2 + 3 = 6$$. +/// - $$28$$ is a perfect number since the divisors of 28 are $$1 + 2 + 4 + 7 + 14 = 28$$ +/// +///
+/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/int as intx +/// +/// pub fn example() { +/// intx.is_perfect(6) +/// |> should.equal(True) +/// +/// intx.is_perfect(28) +/// |> should.equal(True) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn is_perfect(n: Int) -> Bool { + do_sum(arithmetics.proper_divisors(n)) == n +} + +fn do_sum(arr: List(Int)) -> Int { + case arr { + [] -> 0 + _ -> + arr + |> list.fold(0, fn(acc: Int, a: Int) -> Int { a + acc }) + } +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// A function that tests whether a given integer value $$x \in \mathbb{Z}$$ is even. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/int as intx +/// +/// pub fn example() { +/// intx.is_even(-3) +/// |> should.equal(False) +/// +/// intx.is_even(-4) +/// |> should.equal(True) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn is_even(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 odd. +/// +///
+/// Example: +/// +/// import gleeunit/should +/// import gleam_community/maths/int as intx +/// +/// pub fn example() { +/// intx.is_odd(-3) +/// |> should.equal(True) +/// +/// intx.is_odd(-4) +/// |> should.equal(False) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn is_odd(x: Int) -> Bool { + x % 2 != 0 +} diff --git a/test/gleam/gleam_community_maths_arithmetics.gleam b/test/gleam/gleam_community_maths_arithmetics.gleam new file mode 100644 index 0000000..1b83e59 --- /dev/null +++ b/test/gleam/gleam_community_maths_arithmetics.gleam @@ -0,0 +1,203 @@ +import gleam_community/maths/arithmetics +import gleeunit +import gleeunit/should + +pub fn main() { + gleeunit.main() +} + +pub fn int_gcd_test() { + arithmetics.gcd(1, 1) + |> should.equal(1) + + arithmetics.gcd(100, 10) + |> should.equal(10) + + arithmetics.gcd(10, 100) + |> should.equal(10) + + arithmetics.gcd(100, -10) + |> should.equal(10) + + arithmetics.gcd(-36, -17) + |> should.equal(1) + + arithmetics.gcd(-30, -42) + |> should.equal(6) +} + +pub fn int_lcm_test() { + arithmetics.lcm(1, 1) + |> should.equal(1) + + arithmetics.lcm(100, 10) + |> should.equal(100) + + arithmetics.lcm(10, 100) + |> should.equal(100) + + arithmetics.lcm(100, -10) + |> should.equal(100) + + arithmetics.lcm(-36, -17) + |> should.equal(612) + + arithmetics.lcm(-30, -42) + |> should.equal(210) +} + +pub fn int_proper_divisors_test() { + arithmetics.proper_divisors(2) + |> should.equal([1]) + + arithmetics.proper_divisors(6) + |> should.equal([1, 2, 3]) + + arithmetics.proper_divisors(13) + |> should.equal([1]) + + arithmetics.proper_divisors(18) + |> should.equal([1, 2, 3, 6, 9]) +} + +pub fn int_divisors_test() { + arithmetics.divisors(2) + |> should.equal([1, 2]) + + arithmetics.divisors(6) + |> should.equal([1, 2, 3, 6]) + + arithmetics.divisors(13) + |> should.equal([1, 13]) + + arithmetics.divisors(18) + |> should.equal([1, 2, 3, 6, 9, 18]) +} + +pub fn float_list_sum_test() { + // An empty list returns 0 + [] + |> arithmetics.float_sum() + |> should.equal(0.0) + + // Valid input returns a result + [1.0, 2.0, 3.0] + |> arithmetics.float_sum() + |> should.equal(6.0) + + [-2.0, 4.0, 6.0] + |> arithmetics.float_sum() + |> should.equal(8.0) +} + +pub fn int_list_sum_test() { + // An empty list returns 0 + [] + |> arithmetics.int_sum() + |> should.equal(0) + + // Valid input returns a result + [1, 2, 3] + |> arithmetics.int_sum() + |> should.equal(6) + + [-2, 4, 6] + |> arithmetics.int_sum() + |> should.equal(8) +} + +pub fn float_list_product_test() { + // An empty list returns 0 + [] + |> arithmetics.float_product() + |> should.equal(1.0) + + // Valid input returns a result + [1.0, 2.0, 3.0] + |> arithmetics.float_product() + |> should.equal(6.0) + + [-2.0, 4.0, 6.0] + |> arithmetics.float_product() + |> should.equal(-48.0) +} + +pub fn int_list_product_test() { + // An empty list returns 0 + [] + |> arithmetics.int_product() + |> should.equal(1) + + // Valid input returns a result + [1, 2, 3] + |> arithmetics.int_product() + |> should.equal(6) + + [-2, 4, 6] + |> arithmetics.int_product() + |> should.equal(-48) +} + +pub fn float_list_cumulative_sum_test() { + // An empty lists returns an empty list + [] + |> arithmetics.float_cumulative_sum() + |> should.equal([]) + + // Valid input returns a result + [1.0, 2.0, 3.0] + |> arithmetics.float_cumulative_sum() + |> should.equal([1.0, 3.0, 6.0]) + + [-2.0, 4.0, 6.0] + |> arithmetics.float_cumulative_sum() + |> should.equal([-2.0, 2.0, 8.0]) +} + +pub fn int_list_cumulative_sum_test() { + // An empty lists returns an empty list + [] + |> arithmetics.int_cumulative_sum() + |> should.equal([]) + + // Valid input returns a result + [1, 2, 3] + |> arithmetics.int_cumulative_sum() + |> should.equal([1, 3, 6]) + + [-2, 4, 6] + |> arithmetics.int_cumulative_sum() + |> should.equal([-2, 2, 8]) +} + +pub fn float_list_cumulative_product_test() { + // An empty lists returns an empty list + [] + |> arithmetics.float_cumumlative_product() + |> should.equal([]) + + // Valid input returns a result + [1.0, 2.0, 3.0] + |> arithmetics.float_cumumlative_product() + |> should.equal([1.0, 2.0, 6.0]) + + [-2.0, 4.0, 6.0] + |> arithmetics.float_cumumlative_product() + |> should.equal([-2.0, -8.0, -48.0]) +} + +pub fn int_list_cumulative_product_test() { + // An empty lists returns an empty list + [] + |> arithmetics.int_cumulative_product() + |> should.equal([]) + + // Valid input returns a result + [1, 2, 3] + |> arithmetics.int_cumulative_product() + |> should.equal([1, 2, 6]) + + [-2, 4, 6] + |> arithmetics.int_cumulative_product() + |> should.equal([-2, -8, -48]) +} diff --git a/test/gleam/gleam_community_maths_combinatorics.gleam b/test/gleam/gleam_community_maths_combinatorics.gleam new file mode 100644 index 0000000..62c3b8c --- /dev/null +++ b/test/gleam/gleam_community_maths_combinatorics.gleam @@ -0,0 +1,167 @@ +import gleam_community/maths/combinatorics +import gleam/set +import gleeunit +import gleeunit/should + +pub fn main() { + gleeunit.main() +} + +pub fn int_factorial_test() { + // Invalid input gives an error + combinatorics.factorial(-1) + |> should.be_error() + + // Valid input returns a result + combinatorics.factorial(0) + |> should.equal(Ok(1)) + + combinatorics.factorial(1) + |> should.equal(Ok(1)) + + combinatorics.factorial(2) + |> should.equal(Ok(2)) + + combinatorics.factorial(3) + |> should.equal(Ok(6)) + + combinatorics.factorial(4) + |> should.equal(Ok(24)) +} + +pub fn int_combination_test() { + // Invalid input gives an error + // Error on: n = -1 < 0 + combinatorics.combination(-1, 1) + |> should.be_error() + + // Valid input returns a result + combinatorics.combination(4, 0) + |> should.equal(Ok(1)) + + combinatorics.combination(4, 4) + |> should.equal(Ok(1)) + + combinatorics.combination(4, 2) + |> should.equal(Ok(6)) + + combinatorics.combination(7, 5) + |> should.equal(Ok(21)) + // NOTE: Tests with the 'combination' function that produce values that + // exceed precision of the JavaScript 'Number' primitive will result in + // errors +} + +pub fn math_permutation_test() { + // Invalid input gives an error + // Error on: n = -1 < 0 + combinatorics.permutation(-1, 1) + |> should.be_error() + + // Valid input returns a result + combinatorics.permutation(4, 0) + |> should.equal(Ok(1)) + + combinatorics.permutation(4, 4) + |> should.equal(Ok(1)) + + combinatorics.permutation(4, 2) + |> should.equal(Ok(12)) +} + +pub fn list_cartesian_product_test() { + // An empty lists returns an empty list + [] + |> combinatorics.cartesian_product([]) + |> should.equal([]) + + // Test with some arbitrary inputs + [1, 2, 3] + |> combinatorics.cartesian_product([1, 2, 3]) + |> set.from_list() + |> should.equal(set.from_list([ + #(1, 1), + #(1, 2), + #(1, 3), + #(2, 1), + #(2, 2), + #(2, 3), + #(3, 1), + #(3, 2), + #(3, 3), + ])) + + [1.0, 10.0] + |> combinatorics.cartesian_product([1.0, 2.0]) + |> set.from_list() + |> should.equal(set.from_list([ + #(1.0, 1.0), + #(1.0, 2.0), + #(10.0, 1.0), + #(10.0, 2.0), + ])) +} + +pub fn list_permutation_test() { + // An empty lists returns an empty list + [] + |> combinatorics.list_permutation() + |> should.equal([[]]) + + // Test with some arbitrary inputs + [1, 2] + |> combinatorics.list_permutation() + |> should.equal([[1, 2], [2, 1]]) + + // Test with some arbitrary inputs + [1, 2, 3] + |> combinatorics.list_permutation() + |> set.from_list() + |> should.equal(set.from_list([ + [1, 2, 3], + [2, 1, 3], + [3, 1, 2], + [1, 3, 2], + [2, 3, 1], + [3, 2, 1], + ])) +} + +pub fn list_combination_test() { + // A negative number returns an error + [] + |> combinatorics.list_combination(-1) + |> should.be_error() + + // k is larger than given input list returns an error + [1, 2] + |> combinatorics.list_combination(3) + |> should.be_error() + // An empty lists returns an empty list + [] + |> combinatorics.list_combination(0) + |> should.equal(Ok([[]])) + + // Test with some arbitrary inputs + [1, 2] + |> combinatorics.list_combination(1) + |> should.equal(Ok([[1], [2]])) + + // Test with some arbitrary inputs + [1, 2] + |> combinatorics.list_combination(2) + |> should.equal(Ok([[1, 2]])) + + // Test with some arbitrary inputs + let assert Ok(result) = combinatorics.list_combination([1, 2, 3, 4], 2) + result + |> set.from_list() + |> should.equal(set.from_list([[1, + 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]])) + + // Test with some arbitrary inputs + let assert Ok(result) = combinatorics.list_combination([1, 2, 3, 4], 3) + result + |> set.from_list() + |> should.equal(set.from_list([[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]])) +} diff --git a/test/gleam/gleam_community_maths_conversion.gleam b/test/gleam/gleam_community_maths_conversion.gleam new file mode 100644 index 0000000..053078b --- /dev/null +++ b/test/gleam/gleam_community_maths_conversion.gleam @@ -0,0 +1,44 @@ +import gleam_community/maths/elementary +import gleam_community/maths/tests +import gleam_community/maths/conversion +import gleeunit +import gleeunit/should + +pub fn main() { + gleeunit.main() +} + +pub fn float_to_degree_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + conversion.radians_to_degrees(0.0) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + conversion.radians_to_degrees(2.0 *. elementary.pi()) + |> tests.is_close(360.0, 0.0, tol) + |> should.be_true() +} + +pub fn float_to_radian_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + conversion.degrees_to_radians(0.0) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + conversion.degrees_to_radians(360.0) + |> tests.is_close(2.0 *. elementary.pi(), 0.0, tol) + |> should.be_true() +} + +pub fn float_to_int_test() { + conversion.float_to_int(12.0654) + |> should.equal(12) +} + +pub fn int_to_float_test() { + conversion.int_to_float(-1) + |> should.equal(-1.0) + + conversion.int_to_float(1) + |> should.equal(1.0) +} diff --git a/test/gleam/gleam_community_maths_elementary.gleam b/test/gleam/gleam_community_maths_elementary.gleam new file mode 100644 index 0000000..c84dee2 --- /dev/null +++ b/test/gleam/gleam_community_maths_elementary.gleam @@ -0,0 +1,482 @@ +import gleam_community/maths/elementary +import gleam_community/maths/tests +import gleeunit +import gleeunit/should +import gleam/option + +pub fn main() { + gleeunit.main() +} + +pub fn float_acos_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + let assert Ok(result) = elementary.acos(1.0) + result + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + let assert Ok(result) = elementary.acos(0.5) + result + |> tests.is_close(1.047197, 0.0, tol) + |> should.be_true() + + // Check that we get an error when the function is evaluated + // outside its domain + elementary.acos(1.1) + |> should.be_error() + + elementary.acos(-1.1) + |> should.be_error() +} + +pub fn float_acosh_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + let assert Ok(result) = elementary.acosh(1.0) + result + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + // Check that we get an error when the function is evaluated + // outside its domain + elementary.acosh(0.0) + |> should.be_error() +} + +pub fn float_asin_test() { + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.asin(0.0) + |> should.equal(Ok(0.0)) + + let assert Ok(tol) = elementary.power(-10.0, -6.0) + let assert Ok(result) = elementary.asin(0.5) + result + |> tests.is_close(0.523598, 0.0, tol) + |> should.be_true() + + // Check that we get an error when the function is evaluated + // outside its domain + elementary.asin(1.1) + |> should.be_error() + + elementary.asin(-1.1) + |> should.be_error() +} + +pub fn float_asinh_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.asinh(0.0) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + elementary.asinh(0.5) + |> tests.is_close(0.481211, 0.0, tol) + |> should.be_true() +} + +pub fn float_atan_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.atan(0.0) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + elementary.atan(0.5) + |> tests.is_close(0.463647, 0.0, tol) + |> should.be_true() +} + +pub fn math_atan2_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.atan2(0.0, 0.0) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + elementary.atan2(0.0, 1.0) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + // Check atan2(y=1.0, x=0.5) + // Should be equal to atan(y / x) for any x > 0 and any y + let result = elementary.atan(1.0 /. 0.5) + elementary.atan2(1.0, 0.5) + |> tests.is_close(result, 0.0, tol) + |> should.be_true() + + // Check atan2(y=2.0, x=-1.5) + // Should be equal to pi + atan(y / x) for any x < 0 and y >= 0 + let result = elementary.pi() +. elementary.atan(2.0 /. -1.5) + elementary.atan2(2.0, -1.5) + |> tests.is_close(result, 0.0, tol) + |> should.be_true() + + // Check atan2(y=-2.0, x=-1.5) + // Should be equal to atan(y / x) - pi for any x < 0 and y < 0 + let result = elementary.atan(-2.0 /. -1.5) -. elementary.pi() + elementary.atan2(-2.0, -1.5) + |> tests.is_close(result, 0.0, tol) + |> should.be_true() + + // Check atan2(y=1.5, x=0.0) + // Should be equal to pi/2 for x = 0 and any y > 0 + let result = elementary.pi() /. 2.0 + elementary.atan2(1.5, 0.0) + |> tests.is_close(result, 0.0, tol) + |> should.be_true() + + // Check atan2(y=-1.5, x=0.0) + // Should be equal to -pi/2 for x = 0 and any y < 0 + let result = -1.0 *. elementary.pi() /. 2.0 + elementary.atan2(-1.5, 0.0) + |> tests.is_close(result, 0.0, tol) + |> should.be_true() +} + +pub fn float_atanh_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + let assert Ok(result) = elementary.atanh(0.0) + result + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + let assert Ok(result) = elementary.atanh(0.5) + result + |> tests.is_close(0.549306, 0.0, tol) + |> should.be_true() + + // Check that we get an error when the function is evaluated + // outside its domain + elementary.atanh(1.0) + |> should.be_error() + + elementary.atanh(2.0) + |> should.be_error() + + elementary.atanh(1.0) + |> should.be_error() + + elementary.atanh(-2.0) + |> should.be_error() +} + +pub fn float_cos_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.cos(0.0) + |> tests.is_close(1.0, 0.0, tol) + |> should.be_true() + + elementary.cos(elementary.pi()) + |> tests.is_close(-1.0, 0.0, tol) + |> should.be_true() + + elementary.cos(0.5) + |> tests.is_close(0.877582, 0.0, tol) + |> should.be_true() +} + +pub fn float_cosh_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.cosh(0.0) + |> tests.is_close(1.0, 0.0, tol) + |> should.be_true() + + elementary.cosh(0.5) + |> tests.is_close(1.127625, 0.0, tol) + |> should.be_true() + // An (overflow) error might occur when given an input + // value that will result in a too large output value + // e.g. elementary.cosh(1000.0) but this is a property of the + // runtime. +} + +pub fn float_sin_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.sin(0.0) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + elementary.sin(0.5 *. elementary.pi()) + |> tests.is_close(1.0, 0.0, tol) + |> should.be_true() + + elementary.sin(0.5) + |> tests.is_close(0.479425, 0.0, tol) + |> should.be_true() +} + +pub fn float_sinh_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.sinh(0.0) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + elementary.sinh(0.5) + |> tests.is_close(0.521095, 0.0, tol) + |> should.be_true() + // An (overflow) error might occur when given an input + // value that will result in a too large output value + // e.g. elementary.sinh(1000.0) but this is a property of the + // runtime. +} + +pub fn math_tan_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.tan(0.0) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + elementary.tan(0.5) + |> tests.is_close(0.546302, 0.0, tol) + |> should.be_true() +} + +pub fn math_tanh_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.tanh(0.0) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + elementary.tanh(25.0) + |> tests.is_close(1.0, 0.0, tol) + |> should.be_true() + + elementary.tanh(-25.0) + |> tests.is_close(-1.0, 0.0, tol) + |> should.be_true() + + elementary.tanh(0.5) + |> tests.is_close(0.462117, 0.0, tol) + |> should.be_true() +} + +pub fn float_exponential_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.exponential(0.0) + |> tests.is_close(1.0, 0.0, tol) + |> should.be_true() + + elementary.exponential(0.5) + |> tests.is_close(1.648721, 0.0, tol) + |> should.be_true() + // An (overflow) error might occur when given an input + // value that will result in a too large output value + // e.g. elementary.exponential(1000.0) but this is a property of the + // runtime. +} + +pub fn float_natural_logarithm_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.natural_logarithm(1.0) + |> should.equal(Ok(0.0)) + + let assert Ok(result) = elementary.natural_logarithm(0.5) + result + |> tests.is_close(-0.693147, 0.0, tol) + |> should.be_true() + + // Check that we get an error when the function is evaluated + // outside its domain + elementary.natural_logarithm(-1.0) + |> should.be_error() +} + +pub fn float_logarithm_test() { + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.logarithm(10.0, option.Some(10.0)) + |> should.equal(Ok(1.0)) + + elementary.logarithm(10.0, option.Some(100.0)) + |> should.equal(Ok(0.5)) + + elementary.logarithm(1.0, option.Some(0.25)) + |> should.equal(Ok(0.0)) + + // Check that we get an error when the function is evaluated + // outside its domain + elementary.logarithm(1.0, option.Some(1.0)) + |> should.be_error() + + elementary.logarithm(10.0, option.Some(1.0)) + |> should.be_error() + + elementary.logarithm(-1.0, option.Some(1.0)) + |> should.be_error() + + elementary.logarithm(1.0, option.Some(10.0)) + |> should.equal(Ok(0.0)) + + elementary.logarithm(elementary.e(), option.Some(elementary.e())) + |> should.equal(Ok(1.0)) + + elementary.logarithm(-1.0, option.Some(2.0)) + |> should.be_error() +} + +pub fn float_logarithm_2_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + elementary.logarithm_2(1.0) + |> should.equal(Ok(0.0)) + + elementary.logarithm_2(2.0) + |> should.equal(Ok(1.0)) + + let assert Ok(result) = elementary.logarithm_2(5.0) + result + |> tests.is_close(2.321928, 0.0, tol) + |> should.be_true() + + // Check that we get an error when the function is evaluated + // outside its domain + elementary.logarithm_2(-1.0) + |> should.be_error() +} + +pub fn float_logarithm_10_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + let assert Ok(result) = elementary.logarithm_10(1.0) + result + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + let assert Ok(result) = elementary.logarithm_10(10.0) + result + |> tests.is_close(1.0, 0.0, tol) + |> should.be_true() + + let assert Ok(result) = elementary.logarithm_10(50.0) + result + |> tests.is_close(1.69897, 0.0, tol) + |> should.be_true() + + // Check that we get an error when the function is evaluated + // outside its domain + elementary.logarithm_10(-1.0) + |> should.be_error() +} + +pub fn float_power_test() { + elementary.power(2.0, 2.0) + |> should.equal(Ok(4.0)) + + elementary.power(-5.0, 3.0) + |> should.equal(Ok(-125.0)) + + elementary.power(10.5, 0.0) + |> should.equal(Ok(1.0)) + + elementary.power(16.0, 0.5) + |> should.equal(Ok(4.0)) + + elementary.power(2.0, -1.0) + |> should.equal(Ok(0.5)) + + elementary.power(2.0, -1.0) + |> should.equal(Ok(0.5)) + + // elementary.power(-1.0, 0.5) is equivalent to float.square_root(-1.0) + // and should return an error as an imaginary number would otherwise + // have to be returned + elementary.power(-1.0, 0.5) + |> should.be_error() + + // Check another case with a negative base and fractional exponent + elementary.power(-1.5, 1.5) + |> should.be_error() + + // elementary.power(0.0, -1.0) is equivalent to 1. /. 0 and is expected + // to be an error + elementary.power(0.0, -1.0) + |> should.be_error() + + // Check that a negative base and exponent is fine as long as the + // exponent is not fractional + elementary.power(-2.0, -1.0) + |> should.equal(Ok(-0.5)) +} + +pub fn float_square_root_test() { + elementary.square_root(1.0) + |> should.equal(Ok(1.0)) + + elementary.square_root(9.0) + |> should.equal(Ok(3.0)) + + // An error should be returned as an imaginary number would otherwise + // have to be returned + elementary.square_root(-1.0) + |> should.be_error() +} + +pub fn float_cube_root_test() { + elementary.cube_root(1.0) + |> should.equal(Ok(1.0)) + + elementary.cube_root(27.0) + |> should.equal(Ok(3.0)) + + // An error should be returned as an imaginary number would otherwise + // have to be returned + elementary.cube_root(-1.0) + |> should.be_error() +} + +pub fn float_nth_root_test() { + elementary.nth_root(9.0, 2) + |> should.equal(Ok(3.0)) + + elementary.nth_root(27.0, 3) + |> should.equal(Ok(3.0)) + + elementary.nth_root(1.0, 4) + |> should.equal(Ok(1.0)) + + elementary.nth_root(256.0, 4) + |> should.equal(Ok(4.0)) + + // An error should be returned as an imaginary number would otherwise + // have to be returned + elementary.nth_root(-1.0, 4) + |> should.be_error() +} + +pub fn float_constants_test() { + elementary.e() + |> tests.is_close(2.71828, 0.0, 0.00001) + |> should.be_true() + + elementary.pi() + |> tests.is_close(3.14159, 0.0, 0.00001) + |> should.be_true() +} diff --git a/test/gleam/gleam_community_maths_float_list_test.gleam b/test/gleam/gleam_community_maths_float_list_test.gleam deleted file mode 100644 index b51bb5c..0000000 --- a/test/gleam/gleam_community_maths_float_list_test.gleam +++ /dev/null @@ -1,519 +0,0 @@ -import gleam/int -import gleam/list -import gleam/pair -import gleam_community/maths/float_list -import gleam_community/maths/float as floatx -import gleeunit -import gleeunit/should -import gleam/io - -pub fn main() { - gleeunit.main() -} - -pub fn float_list_all_close_test() { - let val: Float = 99.0 - let ref_val: Float = 100.0 - let xarr: List(Float) = list.repeat(val, 42) - let yarr: List(Float) = list.repeat(ref_val, 42) - // We set 'atol' and 'rtol' such that the values are equivalent - // if 'val' is within 1 percent of 'ref_val' +/- 0.1 - let rtol: Float = 0.01 - let atol: Float = 0.1 - float_list.all_close(xarr, yarr, rtol, atol) - |> fn(zarr: Result(List(Bool), String)) -> Result(Bool, Nil) { - case zarr { - Ok(arr) -> - arr - |> list.all(fn(a: Bool) -> Bool { a }) - |> Ok - _ -> - Nil - |> Error - } - } - |> should.equal(Ok(True)) -} - -pub fn float_list_norm_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - - // An empty lists returns 0.0 - [] - |> float_list.norm(1.0) - |> should.equal(0.0) - - // Check that the function agrees, at some arbitrary input - // points, with known function values - [1.0, 1.0, 1.0] - |> float_list.norm(1.0) - |> floatx.is_close(3.0, 0.0, tol) - |> should.be_true() - - [1.0, 1.0, 1.0] - |> float_list.norm(-1.0) - |> floatx.is_close(0.3333333333333333, 0.0, tol) - |> should.be_true() - - [-1.0, -1.0, -1.0] - |> float_list.norm(-1.0) - |> floatx.is_close(0.3333333333333333, 0.0, tol) - |> should.be_true() - - [-1.0, -1.0, -1.0] - |> float_list.norm(1.0) - |> floatx.is_close(3.0, 0.0, tol) - |> should.be_true() - - [-1.0, -2.0, -3.0] - |> float_list.norm(-10.0) - |> floatx.is_close(0.9999007044905545, 0.0, tol) - |> should.be_true() - - [-1.0, -2.0, -3.0] - |> float_list.norm(-100.0) - |> floatx.is_close(1.0, 0.0, tol) - |> should.be_true() - - [-1.0, -2.0, -3.0] - |> float_list.norm(2.0) - |> floatx.is_close(3.7416573867739413, 0.0, tol) - |> should.be_true() -} - -pub fn float_list_minkowski_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - - // Empty lists returns 0.0 - float_list.minkowski_distance([], [], 1.0) - |> should.equal(Ok(0.0)) - - // Differing lengths returns error - float_list.minkowski_distance([], [1.0], 1.0) - |> should.be_error() - - // Test order < 1 - float_list.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0) - |> should.be_error() - - // Check that the function agrees, at some arbitrary input - // points, with known function values - let assert Ok(result) = - float_list.minkowski_distance([1.0, 1.0], [1.0, 1.0], 1.0) - result - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - let assert Ok(result) = - float_list.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0) - result - |> floatx.is_close(1.0717734625362931, 0.0, tol) - |> should.be_true() - - let assert Ok(result) = - float_list.minkowski_distance([0.0, 0.0], [1.0, 1.0], 100.0) - result - |> floatx.is_close(1.0069555500567189, 0.0, tol) - |> should.be_true() - - let assert Ok(result) = - float_list.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0) - result - |> floatx.is_close(1.0717734625362931, 0.0, tol) - |> should.be_true() - - // Euclidean distance (p = 2) - let assert Ok(result) = - float_list.minkowski_distance([0.0, 0.0], [1.0, 2.0], 2.0) - result - |> floatx.is_close(2.23606797749979, 0.0, tol) - |> should.be_true() - - // Manhatten distance (p = 1) - let assert Ok(result) = - float_list.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0) - result - |> floatx.is_close(3.0, 0.0, tol) - |> should.be_true() -} - -pub fn float_list_euclidean_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - - // Empty lists returns 0.0 - float_list.euclidean_distance([], []) - |> should.equal(Ok(0.0)) - - // Differing lengths returns error - float_list.euclidean_distance([], [1.0]) - |> should.be_error() - - // Euclidean distance (p = 2) - let assert Ok(result) = float_list.euclidean_distance([0.0, 0.0], [1.0, 2.0]) - result - |> floatx.is_close(2.23606797749979, 0.0, tol) - |> should.be_true() -} - -pub fn float_list_manhatten_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - - // Empty lists returns 0.0 - float_list.manhatten_distance([], []) - |> should.equal(Ok(0.0)) - - // Differing lengths returns error - float_list.manhatten_distance([], [1.0]) - |> should.be_error() - - // Manhatten distance (p = 1) - let assert Ok(result) = float_list.manhatten_distance([0.0, 0.0], [1.0, 2.0]) - result - |> floatx.is_close(3.0, 0.0, tol) - |> should.be_true() -} - -pub fn float_list_linear_space_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - - // Check that the function agrees, at some arbitrary input - // points, with known function values - // ---> With endpoint included - let assert Ok(linspace) = float_list.linear_space(10.0, 50.0, 5, True) - let assert Ok(result) = - float_list.all_close(linspace, [10.0, 20.0, 30.0, 40.0, 50.0], 0.0, tol) - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - let assert Ok(linspace) = float_list.linear_space(10.0, 20.0, 5, True) - let assert Ok(result) = - float_list.all_close(linspace, [10.0, 12.5, 15.0, 17.5, 20.0], 0.0, tol) - - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // Try with negative stop - // ----> Without endpoint included - let assert Ok(linspace) = float_list.linear_space(10.0, 50.0, 5, False) - let assert Ok(result) = - float_list.all_close(linspace, [10.0, 18.0, 26.0, 34.0, 42.0], 0.0, tol) - - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - let assert Ok(linspace) = float_list.linear_space(10.0, 20.0, 5, False) - let assert Ok(result) = - float_list.all_close(linspace, [10.0, 12.0, 14.0, 16.0, 18.0], 0.0, tol) - - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // Try with negative stop - let assert Ok(linspace) = float_list.linear_space(10.0, -50.0, 5, False) - let assert Ok(result) = - float_list.all_close(linspace, [10.0, -2.0, -14.0, -26.0, -38.0], 0.0, tol) - - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - let assert Ok(linspace) = float_list.linear_space(10.0, -20.0, 5, True) - let assert Ok(result) = - float_list.all_close(linspace, [10.0, 2.5, -5.0, -12.5, -20.0], 0.0, tol) - - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // Try with negative start - let assert Ok(linspace) = float_list.linear_space(-10.0, 50.0, 5, False) - let assert Ok(result) = - float_list.all_close(linspace, [-10.0, 2.0, 14.0, 26.0, 38.0], 0.0, tol) - - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - let assert Ok(linspace) = float_list.linear_space(-10.0, 20.0, 5, True) - let assert Ok(result) = - float_list.all_close(linspace, [-10.0, -2.5, 5.0, 12.5, 20.0], 0.0, tol) - - // A negative number of points does not work (-5) - float_list.linear_space(10.0, 50.0, -5, True) - |> should.be_error() -} - -pub fn float_list_logarithmic_space_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - // ---> With endpoint included - // - Positive start, stop, base - let assert Ok(logspace) = - float_list.logarithmic_space(1.0, 3.0, 3, True, 10.0) - let assert Ok(result) = - float_list.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // - Positive start, stop, negative base - let assert Ok(logspace) = - float_list.logarithmic_space(1.0, 3.0, 3, True, -10.0) - let assert Ok(result) = - float_list.all_close(logspace, [-10.0, 100.0, -1000.0], 0.0, tol) - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // - Positive start, negative stop, base - let assert Ok(logspace) = - float_list.logarithmic_space(1.0, -3.0, 3, True, -10.0) - let assert Ok(result) = - float_list.all_close(logspace, [-10.0, -0.1, -0.001], 0.0, tol) - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // - Positive start, base, negative stop - let assert Ok(logspace) = - float_list.logarithmic_space(1.0, -3.0, 3, True, 10.0) - let assert Ok(result) = - float_list.all_close(logspace, [10.0, 0.1, 0.001], 0.0, tol) - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // - Positive stop, base, negative start - let assert Ok(logspace) = - float_list.logarithmic_space(-1.0, 3.0, 3, True, 10.0) - let assert Ok(result) = - float_list.all_close(logspace, [0.1, 10.0, 1000.0], 0.0, tol) - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // ----> Without endpoint included - // - Positive start, stop, base - let assert Ok(logspace) = - float_list.logarithmic_space(1.0, 3.0, 3, False, 10.0) - let assert Ok(result) = - float_list.all_close(logspace, [10.0, 46.41588834, 215.443469], 0.0, tol) - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // A negative number of points does not work (-3) - float_list.logarithmic_space(1.0, 3.0, -3, True, 10.0) - |> should.be_error() -} - -pub fn float_list_geometric_space_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - // ---> With endpoint included - // - Positive start, stop - let assert Ok(logspace) = float_list.geometric_space(10.0, 1000.0, 3, True) - let assert Ok(result) = - float_list.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // - Positive start, negative stop - let assert Ok(logspace) = float_list.geometric_space(10.0, 0.001, 3, True) - let assert Ok(result) = - float_list.all_close(logspace, [10.0, 0.1, 0.001], 0.0, tol) - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // - Positive stop, negative start - let assert Ok(logspace) = float_list.geometric_space(0.1, 1000.0, 3, True) - let assert Ok(result) = - float_list.all_close(logspace, [0.1, 10.0, 1000.0], 0.0, tol) - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // ----> Without endpoint included - // - Positive start, stop - let assert Ok(logspace) = float_list.geometric_space(10.0, 1000.0, 3, False) - let assert Ok(result) = - float_list.all_close(logspace, [10.0, 46.41588834, 215.443469], 0.0, tol) - result - |> list.all(fn(x) { x == True }) - |> should.be_true() - - // Test invalid input (start and stop can't be equal to 0.0) - float_list.geometric_space(0.0, 1000.0, 3, False) - |> should.be_error() - - float_list.geometric_space(-1000.0, 0.0, 3, False) - |> should.be_error() - - // A negative number of points does not work - float_list.geometric_space(-1000.0, 0.0, -3, False) - |> should.be_error() -} - -pub fn float_list_arange_test() { - // Positive start, stop, step - float_list.arange(1.0, 5.0, 1.0) - |> should.equal([1.0, 2.0, 3.0, 4.0]) - - float_list.arange(1.0, 5.0, 0.5) - |> should.equal([1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5]) - - float_list.arange(1.0, 2.0, 0.25) - |> should.equal([1.0, 1.25, 1.5, 1.75]) - - // Reverse (switch start/stop largest/smallest value) - float_list.arange(5.0, 1.0, 1.0) - |> should.equal([]) - - // Reverse negative step - float_list.arange(5.0, 1.0, -1.0) - |> should.equal([5.0, 4.0, 3.0, 2.0]) - - // Positive start, negative stop, step - float_list.arange(5.0, -1.0, -1.0) - |> should.equal([5.0, 4.0, 3.0, 2.0, 1.0, 0.0]) - - // Negative start, stop, step - float_list.arange(-5.0, -1.0, -1.0) - |> should.equal([]) - - // Negative start, stop, positive step - float_list.arange(-5.0, -1.0, 1.0) - |> should.equal([-5.0, -4.0, -3.0, -2.0]) -} - -pub fn float_list_maximum_test() { - // An empty lists returns an error - [] - |> float_list.maximum() - |> should.be_error() - - // Valid input returns a result - [4.0, 4.0, 3.0, 2.0, 1.0] - |> float_list.maximum() - |> should.equal(Ok(4.0)) -} - -pub fn float_list_minimum_test() { - // An empty lists returns an error - [] - |> float_list.minimum() - |> should.be_error() - - // Valid input returns a result - [4.0, 4.0, 3.0, 2.0, 1.0] - |> float_list.minimum() - |> should.equal(Ok(1.0)) -} - -pub fn float_list_arg_maximum_test() { - // An empty lists returns an error - [] - |> float_list.arg_maximum() - |> should.be_error() - - // Valid input returns a result - [4.0, 4.0, 3.0, 2.0, 1.0] - |> float_list.arg_maximum() - |> should.equal(Ok([0, 1])) -} - -pub fn float_list_arg_minimum_test() { - // An empty lists returns an error - [] - |> float_list.arg_minimum() - |> should.be_error() - - // Valid input returns a result - [4.0, 4.0, 3.0, 2.0, 1.0] - |> float_list.arg_minimum() - |> should.equal(Ok([4])) -} - -pub fn float_list_extrema_test() { - // An empty lists returns an error - [] - |> float_list.extrema() - |> should.be_error() - - // Valid input returns a result - [4.0, 4.0, 3.0, 2.0, 1.0] - |> float_list.extrema() - |> should.equal(Ok(#(1.0, 4.0))) -} - -pub fn float_list_sum_test() { - // An empty list returns 0 - [] - |> float_list.sum() - |> should.equal(0.0) - - // Valid input returns a result - [1.0, 2.0, 3.0] - |> float_list.sum() - |> should.equal(6.0) - - [-2.0, 4.0, 6.0] - |> float_list.sum() - |> should.equal(8.0) -} - -pub fn float_list_cumulative_sum_test() { - // An empty lists returns an empty list - [] - |> float_list.cumulative_sum() - |> should.equal([]) - - // Valid input returns a result - [1.0, 2.0, 3.0] - |> float_list.cumulative_sum() - |> should.equal([1.0, 3.0, 6.0]) - - [-2.0, 4.0, 6.0] - |> float_list.cumulative_sum() - |> should.equal([-2.0, 2.0, 8.0]) -} - -pub fn float_list_product_test() { - // An empty list returns 0 - [] - |> float_list.product() - |> should.equal(1.0) - - // Valid input returns a result - [1.0, 2.0, 3.0] - |> float_list.product() - |> should.equal(6.0) - - [-2.0, 4.0, 6.0] - |> float_list.product() - |> should.equal(-48.0) -} - -pub fn float_list_cumulative_product_test() { - // An empty lists returns an empty list - [] - |> float_list.cumumlative_product() - |> should.equal([]) - - // Valid input returns a result - [1.0, 2.0, 3.0] - |> float_list.cumumlative_product() - |> should.equal([1.0, 2.0, 6.0]) - - [-2.0, 4.0, 6.0] - |> float_list.cumumlative_product() - |> should.equal([-2.0, -8.0, -48.0]) -} diff --git a/test/gleam/gleam_community_maths_float_test.gleam b/test/gleam/gleam_community_maths_float_test.gleam deleted file mode 100644 index a855aba..0000000 --- a/test/gleam/gleam_community_maths_float_test.gleam +++ /dev/null @@ -1,1069 +0,0 @@ -import gleam_community/maths/float as floatx -import gleeunit -import gleeunit/should -import gleam/result -import gleam/io -import gleam/option - -pub fn main() { - gleeunit.main() -} - -pub fn float_acos_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - let assert Ok(result) = floatx.acos(1.0) - result - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - let assert Ok(result) = floatx.acos(0.5) - result - |> floatx.is_close(1.047197, 0.0, tol) - |> should.be_true() - - // Check that we get an error when the function is evaluated - // outside its domain - floatx.acos(1.1) - |> should.be_error() - - floatx.acos(-1.1) - |> should.be_error() -} - -pub fn float_acosh_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - let assert Ok(result) = floatx.acosh(1.0) - result - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - // Check that we get an error when the function is evaluated - // outside its domain - floatx.acosh(0.0) - |> should.be_error() -} - -pub fn float_asin_test() { - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.asin(0.0) - |> should.equal(Ok(0.0)) - - let assert Ok(tol) = floatx.power(-10.0, -6.0) - let assert Ok(result) = floatx.asin(0.5) - result - |> floatx.is_close(0.523598, 0.0, tol) - |> should.be_true() - - // Check that we get an error when the function is evaluated - // outside its domain - floatx.asin(1.1) - |> should.be_error() - - floatx.asin(-1.1) - |> should.be_error() -} - -pub fn float_asinh_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.asinh(0.0) - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - floatx.asinh(0.5) - |> floatx.is_close(0.481211, 0.0, tol) - |> should.be_true() -} - -pub fn float_atan_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.atan(0.0) - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - floatx.atan(0.5) - |> floatx.is_close(0.463647, 0.0, tol) - |> should.be_true() -} - -pub fn math_atan2_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.atan2(0.0, 0.0) - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - floatx.atan2(0.0, 1.0) - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - // Check atan2(y=1.0, x=0.5) - // Should be equal to atan(y / x) for any x > 0 and any y - let result = floatx.atan(1.0 /. 0.5) - floatx.atan2(1.0, 0.5) - |> floatx.is_close(result, 0.0, tol) - |> should.be_true() - - // Check atan2(y=2.0, x=-1.5) - // Should be equal to pi + atan(y / x) for any x < 0 and y >= 0 - let result = floatx.pi() +. floatx.atan(2.0 /. -1.5) - floatx.atan2(2.0, -1.5) - |> floatx.is_close(result, 0.0, tol) - |> should.be_true() - - // Check atan2(y=-2.0, x=-1.5) - // Should be equal to atan(y / x) - pi for any x < 0 and y < 0 - let result = floatx.atan(-2.0 /. -1.5) -. floatx.pi() - floatx.atan2(-2.0, -1.5) - |> floatx.is_close(result, 0.0, tol) - |> should.be_true() - - // Check atan2(y=1.5, x=0.0) - // Should be equal to pi/2 for x = 0 and any y > 0 - let result = floatx.pi() /. 2.0 - floatx.atan2(1.5, 0.0) - |> floatx.is_close(result, 0.0, tol) - |> should.be_true() - - // Check atan2(y=-1.5, x=0.0) - // Should be equal to -pi/2 for x = 0 and any y < 0 - let result = -1.0 *. floatx.pi() /. 2.0 - floatx.atan2(-1.5, 0.0) - |> floatx.is_close(result, 0.0, tol) - |> should.be_true() -} - -pub fn float_atanh_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - let assert Ok(result) = floatx.atanh(0.0) - result - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - let assert Ok(result) = floatx.atanh(0.5) - result - |> floatx.is_close(0.549306, 0.0, tol) - |> should.be_true() - - // Check that we get an error when the function is evaluated - // outside its domain - floatx.atanh(1.0) - |> should.be_error() - - floatx.atanh(2.0) - |> should.be_error() - - floatx.atanh(1.0) - |> should.be_error() - - floatx.atanh(-2.0) - |> should.be_error() -} - -pub fn float_cos_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.cos(0.0) - |> floatx.is_close(1.0, 0.0, tol) - |> should.be_true() - - floatx.cos(floatx.pi()) - |> floatx.is_close(-1.0, 0.0, tol) - |> should.be_true() - - floatx.cos(0.5) - |> floatx.is_close(0.877582, 0.0, tol) - |> should.be_true() -} - -pub fn float_cosh_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.cosh(0.0) - |> floatx.is_close(1.0, 0.0, tol) - |> should.be_true() - - floatx.cosh(0.5) - |> floatx.is_close(1.127625, 0.0, tol) - |> should.be_true() - // An (overflow) error might occur when given an input - // value that will result in a too large output value - // e.g. floatx.cosh(1000.0) but this is a property of the - // runtime. -} - -pub fn float_exponential_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.exponential(0.0) - |> floatx.is_close(1.0, 0.0, tol) - |> should.be_true() - - floatx.exponential(0.5) - |> floatx.is_close(1.648721, 0.0, tol) - |> should.be_true() - // An (overflow) error might occur when given an input - // value that will result in a too large output value - // e.g. floatx.exponential(1000.0) but this is a property of the - // runtime. -} - -pub fn float_natural_logarithm_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.natural_logarithm(1.0) - |> should.equal(Ok(0.0)) - - let assert Ok(result) = floatx.natural_logarithm(0.5) - result - |> floatx.is_close(-0.693147, 0.0, tol) - |> should.be_true() - - // Check that we get an error when the function is evaluated - // outside its domain - floatx.natural_logarithm(-1.0) - |> should.be_error() -} - -pub fn float_logarithm_10_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - let assert Ok(result) = floatx.logarithm_10(1.0) - result - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - let assert Ok(result) = floatx.logarithm_10(10.0) - result - |> floatx.is_close(1.0, 0.0, tol) - |> should.be_true() - - let assert Ok(result) = floatx.logarithm_10(50.0) - result - |> floatx.is_close(1.69897, 0.0, tol) - |> should.be_true() - - // Check that we get an error when the function is evaluated - // outside its domain - floatx.logarithm_10(-1.0) - |> should.be_error() -} - -pub fn float_logarithm_2_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.logarithm_2(1.0) - |> should.equal(Ok(0.0)) - - floatx.logarithm_2(2.0) - |> should.equal(Ok(1.0)) - - let assert Ok(result) = floatx.logarithm_2(5.0) - result - |> floatx.is_close(2.321928, 0.0, tol) - |> should.be_true() - - // Check that we get an error when the function is evaluated - // outside its domain - floatx.logarithm_2(-1.0) - |> should.be_error() -} - -pub fn float_logarithm_test() { - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.logarithm(10.0, option.Some(10.0)) - |> should.equal(Ok(1.0)) - - floatx.logarithm(10.0, option.Some(100.0)) - |> should.equal(Ok(0.5)) - - floatx.logarithm(1.0, option.Some(0.25)) - |> should.equal(Ok(0.0)) - - // Check that we get an error when the function is evaluated - // outside its domain - floatx.logarithm(1.0, option.Some(1.0)) - |> should.be_error() - - floatx.logarithm(10.0, option.Some(1.0)) - |> should.be_error() - - floatx.logarithm(-1.0, option.Some(1.0)) - |> should.be_error() - - floatx.logarithm(1.0, option.Some(10.0)) - |> should.equal(Ok(0.0)) - - floatx.logarithm(floatx.e(), option.Some(floatx.e())) - |> should.equal(Ok(1.0)) - - floatx.logarithm(-1.0, option.Some(2.0)) - |> should.be_error() -} - -pub fn float_power_test() { - floatx.power(2.0, 2.0) - |> should.equal(Ok(4.0)) - - floatx.power(-5.0, 3.0) - |> should.equal(Ok(-125.0)) - - floatx.power(10.5, 0.0) - |> should.equal(Ok(1.0)) - - floatx.power(16.0, 0.5) - |> should.equal(Ok(4.0)) - - floatx.power(2.0, -1.0) - |> should.equal(Ok(0.5)) - - floatx.power(2.0, -1.0) - |> should.equal(Ok(0.5)) - - // floatx.power(-1.0, 0.5) is equivalent to float.square_root(-1.0) - // and should return an error as an imaginary number would otherwise - // have to be returned - floatx.power(-1.0, 0.5) - |> should.be_error() - - // Check another case with a negative base and fractional exponent - floatx.power(-1.5, 1.5) - |> should.be_error() - - // floatx.power(0.0, -1.0) is equivalent to 1. /. 0 and is expected - // to be an error - floatx.power(0.0, -1.0) - |> should.be_error() - - // Check that a negative base and exponent is fine as long as the - // exponent is not fractional - floatx.power(-2.0, -1.0) - |> should.equal(Ok(-0.5)) -} - -pub fn float_square_root_test() { - floatx.square_root(1.0) - |> should.equal(Ok(1.0)) - - floatx.square_root(9.0) - |> should.equal(Ok(3.0)) - - // An error should be returned as an imaginary number would otherwise - // have to be returned - floatx.square_root(-1.0) - |> should.be_error() -} - -pub fn float_cube_root_test() { - floatx.cube_root(1.0) - |> should.equal(Ok(1.0)) - - floatx.cube_root(27.0) - |> should.equal(Ok(3.0)) - - // An error should be returned as an imaginary number would otherwise - // have to be returned - floatx.cube_root(-1.0) - |> should.be_error() -} - -pub fn float_nth_root_test() { - floatx.nth_root(9.0, 2) - |> should.equal(Ok(3.0)) - - floatx.nth_root(27.0, 3) - |> should.equal(Ok(3.0)) - - floatx.nth_root(1.0, 4) - |> should.equal(Ok(1.0)) - - floatx.nth_root(256.0, 4) - |> should.equal(Ok(4.0)) - - // An error should be returned as an imaginary number would otherwise - // have to be returned - floatx.nth_root(-1.0, 4) - |> should.be_error() -} - -pub fn float_hypotenuse_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - - floatx.hypotenuse(0.0, 0.0) - |> should.equal(0.0) - - floatx.hypotenuse(1.0, 0.0) - |> should.equal(1.0) - - floatx.hypotenuse(0.0, 1.0) - |> should.equal(1.0) - - let result = floatx.hypotenuse(11.0, 22.0) - result - |> floatx.is_close(24.596747, 0.0, tol) - |> should.be_true() -} - -pub fn float_sin_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.sin(0.0) - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - floatx.sin(0.5 *. floatx.pi()) - |> floatx.is_close(1.0, 0.0, tol) - |> should.be_true() - - floatx.sin(0.5) - |> floatx.is_close(0.479425, 0.0, tol) - |> should.be_true() -} - -pub fn float_sinh_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.sinh(0.0) - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - floatx.sinh(0.5) - |> floatx.is_close(0.521095, 0.0, tol) - |> should.be_true() - // An (overflow) error might occur when given an input - // value that will result in a too large output value - // e.g. floatx.sinh(1000.0) but this is a property of the - // runtime. -} - -pub fn math_tan_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.tan(0.0) - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - floatx.tan(0.5) - |> floatx.is_close(0.546302, 0.0, tol) - |> should.be_true() -} - -pub fn math_tanh_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - // Check that the function agrees, at some arbitrary input - // points, with known function values - floatx.tanh(0.0) - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - floatx.tanh(25.0) - |> floatx.is_close(1.0, 0.0, tol) - |> should.be_true() - - floatx.tanh(-25.0) - |> floatx.is_close(-1.0, 0.0, tol) - |> should.be_true() - - floatx.tanh(0.5) - |> floatx.is_close(0.462117, 0.0, tol) - |> should.be_true() -} - -pub fn float_to_degree_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - floatx.to_degree(0.0) - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - floatx.to_degree(2.0 *. floatx.pi()) - |> floatx.is_close(360.0, 0.0, tol) - |> should.be_true() -} - -pub fn float_to_radian_test() { - let assert Ok(tol) = floatx.power(-10.0, -6.0) - floatx.to_radian(0.0) - |> floatx.is_close(0.0, 0.0, tol) - |> should.be_true() - - floatx.to_radian(360.0) - |> floatx.is_close(2.0 *. floatx.pi(), 0.0, tol) - |> should.be_true() -} - -pub fn float_ceiling_test() { - // Round 3. digit AFTER decimal point - floatx.ceiling(12.0654, option.Some(3)) - |> should.equal(Ok(12.066)) - - // Round 2. digit AFTER decimal point - floatx.ceiling(12.0654, option.Some(2)) - |> should.equal(Ok(12.07)) - - // Round 1. digit AFTER decimal point - floatx.ceiling(12.0654, option.Some(1)) - |> should.equal(Ok(12.1)) - - // Round 0. digit BEFORE decimal point - floatx.ceiling(12.0654, option.Some(0)) - |> should.equal(Ok(13.0)) - - // Round 1. digit BEFORE decimal point - floatx.ceiling(12.0654, option.Some(-1)) - |> should.equal(Ok(20.0)) - - // Round 2. digit BEFORE decimal point - floatx.ceiling(12.0654, option.Some(-2)) - |> should.equal(Ok(100.0)) - - // Round 3. digit BEFORE decimal point - floatx.ceiling(12.0654, option.Some(-3)) - |> should.equal(Ok(1000.0)) -} - -pub fn float_floor_test() { - // Round 3. digit AFTER decimal point - floatx.floor(12.0654, option.Some(3)) - |> should.equal(Ok(12.065)) - - // Round 2. digit AFTER decimal point - floatx.floor(12.0654, option.Some(2)) - |> should.equal(Ok(12.06)) - - // Round 1. digit AFTER decimal point - floatx.floor(12.0654, option.Some(1)) - |> should.equal(Ok(12.0)) - - // Round 0. digit BEFORE decimal point - floatx.floor(12.0654, option.Some(0)) - |> should.equal(Ok(12.0)) - - // Round 1. digit BEFORE decimal point - floatx.floor(12.0654, option.Some(-1)) - |> should.equal(Ok(10.0)) - - // Round 2. digit BEFORE decimal point - floatx.floor(12.0654, option.Some(-2)) - |> should.equal(Ok(0.0)) - - // Round 2. digit BEFORE decimal point - floatx.floor(12.0654, option.Some(-3)) - |> should.equal(Ok(0.0)) -} - -pub fn float_truncate_test() { - // Round 3. digit AFTER decimal point - floatx.truncate(12.0654, option.Some(3)) - |> should.equal(Ok(12.065)) - - // Round 2. digit AFTER decimal point - floatx.truncate(12.0654, option.Some(2)) - |> should.equal(Ok(12.06)) - - // Round 1. digit AFTER decimal point - floatx.truncate(12.0654, option.Some(1)) - |> should.equal(Ok(12.0)) - - // Round 0. digit BEFORE decimal point - floatx.truncate(12.0654, option.Some(0)) - |> should.equal(Ok(12.0)) - - // Round 1. digit BEFORE decimal point - floatx.truncate(12.0654, option.Some(-1)) - |> should.equal(Ok(10.0)) - - // Round 2. digit BEFORE decimal point - floatx.truncate(12.0654, option.Some(-2)) - |> should.equal(Ok(0.0)) - - // Round 2. digit BEFORE decimal point - floatx.truncate(12.0654, option.Some(-3)) - |> should.equal(Ok(0.0)) -} - -pub fn float_minimum_test() { - floatx.minimum(0.75, 0.5) - |> should.equal(0.5) - - floatx.minimum(0.5, 0.75) - |> should.equal(0.5) - - floatx.minimum(-0.75, 0.5) - |> should.equal(-0.75) - - floatx.minimum(-0.75, 0.5) - |> should.equal(-0.75) -} - -pub fn float_maximum_test() { - floatx.maximum(0.75, 0.5) - |> should.equal(0.75) - - floatx.maximum(0.5, 0.75) - |> should.equal(0.75) - - floatx.maximum(-0.75, 0.5) - |> should.equal(0.5) - - floatx.maximum(-0.75, 0.5) - |> should.equal(0.5) -} - -pub fn float_minmax_test() { - floatx.minmax(0.75, 0.5) - |> should.equal(#(0.5, 0.75)) - - floatx.minmax(0.5, 0.75) - |> should.equal(#(0.5, 0.75)) - - floatx.minmax(-0.75, 0.5) - |> should.equal(#(-0.75, 0.5)) - - floatx.minmax(-0.75, 0.5) - |> should.equal(#(-0.75, 0.5)) -} - -pub fn float_sign_test() { - floatx.sign(100.0) - |> should.equal(1.0) - - floatx.sign(0.0) - |> should.equal(0.0) - - floatx.sign(-100.0) - |> should.equal(-1.0) -} - -pub fn float_flip_sign_test() { - floatx.flip_sign(100.0) - |> should.equal(-100.0) - - floatx.flip_sign(0.0) - |> should.equal(-0.0) - - floatx.flip_sign(-100.0) - |> should.equal(100.0) -} - -pub fn float_copy_sign_test() { - floatx.copy_sign(100.0, 10.0) - |> should.equal(100.0) - - floatx.copy_sign(-100.0, 10.0) - |> should.equal(100.0) - - floatx.copy_sign(100.0, -10.0) - |> should.equal(-100.0) - - floatx.copy_sign(-100.0, -10.0) - |> should.equal(-100.0) -} - -pub fn float_beta_function_test() { - io.debug("TODO: Implement tests for 'float.beta'.") -} - -pub fn float_error_function_test() { - io.debug("TODO: Implement tests for 'float.erf'.") -} - -pub fn float_gamma_function_test() { - io.debug("TODO: Implement tests for 'float.gamma'.") -} - -pub fn math_round_to_nearest_test() { - // Try with positive values - floatx.round(1.5, option.Some(0), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(2.0)) - - floatx.round(1.75, option.Some(0), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(2.0)) - - floatx.round(2.0, option.Some(0), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(2.0)) - - floatx.round(3.5, option.Some(0), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(4.0)) - - floatx.round(4.5, option.Some(0), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(4.0)) - - // Try with negative values - floatx.round(-3.5, option.Some(0), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(-4.0)) - - floatx.round(-4.5, option.Some(0), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(-4.0)) - - // Round 3. digit AFTER decimal point - floatx.round(12.0654, option.Some(3), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(12.065)) - - // Round 2. digit AFTER decimal point - floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(12.07)) - - // Round 1. digit AFTER decimal point - floatx.round(12.0654, option.Some(1), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(12.1)) - - // Round 0. digit BEFORE decimal point - floatx.round(12.0654, option.Some(0), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(12.0)) - - // Round 1. digit BEFORE decimal point - floatx.round(12.0654, option.Some(-1), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(10.0)) - - // Round 2. digit BEFORE decimal point - floatx.round(12.0654, option.Some(-2), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(0.0)) - - // Round 3. digit BEFORE decimal point - floatx.round(12.0654, option.Some(-3), option.Some(floatx.RoundNearest)) - |> should.equal(Ok(0.0)) -} - -pub fn math_round_up_test() { - // Note: Rounding mode "RoundUp" is an alias for the ceiling function - // Try with positive values - floatx.round(0.45, option.Some(0), option.Some(floatx.RoundUp)) - |> should.equal(Ok(1.0)) - - floatx.round(0.5, option.Some(0), option.Some(floatx.RoundUp)) - |> should.equal(Ok(1.0)) - - floatx.round(0.45, option.Some(1), option.Some(floatx.RoundUp)) - |> should.equal(Ok(0.5)) - - floatx.round(0.5, option.Some(1), option.Some(floatx.RoundUp)) - |> should.equal(Ok(0.5)) - - floatx.round(0.455, option.Some(2), option.Some(floatx.RoundUp)) - |> should.equal(Ok(0.46)) - - floatx.round(0.505, option.Some(2), option.Some(floatx.RoundUp)) - |> should.equal(Ok(0.51)) - - // Try with negative values - floatx.round(-0.45, option.Some(0), option.Some(floatx.RoundUp)) - |> should.equal(Ok(-0.0)) - - floatx.round(-0.5, option.Some(0), option.Some(floatx.RoundUp)) - |> should.equal(Ok(-0.0)) - - floatx.round(-0.45, option.Some(1), option.Some(floatx.RoundUp)) - |> should.equal(Ok(-0.4)) - - floatx.round(-0.5, option.Some(1), option.Some(floatx.RoundUp)) - |> should.equal(Ok(-0.5)) - - floatx.round(-0.455, option.Some(2), option.Some(floatx.RoundUp)) - |> should.equal(Ok(-0.45)) - - floatx.round(-0.505, option.Some(2), option.Some(floatx.RoundUp)) - |> should.equal(Ok(-0.5)) -} - -pub fn math_round_down_test() { - // Note: Rounding mode "RoundDown" is an alias for the floor function - // Try with positive values - floatx.round(0.45, option.Some(0), option.Some(floatx.RoundDown)) - |> should.equal(Ok(0.0)) - - floatx.round(0.5, option.Some(0), option.Some(floatx.RoundDown)) - |> should.equal(Ok(0.0)) - - floatx.round(0.45, option.Some(1), option.Some(floatx.RoundDown)) - |> should.equal(Ok(0.4)) - - floatx.round(0.5, option.Some(1), option.Some(floatx.RoundDown)) - |> should.equal(Ok(0.5)) - - floatx.round(0.455, option.Some(2), option.Some(floatx.RoundDown)) - |> should.equal(Ok(0.45)) - - floatx.round(0.505, option.Some(2), option.Some(floatx.RoundDown)) - |> should.equal(Ok(0.5)) - - // Try with negative values - floatx.round(-0.45, option.Some(0), option.Some(floatx.RoundDown)) - |> should.equal(Ok(-1.0)) - - floatx.round(-0.5, option.Some(0), option.Some(floatx.RoundDown)) - |> should.equal(Ok(-1.0)) - - floatx.round(-0.45, option.Some(1), option.Some(floatx.RoundDown)) - |> should.equal(Ok(-0.5)) - - floatx.round(-0.5, option.Some(1), option.Some(floatx.RoundDown)) - |> should.equal(Ok(-0.5)) - - floatx.round(-0.455, option.Some(2), option.Some(floatx.RoundDown)) - |> should.equal(Ok(-0.46)) - - floatx.round(-0.505, option.Some(2), option.Some(floatx.RoundDown)) - |> should.equal(Ok(-0.51)) -} - -pub fn math_round_to_zero_test() { - // Note: Rounding mode "RoundToZero" is an alias for the truncate function - // Try with positive values - floatx.round(0.5, option.Some(0), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(0.0)) - - floatx.round(0.75, option.Some(0), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(0.0)) - - floatx.round(0.45, option.Some(1), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(0.4)) - - floatx.round(0.57, option.Some(1), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(0.5)) - - floatx.round(0.4575, option.Some(2), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(0.45)) - - floatx.round(0.5075, option.Some(2), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(0.5)) - - // Try with negative values - floatx.round(-0.5, option.Some(0), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(0.0)) - - floatx.round(-0.75, option.Some(0), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(0.0)) - - floatx.round(-0.45, option.Some(1), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(-0.4)) - - floatx.round(-0.57, option.Some(1), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(-0.5)) - - floatx.round(-0.4575, option.Some(2), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(-0.45)) - - floatx.round(-0.5075, option.Some(2), option.Some(floatx.RoundToZero)) - |> should.equal(Ok(-0.5)) -} - -pub fn math_round_ties_away_test() { - // Try with positive values - floatx.round(1.4, option.Some(0), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(1.0)) - - floatx.round(1.5, option.Some(0), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(2.0)) - - floatx.round(2.5, option.Some(0), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(3.0)) - - // Try with negative values - floatx.round(-1.4, option.Some(0), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(-1.0)) - - floatx.round(-1.5, option.Some(0), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(-2.0)) - - floatx.round(-2.0, option.Some(0), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(-2.0)) - - floatx.round(-2.5, option.Some(0), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(-3.0)) - - // Round 3. digit AFTER decimal point - floatx.round(12.0654, option.Some(3), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(12.065)) - - // Round 2. digit AFTER decimal point - floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(12.07)) - - // Round 1. digit AFTER decimal point - floatx.round(12.0654, option.Some(1), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(12.1)) - - // Round 0. digit BEFORE decimal point - floatx.round(12.0654, option.Some(0), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(12.0)) - - // Round 1. digit BEFORE decimal point - floatx.round(12.0654, option.Some(-1), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(10.0)) - - // Round 2. digit BEFORE decimal point - floatx.round(12.0654, option.Some(-2), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(0.0)) - - // Round 2. digit BEFORE decimal point - floatx.round(12.0654, option.Some(-3), option.Some(floatx.RoundTiesAway)) - |> should.equal(Ok(0.0)) -} - -pub fn math_round_ties_up_test() { - // Try with positive values - floatx.round(1.4, option.Some(0), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(1.0)) - - floatx.round(1.5, option.Some(0), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(2.0)) - - floatx.round(2.5, option.Some(0), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(3.0)) - - // Try with negative values - floatx.round(-1.4, option.Some(0), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(-1.0)) - - floatx.round(-1.5, option.Some(0), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(-1.0)) - - floatx.round(-2.0, option.Some(0), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(-2.0)) - - floatx.round(-2.5, option.Some(0), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(-2.0)) - - // Round 3. digit AFTER decimal point - floatx.round(12.0654, option.Some(3), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(12.065)) - - // Round 2. digit AFTER decimal point - floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(12.07)) - - // Round 1. digit AFTER decimal point - floatx.round(12.0654, option.Some(1), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(12.1)) - - // Round 0. digit BEFORE decimal point - floatx.round(12.0654, option.Some(0), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(12.0)) - - // Round 1. digit BEFORE decimal point - floatx.round(12.0654, option.Some(-1), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(10.0)) - - // Round 2. digit BEFORE decimal point - floatx.round(12.0654, option.Some(-2), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(0.0)) - - // Round 2. digit BEFORE decimal point - floatx.round(12.0654, option.Some(-3), option.Some(floatx.RoundTiesUp)) - |> should.equal(Ok(0.0)) -} - -pub fn math_round_edge_cases_test() { - // The default number of digits is 0 if None is provided - floatx.round(12.0654, option.None, option.Some(floatx.RoundNearest)) - |> should.equal(Ok(12.0)) - - // The default rounding mode is floatx.RoundNearest if None is provided - floatx.round(12.0654, option.None, option.None) - |> should.equal(Ok(12.0)) -} - -pub fn float_incomplete_gamma_function_test() { - // Invalid input gives an error - // 1st arg is invalid - floatx.incomplete_gamma(-1.0, 1.0) - |> should.be_error() - - // 2nd arg is invalid - floatx.incomplete_gamma(1.0, -1.0) - |> should.be_error() - - // Valid input returns a result - floatx.incomplete_gamma(1.0, 0.0) - |> result.unwrap(-999.0) - |> floatx.is_close(0.0, 0.0, 0.01) - |> should.be_true() - - floatx.incomplete_gamma(1.0, 2.0) - |> result.unwrap(-999.0) - |> floatx.is_close(0.864664716763387308106, 0.0, 0.01) - |> should.be_true() - - floatx.incomplete_gamma(2.0, 3.0) - |> result.unwrap(-999.0) - |> floatx.is_close(0.8008517265285442280826, 0.0, 0.01) - |> should.be_true() - - floatx.incomplete_gamma(3.0, 4.0) - |> result.unwrap(-999.0) - |> floatx.is_close(1.523793388892911312363, 0.0, 0.01) - |> should.be_true() -} - -pub fn float_absolute_difference_test() { - floatx.absolute_difference(0.0, 0.0) - |> should.equal(0.0) - - floatx.absolute_difference(1.0, 2.0) - |> should.equal(1.0) - - floatx.absolute_difference(2.0, 1.0) - |> should.equal(1.0) - - floatx.absolute_difference(-1.0, 0.0) - |> should.equal(1.0) - - floatx.absolute_difference(0.0, -1.0) - |> should.equal(1.0) - - floatx.absolute_difference(10.0, 20.0) - |> should.equal(10.0) - - floatx.absolute_difference(-10.0, -20.0) - |> should.equal(10.0) - - floatx.absolute_difference(-10.5, 10.5) - |> should.equal(21.0) -} - -pub fn float_constants_test() { - floatx.e() - |> floatx.is_close(2.71828, 0.0, 0.00001) - |> should.be_true() - - floatx.pi() - |> floatx.is_close(3.14159, 0.0, 0.00001) - |> should.be_true() -} - -pub fn float_is_close_test() { - let val: Float = 99.0 - let ref_val: Float = 100.0 - // We set 'atol' and 'rtol' such that the values are equivalent - // if 'val' is within 1 percent of 'ref_val' +/- 0.1 - let rtol: Float = 0.01 - let atol: Float = 0.1 - floatx.is_close(val, ref_val, rtol, atol) - |> should.be_true() -} - -pub fn float_to_int_test() { - floatx.to_int(12.0654) - |> should.equal(12) -} diff --git a/test/gleam/gleam_community_maths_int_list_test.gleam b/test/gleam/gleam_community_maths_int_list_test.gleam deleted file mode 100644 index 083a39d..0000000 --- a/test/gleam/gleam_community_maths_int_list_test.gleam +++ /dev/null @@ -1,148 +0,0 @@ -import gleam/int -import gleam/list -import gleam/pair -import gleam_community/maths/int_list -import gleeunit -import gleeunit/should - -pub fn main() { - gleeunit.main() -} - -pub fn int_list_maximum_test() { - // An empty lists returns an error - [] - |> int_list.maximum() - |> should.be_error() - - // Valid input returns a result - [4, 4, 3, 2, 1] - |> int_list.maximum() - |> should.equal(Ok(4)) -} - -pub fn int_list_minimum_test() { - // An empty lists returns an error - [] - |> int_list.minimum() - |> should.be_error() - - // Valid input returns a result - [4, 4, 3, 2, 1] - |> int_list.minimum() - |> should.equal(Ok(1)) -} - -pub fn int_list_arg_maximum_test() { - // An empty lists returns an error - [] - |> int_list.arg_maximum() - |> should.be_error() - - // Valid input returns a result - [4, 4, 3, 2, 1] - |> int_list.arg_maximum() - |> should.equal(Ok([0, 1])) -} - -pub fn int_list_arg_minimum_test() { - // An empty lists returns an error - [] - |> int_list.arg_minimum() - |> should.be_error() - - // Valid input returns a result - [4, 4, 3, 2, 1] - |> int_list.arg_minimum() - |> should.equal(Ok([4])) -} - -pub fn int_list_extrema_test() { - // An empty lists returns an error - [] - |> int_list.extrema() - |> should.be_error() - - // Valid input returns a result - [4, 4, 3, 2, 1] - |> int_list.extrema() - |> should.equal(Ok(#(1, 4))) -} - -pub fn int_list_sum_test() { - // An empty list returns 0 - [] - |> int_list.sum() - |> should.equal(0) - - // Valid input returns a result - [1, 2, 3] - |> int_list.sum() - |> should.equal(6) - - [-2, 4, 6] - |> int_list.sum() - |> should.equal(8) -} - -pub fn int_list_cumulative_sum_test() { - // An empty lists returns an empty list - [] - |> int_list.cumulative_sum() - |> should.equal([]) - - // Valid input returns a result - [1, 2, 3] - |> int_list.cumulative_sum() - |> should.equal([1, 3, 6]) - - [-2, 4, 6] - |> int_list.cumulative_sum() - |> should.equal([-2, 2, 8]) -} - -pub fn int_list_product_test() { - // An empty list returns 0 - [] - |> int_list.product() - |> should.equal(1) - - // Valid input returns a result - [1, 2, 3] - |> int_list.product() - |> should.equal(6) - - [-2, 4, 6] - |> int_list.product() - |> should.equal(-48) -} - -pub fn int_list_cumulative_product_test() { - // An empty lists returns an empty list - [] - |> int_list.cumulative_product() - |> should.equal([]) - - // Valid input returns a result - [1, 2, 3] - |> int_list.cumulative_product() - |> should.equal([1, 2, 6]) - - [-2, 4, 6] - |> int_list.cumulative_product() - |> should.equal([-2, -8, -48]) -} - -pub fn int_list_manhatten_test() { - // Empty lists returns 0 - int_list.manhatten_distance([], []) - |> should.equal(Ok(0)) - - // Differing lengths returns error - int_list.manhatten_distance([], [1]) - |> should.be_error() - - let assert Ok(result) = int_list.manhatten_distance([0, 0], [1, 2]) - result - |> should.equal(3) -} diff --git a/test/gleam/gleam_community_maths_int_test.gleam b/test/gleam/gleam_community_maths_int_test.gleam deleted file mode 100644 index af4809f..0000000 --- a/test/gleam/gleam_community_maths_int_test.gleam +++ /dev/null @@ -1,327 +0,0 @@ -import gleam_community/maths/int as intx -import gleeunit -import gleeunit/should -import gleam/result -import gleam/io - -pub fn int_absolute_difference_test() { - intx.absolute_difference(0, 0) - |> should.equal(0) - - intx.absolute_difference(1, 2) - |> should.equal(1) - - intx.absolute_difference(2, 1) - |> should.equal(1) - - intx.absolute_difference(-1, 0) - |> should.equal(1) - - intx.absolute_difference(0, -1) - |> should.equal(1) - - intx.absolute_difference(10, 20) - |> should.equal(10) - - intx.absolute_difference(-10, -20) - |> should.equal(10) - - intx.absolute_difference(-10, 10) - |> should.equal(20) -} - -pub fn int_factorial_test() { - // Invalid input gives an error - intx.factorial(-1) - |> should.be_error() - - // Valid input returns a result - intx.factorial(0) - |> should.equal(Ok(1)) - - intx.factorial(1) - |> should.equal(Ok(1)) - - intx.factorial(2) - |> should.equal(Ok(2)) - - intx.factorial(3) - |> should.equal(Ok(6)) - - intx.factorial(4) - |> should.equal(Ok(24)) -} - -pub fn int_combination_test() { - // Invalid input gives an error - // Error on: n = -1 < 0 - intx.combination(-1, 1) - |> should.be_error() - - // Valid input returns a result - intx.combination(4, 0) - |> should.equal(Ok(1)) - - intx.combination(4, 4) - |> should.equal(Ok(1)) - - intx.combination(4, 2) - |> should.equal(Ok(6)) - - intx.combination(7, 5) - |> should.equal(Ok(21)) - // NOTE: Tests with the 'combination' function that produce values that - // exceed precision of the JavaScript 'Number' primitive will result in - // errors -} - -pub fn math_permutation_test() { - // Invalid input gives an error - // Error on: n = -1 < 0 - intx.permutation(-1, 1) - |> should.be_error() - - // Valid input returns a result - intx.permutation(4, 0) - |> should.equal(Ok(1)) - - intx.permutation(4, 4) - |> should.equal(Ok(1)) - - intx.permutation(4, 2) - |> should.equal(Ok(12)) -} - -pub fn float_minimum_test() { - intx.minimum(75, 50) - |> should.equal(50) - - intx.minimum(50, 75) - |> should.equal(50) - - intx.minimum(-75, 50) - |> should.equal(-75) - - intx.minimum(-75, 50) - |> should.equal(-75) -} - -pub fn float_maximum_test() { - intx.maximum(75, 50) - |> should.equal(75) - - intx.maximum(50, 75) - |> should.equal(75) - - intx.maximum(-75, 50) - |> should.equal(50) - - intx.maximum(-75, 50) - |> should.equal(50) -} - -pub fn int_minmax_test() { - intx.minmax(75, 50) - |> should.equal(#(50, 75)) - - intx.minmax(50, 75) - |> should.equal(#(50, 75)) - - intx.minmax(-75, 50) - |> should.equal(#(-75, 50)) - - intx.minmax(-75, 50) - |> should.equal(#(-75, 50)) -} - -pub fn int_sign_test() { - intx.sign(100) - |> should.equal(1) - - intx.sign(0) - |> should.equal(0) - - intx.sign(-100) - |> should.equal(-1) -} - -pub fn int_flip_sign_test() { - intx.flip_sign(100) - |> should.equal(-100) - - intx.flip_sign(0) - |> should.equal(-0) - - intx.flip_sign(-100) - |> should.equal(100) -} - -pub fn int_copy_sign_test() { - intx.copy_sign(100, 10) - |> should.equal(100) - - intx.copy_sign(-100, 10) - |> should.equal(100) - - intx.copy_sign(100, -10) - |> should.equal(-100) - - intx.copy_sign(-100, -10) - |> should.equal(-100) -} - -pub fn int_is_power_test() { - intx.is_power(10, 10) - |> should.equal(True) - - intx.is_power(11, 10) - |> should.equal(False) - - intx.is_power(4, 2) - |> should.equal(True) - - intx.is_power(5, 2) - |> should.equal(False) - - intx.is_power(27, 3) - |> should.equal(True) - - intx.is_power(28, 3) - |> should.equal(False) -} - -pub fn int_is_even_test() { - intx.is_even(0) - |> should.equal(True) - - intx.is_even(2) - |> should.equal(True) - - intx.is_even(12) - |> should.equal(True) - - intx.is_even(5) - |> should.equal(False) - - intx.is_even(-3) - |> should.equal(False) - - intx.is_even(-4) - |> should.equal(True) -} - -pub fn int_is_odd_test() { - intx.is_odd(0) - |> should.equal(False) - - intx.is_odd(3) - |> should.equal(True) - - intx.is_odd(13) - |> should.equal(True) - - intx.is_odd(4) - |> should.equal(False) - - intx.is_odd(-3) - |> should.equal(True) - - intx.is_odd(-4) - |> should.equal(False) -} - -pub fn int_gcd_test() { - intx.gcd(1, 1) - |> should.equal(1) - - intx.gcd(100, 10) - |> should.equal(10) - - intx.gcd(10, 100) - |> should.equal(10) - - intx.gcd(100, -10) - |> should.equal(10) - - intx.gcd(-36, -17) - |> should.equal(1) - - intx.gcd(-30, -42) - |> should.equal(6) -} - -pub fn int_lcm_test() { - intx.lcm(1, 1) - |> should.equal(1) - - intx.lcm(100, 10) - |> should.equal(100) - - intx.lcm(10, 100) - |> should.equal(100) - - intx.lcm(100, -10) - |> should.equal(100) - - intx.lcm(-36, -17) - |> should.equal(612) - - intx.lcm(-30, -42) - |> should.equal(210) -} - -pub fn int_to_float_test() { - intx.to_float(-1) - |> should.equal(-1.0) - - intx.to_float(1) - |> should.equal(1.0) -} - -pub fn int_proper_divisors_test() { - intx.proper_divisors(2) - |> should.equal([1]) - - intx.proper_divisors(6) - |> io.debug() - |> should.equal([1, 2, 3]) - intx.proper_divisors(13) - |> should.equal([1]) - - intx.proper_divisors(18) - |> should.equal([1, 2, 3, 6, 9]) -} - -pub fn int_divisors_test() { - intx.divisors(2) - |> should.equal([1, 2]) - - intx.divisors(6) - |> should.equal([1, 2, 3, 6]) - - intx.divisors(13) - |> should.equal([1, 13]) - - intx.divisors(18) - |> should.equal([1, 2, 3, 6, 9, 18]) -} - -pub fn int_is_perfect_test() { - intx.is_perfect(6) - |> should.equal(True) - - intx.is_perfect(28) - |> should.equal(True) - - intx.is_perfect(496) - |> should.equal(True) - - intx.is_perfect(1) - |> should.equal(False) - - intx.is_perfect(3) - |> should.equal(False) - - intx.is_perfect(13) - |> should.equal(False) -} diff --git a/test/gleam/gleam_community_maths_list_test.gleam b/test/gleam/gleam_community_maths_list_test.gleam deleted file mode 100644 index 811c9a8..0000000 --- a/test/gleam/gleam_community_maths_list_test.gleam +++ /dev/null @@ -1,83 +0,0 @@ -import gleam/int -import gleam/list -import gleam/pair -import gleam_community/maths/list as listx -import gleeunit -import gleeunit/should -import gleam/io -import gleam/set - -pub fn main() { - gleeunit.main() -} - -pub fn list_trim_test() { - // An empty lists returns an error - [] - |> listx.trim(0, 0) - |> should.be_error() - - // Trim the list to only the middle part of list - [1.0, 2.0, 3.0, 4.0, 5.0, 6.0] - |> listx.trim(1, 4) - |> should.equal(Ok([2.0, 3.0, 4.0, 5.0])) -} - -pub fn list_cartesian_product_test() { - // An empty lists returns an empty list - [] - |> listx.cartesian_product([]) - |> should.equal([]) - - // Test with some arbitrary inputs - [1, 2, 3] - |> listx.cartesian_product([1, 2, 3]) - |> set.from_list() - |> should.equal(set.from_list([ - #(1, 1), - #(1, 2), - #(1, 3), - #(2, 1), - #(2, 2), - #(2, 3), - #(3, 1), - #(3, 2), - #(3, 3), - ])) - - [1.0, 10.0] - |> listx.cartesian_product([1.0, 2.0]) - |> set.from_list() - |> should.equal(set.from_list([ - #(1.0, 1.0), - #(1.0, 2.0), - #(10.0, 1.0), - #(10.0, 2.0), - ])) -} - -pub fn list_permutation_test() { - io.debug("TODO: Implement tests for 'list.permutation'.") - // // An empty lists returns an empty list - // [] - // |> listx.permutation([]) - // |> should.equal([[]]) - - // Test with some arbitrary inputs - // [1, 2] - // |> listx.permutation() - - // should.be_error(Ok(1)) - // |> should.equal([[1, 2], [2, 1]]) - // // Test with some arbitrary inputs - // [1, 2, 3] - // |> listx.permutation() - // |> should.equal([ - // [1, 2, 3], - // [2, 1, 3], - // [3, 1, 2], - // [1, 3, 2], - // [2, 3, 1], - // [3, 2, 1], - // ]) -} diff --git a/test/gleam/gleam_community_maths_metrics.gleam b/test/gleam/gleam_community_maths_metrics.gleam new file mode 100644 index 0000000..0c91b6d --- /dev/null +++ b/test/gleam/gleam_community_maths_metrics.gleam @@ -0,0 +1,220 @@ +import gleam_community/maths/elementary +import gleam_community/maths/metrics +import gleam_community/maths/tests +import gleeunit +import gleeunit/should + +pub fn main() { + gleeunit.main() +} + +pub fn float_list_norm_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + + // An empty lists returns 0.0 + [] + |> metrics.norm(1.0) + |> should.equal(0.0) + + // Check that the function agrees, at some arbitrary input + // points, with known function values + [1.0, 1.0, 1.0] + |> metrics.norm(1.0) + |> tests.is_close(3.0, 0.0, tol) + |> should.be_true() + + [1.0, 1.0, 1.0] + |> metrics.norm(-1.0) + |> tests.is_close(0.3333333333333333, 0.0, tol) + |> should.be_true() + + [-1.0, -1.0, -1.0] + |> metrics.norm(-1.0) + |> tests.is_close(0.3333333333333333, 0.0, tol) + |> should.be_true() + + [-1.0, -1.0, -1.0] + |> metrics.norm(1.0) + |> tests.is_close(3.0, 0.0, tol) + |> should.be_true() + + [-1.0, -2.0, -3.0] + |> metrics.norm(-10.0) + |> tests.is_close(0.9999007044905545, 0.0, tol) + |> should.be_true() + + [-1.0, -2.0, -3.0] + |> metrics.norm(-100.0) + |> tests.is_close(1.0, 0.0, tol) + |> should.be_true() + + [-1.0, -2.0, -3.0] + |> metrics.norm(2.0) + |> tests.is_close(3.7416573867739413, 0.0, tol) + |> should.be_true() +} + +pub fn float_list_manhatten_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + + // Empty lists returns 0.0 + metrics.float_manhatten_distance([], []) + |> should.equal(Ok(0.0)) + + // Differing lengths returns error + metrics.float_manhatten_distance([], [1.0]) + |> should.be_error() + + // Manhatten distance (p = 1) + let assert Ok(result) = + metrics.float_manhatten_distance([0.0, 0.0], [1.0, 2.0]) + result + |> tests.is_close(3.0, 0.0, tol) + |> should.be_true() +} + +pub fn int_list_manhatten_test() { + // Empty lists returns 0 + metrics.int_manhatten_distance([], []) + |> should.equal(Ok(0)) + + // Differing lengths returns error + metrics.int_manhatten_distance([], [1]) + |> should.be_error() + + let assert Ok(result) = metrics.int_manhatten_distance([0, 0], [1, 2]) + result + |> should.equal(3) +} + +pub fn float_list_minkowski_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + + // Empty lists returns 0.0 + metrics.minkowski_distance([], [], 1.0) + |> should.equal(Ok(0.0)) + + // Differing lengths returns error + metrics.minkowski_distance([], [1.0], 1.0) + |> should.be_error() + + // Test order < 1 + metrics.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0) + |> should.be_error() + + // Check that the function agrees, at some arbitrary input + // points, with known function values + let assert Ok(result) = + metrics.minkowski_distance([1.0, 1.0], [1.0, 1.0], 1.0) + result + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + let assert Ok(result) = + metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0) + result + |> tests.is_close(1.0717734625362931, 0.0, tol) + |> should.be_true() + + let assert Ok(result) = + metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 100.0) + result + |> tests.is_close(1.0069555500567189, 0.0, tol) + |> should.be_true() + + let assert Ok(result) = + metrics.minkowski_distance([0.0, 0.0], [1.0, 1.0], 10.0) + result + |> tests.is_close(1.0717734625362931, 0.0, tol) + |> should.be_true() + + // Euclidean distance (p = 2) + let assert Ok(result) = + metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 2.0) + result + |> tests.is_close(2.23606797749979, 0.0, tol) + |> should.be_true() + + // Manhatten distance (p = 1) + let assert Ok(result) = + metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0) + result + |> tests.is_close(3.0, 0.0, tol) + |> should.be_true() +} + +pub fn float_list_euclidean_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + + // Empty lists returns 0.0 + metrics.euclidean_distance([], []) + |> should.equal(Ok(0.0)) + + // Differing lengths returns error + metrics.euclidean_distance([], [1.0]) + |> should.be_error() + + // Euclidean distance (p = 2) + let assert Ok(result) = metrics.euclidean_distance([0.0, 0.0], [1.0, 2.0]) + result + |> tests.is_close(2.23606797749979, 0.0, tol) + |> should.be_true() +} + +pub fn example_mean_test() { + // An empty list returns an error + [] + |> metrics.mean() + |> should.be_error() + + // Valid input returns a result + [1.0, 2.0, 3.0] + |> metrics.mean() + |> should.equal(Ok(2.0)) +} + +pub fn example_median_test() { + // An empty list returns an error + [] + |> metrics.median() + |> should.be_error() + + // Valid input returns a result + [1.0, 2.0, 3.0] + |> metrics.median() + |> should.equal(Ok(2.0)) + + [1.0, 2.0, 3.0, 4.0] + |> metrics.median() + |> should.equal(Ok(2.5)) +} + +pub fn example_variance_test() { + // Degrees of freedom + let ddof: Int = 1 + + // An empty list returns an error + [] + |> metrics.variance(ddof) + |> should.be_error() + + // Valid input returns a result + [1.0, 2.0, 3.0] + |> metrics.variance(ddof) + |> should.equal(Ok(1.0)) +} + +pub fn example_standard_deviation_test() { + // Degrees of freedom + let ddof: Int = 1 + + // An empty list returns an error + [] + |> metrics.standard_deviation(ddof) + |> should.be_error() + + // Valid input returns a result + [1.0, 2.0, 3.0] + |> metrics.standard_deviation(ddof) + |> should.equal(Ok(1.0)) +} diff --git a/test/gleam/gleam_community_maths_piecewise.gleam b/test/gleam/gleam_community_maths_piecewise.gleam new file mode 100644 index 0000000..c116e88 --- /dev/null +++ b/test/gleam/gleam_community_maths_piecewise.gleam @@ -0,0 +1,758 @@ +import gleam_community/maths/piecewise +import gleeunit +import gleeunit/should +import gleam/option +import gleam/float +import gleam/int + +pub fn main() { + gleeunit.main() +} + +pub fn float_ceiling_test() { + // Round 3. digit AFTER decimal point + piecewise.ceiling(12.0654, option.Some(3)) + |> should.equal(Ok(12.066)) + + // Round 2. digit AFTER decimal point + piecewise.ceiling(12.0654, option.Some(2)) + |> should.equal(Ok(12.07)) + + // Round 1. digit AFTER decimal point + piecewise.ceiling(12.0654, option.Some(1)) + |> should.equal(Ok(12.1)) + + // Round 0. digit BEFORE decimal point + piecewise.ceiling(12.0654, option.Some(0)) + |> should.equal(Ok(13.0)) + + // Round 1. digit BEFORE decimal point + piecewise.ceiling(12.0654, option.Some(-1)) + |> should.equal(Ok(20.0)) + + // Round 2. digit BEFORE decimal point + piecewise.ceiling(12.0654, option.Some(-2)) + |> should.equal(Ok(100.0)) + + // Round 3. digit BEFORE decimal point + piecewise.ceiling(12.0654, option.Some(-3)) + |> should.equal(Ok(1000.0)) +} + +pub fn float_floor_test() { + // Round 3. digit AFTER decimal point + piecewise.floor(12.0654, option.Some(3)) + |> should.equal(Ok(12.065)) + + // Round 2. digit AFTER decimal point + piecewise.floor(12.0654, option.Some(2)) + |> should.equal(Ok(12.06)) + + // Round 1. digit AFTER decimal point + piecewise.floor(12.0654, option.Some(1)) + |> should.equal(Ok(12.0)) + + // Round 0. digit BEFORE decimal point + piecewise.floor(12.0654, option.Some(0)) + |> should.equal(Ok(12.0)) + + // Round 1. digit BEFORE decimal point + piecewise.floor(12.0654, option.Some(-1)) + |> should.equal(Ok(10.0)) + + // Round 2. digit BEFORE decimal point + piecewise.floor(12.0654, option.Some(-2)) + |> should.equal(Ok(0.0)) + + // Round 2. digit BEFORE decimal point + piecewise.floor(12.0654, option.Some(-3)) + |> should.equal(Ok(0.0)) +} + +pub fn float_truncate_test() { + // Round 3. digit AFTER decimal point + piecewise.truncate(12.0654, option.Some(3)) + |> should.equal(Ok(12.065)) + + // Round 2. digit AFTER decimal point + piecewise.truncate(12.0654, option.Some(2)) + |> should.equal(Ok(12.06)) + + // Round 1. digit AFTER decimal point + piecewise.truncate(12.0654, option.Some(1)) + |> should.equal(Ok(12.0)) + + // Round 0. digit BEFORE decimal point + piecewise.truncate(12.0654, option.Some(0)) + |> should.equal(Ok(12.0)) + + // Round 1. digit BEFORE decimal point + piecewise.truncate(12.0654, option.Some(-1)) + |> should.equal(Ok(10.0)) + + // Round 2. digit BEFORE decimal point + piecewise.truncate(12.0654, option.Some(-2)) + |> should.equal(Ok(0.0)) + + // Round 2. digit BEFORE decimal point + piecewise.truncate(12.0654, option.Some(-3)) + |> should.equal(Ok(0.0)) +} + +pub fn math_round_to_nearest_test() { + // Try with positive values + piecewise.round(1.5, option.Some(0), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(2.0)) + + piecewise.round(1.75, option.Some(0), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(2.0)) + + piecewise.round(2.0, option.Some(0), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(2.0)) + + piecewise.round(3.5, option.Some(0), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(4.0)) + + piecewise.round(4.5, option.Some(0), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(4.0)) + + // Try with negative values + piecewise.round(-3.5, option.Some(0), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(-4.0)) + + piecewise.round(-4.5, option.Some(0), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(-4.0)) + + // Round 3. digit AFTER decimal point + piecewise.round(12.0654, option.Some(3), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(12.065)) + + // Round 2. digit AFTER decimal point + piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(12.07)) + + // Round 1. digit AFTER decimal point + piecewise.round(12.0654, option.Some(1), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(12.1)) + + // Round 0. digit BEFORE decimal point + piecewise.round(12.0654, option.Some(0), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(12.0)) + + // Round 1. digit BEFORE decimal point + piecewise.round(12.0654, option.Some(-1), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(10.0)) + + // Round 2. digit BEFORE decimal point + piecewise.round(12.0654, option.Some(-2), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(0.0)) + + // Round 3. digit BEFORE decimal point + piecewise.round(12.0654, option.Some(-3), option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(0.0)) +} + +pub fn math_round_up_test() { + // Note: Rounding mode "RoundUp" is an alias for the ceiling function + // Try with positive values + piecewise.round(0.45, option.Some(0), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(1.0)) + + piecewise.round(0.5, option.Some(0), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(1.0)) + + piecewise.round(0.45, option.Some(1), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(0.5)) + + piecewise.round(0.5, option.Some(1), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(0.5)) + + piecewise.round(0.455, option.Some(2), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(0.46)) + + piecewise.round(0.505, option.Some(2), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(0.51)) + + // Try with negative values + piecewise.round(-0.45, option.Some(0), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(-0.0)) + + piecewise.round(-0.5, option.Some(0), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(-0.0)) + + piecewise.round(-0.45, option.Some(1), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(-0.4)) + + piecewise.round(-0.5, option.Some(1), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(-0.5)) + + piecewise.round(-0.455, option.Some(2), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(-0.45)) + + piecewise.round(-0.505, option.Some(2), option.Some(piecewise.RoundUp)) + |> should.equal(Ok(-0.5)) +} + +pub fn math_round_down_test() { + // Note: Rounding mode "RoundDown" is an alias for the floor function + // Try with positive values + piecewise.round(0.45, option.Some(0), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(0.0)) + + piecewise.round(0.5, option.Some(0), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(0.0)) + + piecewise.round(0.45, option.Some(1), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(0.4)) + + piecewise.round(0.5, option.Some(1), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(0.5)) + + piecewise.round(0.455, option.Some(2), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(0.45)) + + piecewise.round(0.505, option.Some(2), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(0.5)) + + // Try with negative values + piecewise.round(-0.45, option.Some(0), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(-1.0)) + + piecewise.round(-0.5, option.Some(0), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(-1.0)) + + piecewise.round(-0.45, option.Some(1), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(-0.5)) + + piecewise.round(-0.5, option.Some(1), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(-0.5)) + + piecewise.round(-0.455, option.Some(2), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(-0.46)) + + piecewise.round(-0.505, option.Some(2), option.Some(piecewise.RoundDown)) + |> should.equal(Ok(-0.51)) +} + +pub fn math_round_to_zero_test() { + // Note: Rounding mode "RoundToZero" is an alias for the truncate function + // Try with positive values + piecewise.round(0.5, option.Some(0), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(0.0)) + + piecewise.round(0.75, option.Some(0), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(0.0)) + + piecewise.round(0.45, option.Some(1), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(0.4)) + + piecewise.round(0.57, option.Some(1), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(0.5)) + + piecewise.round(0.4575, option.Some(2), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(0.45)) + + piecewise.round(0.5075, option.Some(2), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(0.5)) + + // Try with negative values + piecewise.round(-0.5, option.Some(0), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(0.0)) + + piecewise.round(-0.75, option.Some(0), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(0.0)) + + piecewise.round(-0.45, option.Some(1), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(-0.4)) + + piecewise.round(-0.57, option.Some(1), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(-0.5)) + + piecewise.round(-0.4575, option.Some(2), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(-0.45)) + + piecewise.round(-0.5075, option.Some(2), option.Some(piecewise.RoundToZero)) + |> should.equal(Ok(-0.5)) +} + +pub fn math_round_ties_away_test() { + // Try with positive values + piecewise.round(1.4, option.Some(0), option.Some(piecewise.RoundTiesAway)) + |> should.equal(Ok(1.0)) + + piecewise.round(1.5, option.Some(0), option.Some(piecewise.RoundTiesAway)) + |> should.equal(Ok(2.0)) + + piecewise.round(2.5, option.Some(0), option.Some(piecewise.RoundTiesAway)) + |> should.equal(Ok(3.0)) + + // Try with negative values + piecewise.round(-1.4, option.Some(0), option.Some(piecewise.RoundTiesAway)) + |> should.equal(Ok(-1.0)) + + piecewise.round(-1.5, option.Some(0), option.Some(piecewise.RoundTiesAway)) + |> should.equal(Ok(-2.0)) + + piecewise.round(-2.0, option.Some(0), option.Some(piecewise.RoundTiesAway)) + |> should.equal(Ok(-2.0)) + + piecewise.round(-2.5, option.Some(0), option.Some(piecewise.RoundTiesAway)) + |> should.equal(Ok(-3.0)) + + // Round 3. digit AFTER decimal point + piecewise.round(12.0654, option.Some(3), option.Some(piecewise.RoundTiesAway)) + |> should.equal(Ok(12.065)) + + // Round 2. digit AFTER decimal point + piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundTiesAway)) + |> should.equal(Ok(12.07)) + + // Round 1. digit AFTER decimal point + piecewise.round(12.0654, option.Some(1), option.Some(piecewise.RoundTiesAway)) + |> should.equal(Ok(12.1)) + + // Round 0. digit BEFORE decimal point + piecewise.round(12.0654, option.Some(0), option.Some(piecewise.RoundTiesAway)) + |> should.equal(Ok(12.0)) + + // Round 1. digit BEFORE decimal point + piecewise.round( + 12.0654, + option.Some(-1), + option.Some(piecewise.RoundTiesAway), + ) + |> should.equal(Ok(10.0)) + + // Round 2. digit BEFORE decimal point + piecewise.round( + 12.0654, + option.Some(-2), + option.Some(piecewise.RoundTiesAway), + ) + |> should.equal(Ok(0.0)) + + // Round 2. digit BEFORE decimal point + piecewise.round( + 12.0654, + option.Some(-3), + option.Some(piecewise.RoundTiesAway), + ) + |> should.equal(Ok(0.0)) +} + +pub fn math_round_ties_up_test() { + // Try with positive values + piecewise.round(1.4, option.Some(0), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(1.0)) + + piecewise.round(1.5, option.Some(0), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(2.0)) + + piecewise.round(2.5, option.Some(0), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(3.0)) + + // Try with negative values + piecewise.round(-1.4, option.Some(0), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(-1.0)) + + piecewise.round(-1.5, option.Some(0), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(-1.0)) + + piecewise.round(-2.0, option.Some(0), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(-2.0)) + + piecewise.round(-2.5, option.Some(0), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(-2.0)) + + // Round 3. digit AFTER decimal point + piecewise.round(12.0654, option.Some(3), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(12.065)) + + // Round 2. digit AFTER decimal point + piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(12.07)) + + // Round 1. digit AFTER decimal point + piecewise.round(12.0654, option.Some(1), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(12.1)) + + // Round 0. digit BEFORE decimal point + piecewise.round(12.0654, option.Some(0), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(12.0)) + + // Round 1. digit BEFORE decimal point + piecewise.round(12.0654, option.Some(-1), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(10.0)) + + // Round 2. digit BEFORE decimal point + piecewise.round(12.0654, option.Some(-2), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(0.0)) + + // Round 2. digit BEFORE decimal point + piecewise.round(12.0654, option.Some(-3), option.Some(piecewise.RoundTiesUp)) + |> should.equal(Ok(0.0)) +} + +pub fn math_round_edge_cases_test() { + // The default number of digits is 0 if None is provided + piecewise.round(12.0654, option.None, option.Some(piecewise.RoundNearest)) + |> should.equal(Ok(12.0)) + + // The default rounding mode is piecewise.RoundNearest if None is provided + piecewise.round(12.0654, option.None, option.None) + |> should.equal(Ok(12.0)) +} + +pub fn float_absolute_value_test() { + piecewise.float_absolute_value(20.0) + |> should.equal(20.0) + + piecewise.float_absolute_value(-20.0) + |> should.equal(20.0) +} + +pub fn int_absolute_value_test() { + piecewise.int_absolute_value(20) + |> should.equal(20) + + piecewise.int_absolute_value(-20) + |> should.equal(20) +} + +pub fn float_absolute_difference_test() { + piecewise.float_absolute_difference(20.0, 15.0) + |> should.equal(5.0) + + piecewise.float_absolute_difference(-20.0, -15.0) + |> should.equal(5.0) + + piecewise.float_absolute_difference(20.0, -15.0) + |> should.equal(35.0) + + piecewise.float_absolute_difference(-20.0, 15.0) + |> should.equal(35.0) + + piecewise.float_absolute_difference(0.0, 0.0) + |> should.equal(0.0) + + piecewise.float_absolute_difference(1.0, 2.0) + |> should.equal(1.0) + + piecewise.float_absolute_difference(2.0, 1.0) + |> should.equal(1.0) + + piecewise.float_absolute_difference(-1.0, 0.0) + |> should.equal(1.0) + + piecewise.float_absolute_difference(0.0, -1.0) + |> should.equal(1.0) + + piecewise.float_absolute_difference(10.0, 20.0) + |> should.equal(10.0) + + piecewise.float_absolute_difference(-10.0, -20.0) + |> should.equal(10.0) + + piecewise.float_absolute_difference(-10.5, 10.5) + |> should.equal(21.0) +} + +pub fn int_absolute_difference_test() { + piecewise.int_absolute_difference(20, 15) + |> should.equal(5) + + piecewise.int_absolute_difference(-20, -15) + |> should.equal(5) + + piecewise.int_absolute_difference(20, -15) + |> should.equal(35) + + piecewise.int_absolute_difference(-20, 15) + |> should.equal(35) +} + +pub fn float_sign_test() { + piecewise.float_sign(100.0) + |> should.equal(1.0) + + piecewise.float_sign(0.0) + |> should.equal(0.0) + + piecewise.float_sign(-100.0) + |> should.equal(-1.0) +} + +pub fn float_flip_sign_test() { + piecewise.float_flip_sign(100.0) + |> should.equal(-100.0) + + piecewise.float_flip_sign(0.0) + |> should.equal(-0.0) + + piecewise.float_flip_sign(-100.0) + |> should.equal(100.0) +} + +pub fn float_copy_sign_test() { + piecewise.float_copy_sign(100.0, 10.0) + |> should.equal(100.0) + + piecewise.float_copy_sign(-100.0, 10.0) + |> should.equal(100.0) + + piecewise.float_copy_sign(100.0, -10.0) + |> should.equal(-100.0) + + piecewise.float_copy_sign(-100.0, -10.0) + |> should.equal(-100.0) +} + +pub fn int_sign_test() { + piecewise.int_sign(100) + |> should.equal(1) + + piecewise.int_sign(0) + |> should.equal(0) + + piecewise.int_sign(-100) + |> should.equal(-1) +} + +pub fn int_flip_sign_test() { + piecewise.int_flip_sign(100) + |> should.equal(-100) + + piecewise.int_flip_sign(0) + |> should.equal(-0) + + piecewise.int_flip_sign(-100) + |> should.equal(100) +} + +pub fn int_copy_sign_test() { + piecewise.int_copy_sign(100, 10) + |> should.equal(100) + + piecewise.int_copy_sign(-100, 10) + |> should.equal(100) + + piecewise.int_copy_sign(100, -10) + |> should.equal(-100) + + piecewise.int_copy_sign(-100, -10) + |> should.equal(-100) +} + +pub fn float_minimum_test() { + piecewise.minimum(0.75, 0.5, float.compare) + |> should.equal(0.5) + + piecewise.minimum(0.5, 0.75, float.compare) + |> should.equal(0.5) + + piecewise.minimum(-0.75, 0.5, float.compare) + |> should.equal(-0.75) + + piecewise.minimum(-0.75, 0.5, float.compare) + |> should.equal(-0.75) +} + +pub fn int_minimum_test() { + piecewise.minimum(75, 50, int.compare) + |> should.equal(50) + + piecewise.minimum(50, 75, int.compare) + |> should.equal(50) + + piecewise.minimum(-75, 50, int.compare) + |> should.equal(-75) + + piecewise.minimum(-75, 50, int.compare) + |> should.equal(-75) +} + +pub fn float_maximum_test() { + piecewise.maximum(0.75, 0.5, float.compare) + |> should.equal(0.75) + + piecewise.maximum(0.5, 0.75, float.compare) + |> should.equal(0.75) + + piecewise.maximum(-0.75, 0.5, float.compare) + |> should.equal(0.5) + + piecewise.maximum(-0.75, 0.5, float.compare) + |> should.equal(0.5) +} + +pub fn int_maximum_test() { + piecewise.maximum(75, 50, int.compare) + |> should.equal(75) + + piecewise.maximum(50, 75, int.compare) + |> should.equal(75) + + piecewise.maximum(-75, 50, int.compare) + |> should.equal(50) + + piecewise.maximum(-75, 50, int.compare) + |> should.equal(50) +} + +pub fn float_minmax_test() { + piecewise.minmax(0.75, 0.5, float.compare) + |> should.equal(#(0.5, 0.75)) + + piecewise.minmax(0.5, 0.75, float.compare) + |> should.equal(#(0.5, 0.75)) + + piecewise.minmax(-0.75, 0.5, float.compare) + |> should.equal(#(-0.75, 0.5)) + + piecewise.minmax(-0.75, 0.5, float.compare) + |> should.equal(#(-0.75, 0.5)) +} + +pub fn int_minmax_test() { + piecewise.minmax(75, 50, int.compare) + |> should.equal(#(50, 75)) + + piecewise.minmax(50, 75, int.compare) + |> should.equal(#(50, 75)) + + piecewise.minmax(-75, 50, int.compare) + |> should.equal(#(-75, 50)) + + piecewise.minmax(-75, 50, int.compare) + |> should.equal(#(-75, 50)) +} + +pub fn float_list_minimum_test() { + // An empty lists returns an error + [] + |> piecewise.list_minimum(float.compare) + |> should.be_error() + + // Valid input returns a result + [4.5, 4.5, 3.5, 2.5, 1.5] + |> piecewise.list_minimum(float.compare) + |> should.equal(Ok(1.5)) +} + +pub fn int_list_minimum_test() { + // An empty lists returns an error + [] + |> piecewise.list_minimum(int.compare) + |> should.be_error() + + // Valid input returns a result + [4, 4, 3, 2, 1] + |> piecewise.list_minimum(int.compare) + |> should.equal(Ok(1)) +} + +pub fn float_list_maximum_test() { + // An empty lists returns an error + [] + |> piecewise.list_maximum(float.compare) + |> should.be_error() + + // Valid input returns a result + [4.5, 4.5, 3.5, 2.5, 1.5] + |> piecewise.list_maximum(float.compare) + |> should.equal(Ok(4.5)) +} + +pub fn int_list_maximum_test() { + // An empty lists returns an error + [] + |> piecewise.list_maximum(int.compare) + |> should.be_error() + + // Valid input returns a result + [4, 4, 3, 2, 1] + |> piecewise.list_maximum(int.compare) + |> should.equal(Ok(4)) +} + +pub fn float_list_arg_maximum_test() { + // An empty lists returns an error + [] + |> piecewise.arg_maximum(float.compare) + |> should.be_error() + + // Valid input returns a result + [4.5, 4.5, 3.5, 2.5, 1.5] + |> piecewise.arg_maximum(float.compare) + |> should.equal(Ok([0, 1])) +} + +pub fn int_list_arg_maximum_test() { + // An empty lists returns an error + [] + |> piecewise.arg_maximum(int.compare) + |> should.be_error() + + // Valid input returns a result + [4, 4, 3, 2, 1] + |> piecewise.arg_maximum(int.compare) + |> should.equal(Ok([0, 1])) +} + +pub fn float_list_arg_minimum_test() { + // An empty lists returns an error + [] + |> piecewise.arg_minimum(float.compare) + |> should.be_error() + + // Valid input returns a result + [4.5, 4.5, 3.5, 2.5, 1.5] + |> piecewise.arg_minimum(float.compare) + |> should.equal(Ok([4])) +} + +pub fn int_list_arg_minimum_test() { + // An empty lists returns an error + [] + |> piecewise.arg_minimum(int.compare) + |> should.be_error() + + // Valid input returns a result + [4, 4, 3, 2, 1] + |> piecewise.arg_minimum(int.compare) + |> should.equal(Ok([4])) +} + +pub fn float_list_extrema_test() { + // An empty lists returns an error + [] + |> piecewise.extrema(float.compare) + |> should.be_error() + + // Valid input returns a result + [4.0, 4.0, 3.0, 2.0, 1.0] + |> piecewise.extrema(float.compare) + |> should.equal(Ok(#(1.0, 4.0))) + + // Valid input returns a result + [1.0, 4.0, 2.0, 5.0, 0.0] + |> piecewise.extrema(float.compare) + |> should.equal(Ok(#(0.0, 5.0))) +} + +pub fn int_list_extrema_test() { + // An empty lists returns an error + [] + |> piecewise.extrema(int.compare) + |> should.be_error() + + // Valid input returns a result + [4, 4, 3, 2, 1] + |> piecewise.extrema(int.compare) + |> should.equal(Ok(#(1, 4))) + + // Valid input returns a result + [1, 4, 2, 5, 0] + |> piecewise.extrema(int.compare) + |> should.equal(Ok(#(0, 5))) +} diff --git a/test/gleam/gleam_community_maths_sequences.gleam b/test/gleam/gleam_community_maths_sequences.gleam new file mode 100644 index 0000000..8f5f861 --- /dev/null +++ b/test/gleam/gleam_community_maths_sequences.gleam @@ -0,0 +1,234 @@ +import gleam_community/maths/elementary +import gleam_community/maths/sequences +import gleam_community/maths/tests +import gleam/list +import gleeunit +import gleeunit/should + +pub fn main() { + gleeunit.main() +} + +pub fn float_list_linear_space_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + + // Check that the function agrees, at some arbitrary input + // points, with known function values + // ---> With endpoint included + let assert Ok(linspace) = sequences.linear_space(10.0, 50.0, 5, True) + let assert Ok(result) = + tests.all_close(linspace, [10.0, 20.0, 30.0, 40.0, 50.0], 0.0, tol) + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + let assert Ok(linspace) = sequences.linear_space(10.0, 20.0, 5, True) + let assert Ok(result) = + tests.all_close(linspace, [10.0, 12.5, 15.0, 17.5, 20.0], 0.0, tol) + + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // Try with negative stop + // ----> Without endpoint included + let assert Ok(linspace) = sequences.linear_space(10.0, 50.0, 5, False) + let assert Ok(result) = + tests.all_close(linspace, [10.0, 18.0, 26.0, 34.0, 42.0], 0.0, tol) + + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + let assert Ok(linspace) = sequences.linear_space(10.0, 20.0, 5, False) + let assert Ok(result) = + tests.all_close(linspace, [10.0, 12.0, 14.0, 16.0, 18.0], 0.0, tol) + + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // Try with negative stop + let assert Ok(linspace) = sequences.linear_space(10.0, -50.0, 5, False) + let assert Ok(result) = + tests.all_close(linspace, [10.0, -2.0, -14.0, -26.0, -38.0], 0.0, tol) + + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + let assert Ok(linspace) = sequences.linear_space(10.0, -20.0, 5, True) + let assert Ok(result) = + tests.all_close(linspace, [10.0, 2.5, -5.0, -12.5, -20.0], 0.0, tol) + + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // Try with negative start + let assert Ok(linspace) = sequences.linear_space(-10.0, 50.0, 5, False) + let assert Ok(result) = + tests.all_close(linspace, [-10.0, 2.0, 14.0, 26.0, 38.0], 0.0, tol) + + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + let assert Ok(linspace) = sequences.linear_space(-10.0, 20.0, 5, True) + let assert Ok(result) = + tests.all_close(linspace, [-10.0, -2.5, 5.0, 12.5, 20.0], 0.0, tol) + + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // A negative number of points does not work (-5) + sequences.linear_space(10.0, 50.0, -5, True) + |> should.be_error() +} + +pub fn float_list_logarithmic_space_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + // ---> With endpoint included + // - Positive start, stop, base + let assert Ok(logspace) = sequences.logarithmic_space(1.0, 3.0, 3, True, 10.0) + let assert Ok(result) = + tests.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // - Positive start, stop, negative base + let assert Ok(logspace) = + sequences.logarithmic_space(1.0, 3.0, 3, True, -10.0) + let assert Ok(result) = + tests.all_close(logspace, [-10.0, 100.0, -1000.0], 0.0, tol) + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // - Positive start, negative stop, base + let assert Ok(logspace) = + sequences.logarithmic_space(1.0, -3.0, 3, True, -10.0) + let assert Ok(result) = + tests.all_close(logspace, [-10.0, -0.1, -0.001], 0.0, tol) + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // - Positive start, base, negative stop + let assert Ok(logspace) = + sequences.logarithmic_space(1.0, -3.0, 3, True, 10.0) + let assert Ok(result) = + tests.all_close(logspace, [10.0, 0.1, 0.001], 0.0, tol) + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // - Positive stop, base, negative start + let assert Ok(logspace) = + sequences.logarithmic_space(-1.0, 3.0, 3, True, 10.0) + let assert Ok(result) = + tests.all_close(logspace, [0.1, 10.0, 1000.0], 0.0, tol) + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // ----> Without endpoint included + // - Positive start, stop, base + let assert Ok(logspace) = + sequences.logarithmic_space(1.0, 3.0, 3, False, 10.0) + let assert Ok(result) = + tests.all_close(logspace, [10.0, 46.41588834, 215.443469], 0.0, tol) + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // A negative number of points does not work (-3) + sequences.logarithmic_space(1.0, 3.0, -3, True, 10.0) + |> should.be_error() +} + +pub fn float_list_geometric_space_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Check that the function agrees, at some arbitrary input + // points, with known function values + // ---> With endpoint included + // - Positive start, stop + let assert Ok(logspace) = sequences.geometric_space(10.0, 1000.0, 3, True) + let assert Ok(result) = + tests.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // - Positive start, negative stop + let assert Ok(logspace) = sequences.geometric_space(10.0, 0.001, 3, True) + let assert Ok(result) = + tests.all_close(logspace, [10.0, 0.1, 0.001], 0.0, tol) + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // - Positive stop, negative start + let assert Ok(logspace) = sequences.geometric_space(0.1, 1000.0, 3, True) + let assert Ok(result) = + tests.all_close(logspace, [0.1, 10.0, 1000.0], 0.0, tol) + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // ----> Without endpoint included + // - Positive start, stop + let assert Ok(logspace) = sequences.geometric_space(10.0, 1000.0, 3, False) + let assert Ok(result) = + tests.all_close(logspace, [10.0, 46.41588834, 215.443469], 0.0, tol) + result + |> list.all(fn(x) { x == True }) + |> should.be_true() + + // Test invalid input (start and stop can't be equal to 0.0) + sequences.geometric_space(0.0, 1000.0, 3, False) + |> should.be_error() + + sequences.geometric_space(-1000.0, 0.0, 3, False) + |> should.be_error() + + // A negative number of points does not work + sequences.geometric_space(-1000.0, 0.0, -3, False) + |> should.be_error() +} + +pub fn float_list_arange_test() { + // Positive start, stop, step + sequences.arange(1.0, 5.0, 1.0) + |> should.equal([1.0, 2.0, 3.0, 4.0]) + + sequences.arange(1.0, 5.0, 0.5) + |> should.equal([1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5]) + + sequences.arange(1.0, 2.0, 0.25) + |> should.equal([1.0, 1.25, 1.5, 1.75]) + + // Reverse (switch start/stop largest/smallest value) + sequences.arange(5.0, 1.0, 1.0) + |> should.equal([]) + + // Reverse negative step + sequences.arange(5.0, 1.0, -1.0) + |> should.equal([5.0, 4.0, 3.0, 2.0]) + + // Positive start, negative stop, step + sequences.arange(5.0, -1.0, -1.0) + |> should.equal([5.0, 4.0, 3.0, 2.0, 1.0, 0.0]) + + // Negative start, stop, step + sequences.arange(-5.0, -1.0, -1.0) + |> should.equal([]) + + // Negative start, stop, positive step + sequences.arange(-5.0, -1.0, 1.0) + |> should.equal([-5.0, -4.0, -3.0, -2.0]) +} diff --git a/test/gleam/gleam_community_maths_special.gleam b/test/gleam/gleam_community_maths_special.gleam new file mode 100644 index 0000000..6aa3587 --- /dev/null +++ b/test/gleam/gleam_community_maths_special.gleam @@ -0,0 +1,57 @@ +import gleam_community/maths/special +import gleam_community/maths/tests +import gleeunit +import gleeunit/should +import gleam/result +import gleam/io + +pub fn main() { + gleeunit.main() +} + +pub fn float_beta_function_test() { + io.debug("") + io.debug("TODO: Implement tests for 'float.beta'.") +} + +pub fn float_error_function_test() { + io.debug("") + io.debug("TODO: Implement tests for 'float.erf'.") +} + +pub fn float_gamma_function_test() { + io.debug("") + io.debug("TODO: Implement tests for 'float.gamma'.") +} + +pub fn float_incomplete_gamma_function_test() { + // Invalid input gives an error + // 1st arg is invalid + special.incomplete_gamma(-1.0, 1.0) + |> should.be_error() + + // 2nd arg is invalid + special.incomplete_gamma(1.0, -1.0) + |> should.be_error() + + // Valid input returns a result + special.incomplete_gamma(1.0, 0.0) + |> result.unwrap(-999.0) + |> tests.is_close(0.0, 0.0, 0.01) + |> should.be_true() + + special.incomplete_gamma(1.0, 2.0) + |> result.unwrap(-999.0) + |> tests.is_close(0.864664716763387308106, 0.0, 0.01) + |> should.be_true() + + special.incomplete_gamma(2.0, 3.0) + |> result.unwrap(-999.0) + |> tests.is_close(0.8008517265285442280826, 0.0, 0.01) + |> should.be_true() + + special.incomplete_gamma(3.0, 4.0) + |> result.unwrap(-999.0) + |> tests.is_close(1.523793388892911312363, 0.0, 0.01) + |> should.be_true() +} diff --git a/test/gleam/gleam_community_maths_tests.gleam b/test/gleam/gleam_community_maths_tests.gleam new file mode 100644 index 0000000..870e91b --- /dev/null +++ b/test/gleam/gleam_community_maths_tests.gleam @@ -0,0 +1,143 @@ +import gleam_community/maths/tests +import gleam/list +import gleeunit/should +import gleeunit + +pub fn main() { + gleeunit.main() +} + +pub fn float_is_close_test() { + let val: Float = 99.0 + let ref_val: Float = 100.0 + // We set 'atol' and 'rtol' such that the values are equivalent + // if 'val' is within 1 percent of 'ref_val' +/- 0.1 + let rtol: Float = 0.01 + let atol: Float = 0.1 + tests.is_close(val, ref_val, rtol, atol) + |> should.be_true() +} + +pub fn float_list_all_close_test() { + let val: Float = 99.0 + let ref_val: Float = 100.0 + let xarr: List(Float) = list.repeat(val, 42) + let yarr: List(Float) = list.repeat(ref_val, 42) + // We set 'atol' and 'rtol' such that the values are equivalent + // if 'val' is within 1 percent of 'ref_val' +/- 0.1 + let rtol: Float = 0.01 + let atol: Float = 0.1 + tests.all_close(xarr, yarr, rtol, atol) + |> fn(zarr: Result(List(Bool), String)) -> Result(Bool, Nil) { + case zarr { + Ok(arr) -> + arr + |> list.all(fn(a: Bool) -> Bool { a }) + |> Ok + _ -> + Nil + |> Error + } + } + |> should.equal(Ok(True)) +} + +pub fn float_is_fractional_test() { + tests.is_fractional(1.5) + |> should.equal(True) + + tests.is_fractional(0.5) + |> should.equal(True) + + tests.is_fractional(0.3333) + |> should.equal(True) + + tests.is_fractional(0.9999) + |> should.equal(True) + + tests.is_fractional(1.0) + |> should.equal(False) + + tests.is_fractional(999.0) + |> should.equal(False) +} + +pub fn int_is_power_test() { + tests.is_power(10, 10) + |> should.equal(True) + + tests.is_power(11, 10) + |> should.equal(False) + + tests.is_power(4, 2) + |> should.equal(True) + + tests.is_power(5, 2) + |> should.equal(False) + + tests.is_power(27, 3) + |> should.equal(True) + + tests.is_power(28, 3) + |> should.equal(False) +} + +pub fn int_is_even_test() { + tests.is_even(0) + |> should.equal(True) + + tests.is_even(2) + |> should.equal(True) + + tests.is_even(12) + |> should.equal(True) + + tests.is_even(5) + |> should.equal(False) + + tests.is_even(-3) + |> should.equal(False) + + tests.is_even(-4) + |> should.equal(True) +} + +pub fn int_is_odd_test() { + tests.is_odd(0) + |> should.equal(False) + + tests.is_odd(3) + |> should.equal(True) + + tests.is_odd(13) + |> should.equal(True) + + tests.is_odd(4) + |> should.equal(False) + + tests.is_odd(-3) + |> should.equal(True) + + tests.is_odd(-4) + |> should.equal(False) +} + +pub fn int_is_perfect_test() { + tests.is_perfect(6) + |> should.equal(True) + + tests.is_perfect(28) + |> should.equal(True) + + tests.is_perfect(496) + |> should.equal(True) + + tests.is_perfect(1) + |> should.equal(False) + + tests.is_perfect(3) + |> should.equal(False) + + tests.is_perfect(13) + |> should.equal(False) +} diff --git a/test/gleam_community_maths_test.gleam b/test/gleam_community_maths_test.gleam index e9bdd39..ba7a929 100644 --- a/test/gleam_community_maths_test.gleam +++ b/test/gleam_community_maths_test.gleam @@ -1,9 +1,3 @@ -if erlang { - pub external fn main() -> Nil = - "gleam_community_maths_test_ffi" "main" -} - -if javascript { - pub external fn main() -> Nil = - "./gleam_community_maths_test_ffi.mjs" "main" -} +@external(erlang, "gleam_community_maths_test_ffi", "main") +@external(javascript, "./gleam_community_maths_test_ffi.mjs", "main") +pub fn main() -> Nil diff --git a/test/gleam_community_maths_test_ffi.mjs b/test/gleam_community_maths_test_ffi.mjs index d12c906..48295b4 100755 --- a/test/gleam_community_maths_test_ffi.mjs +++ b/test/gleam_community_maths_test_ffi.mjs @@ -1,35 +1,103 @@ -import { opendir } from "fs/promises"; +// This file is a verbatim copy of gleeunit 0.10.0's -const dir = "build/dev/javascript/gleam_community_maths/gleam/"; +async function* gleamFiles(directory) { + for (let entry of await read_dir(directory)) { + let path = join_path(directory, entry); + if (path.endsWith(".gleam")) { + yield path; + } else { + try { + yield* gleamFiles(path); + } catch (error) { + // Could not read directory, assume it's a file + } + } + } +} -export async function main() { - console.log("Running tests..."); +async function readRootPackageName() { + let toml = await read_file("gleam.toml", "utf-8"); + for (let line of toml.split("\n")) { + let matches = line.match(/\s*name\s*=\s*"([a-z][a-z0-9_]*)"/); // Match regexp in compiler-cli/src/new.rs in validate_name() + if (matches) return matches[1]; + } + throw new Error("Could not determine package name from gleam.toml"); +} +export async function main() { let passes = 0; let failures = 0; - for await (let entry of await opendir(dir)) { - if (!entry.name.endsWith("_test.mjs")) continue; - let module = await import("./gleam/" + entry.name); + let packageName = await readRootPackageName(); + let dist = `../${packageName}/`; + for await (let path of await gleamFiles("test")) { + let js_path = path.slice("test/".length).replace(".gleam", ".mjs"); + let module = await import(join_path(dist, js_path)); for (let fnName of Object.keys(module)) { if (!fnName.endsWith("_test")) continue; try { - module[fnName](); - process.stdout.write(`\u001b[32m.\u001b[0m`); + await module[fnName](); + write(`\u001b[32m.\u001b[0m`); passes++; } catch (error) { - let moduleName = "\ngleam/" + entry.name.slice(0, -3); - process.stdout.write(`\n❌ ${moduleName}.${fnName}: ${error}\n`); + let moduleName = "\n" + js_path.slice(0, -4); + let line = error.line ? `:${error.line}` : ""; + write(`\n❌ ${moduleName}.${fnName}${line}: ${error}\n`); failures++; } } } console.log(` +${passes + failures} tests, ${failures} failures`); + exit(failures ? 1 : 0); +} -${passes + failures} tests -${passes} passes -${failures} failures`); - process.exit(failures ? 1 : 0); +export function crash(message) { + throw new Error(message); } + +function write(message) { + if (globalThis.Deno) { + Deno.stdout.writeSync(new TextEncoder().encode(message)); + } else { + process.stdout.write(message); + } +} + +function exit(code) { + if (globalThis.Deno) { + Deno.exit(code); + } else { + process.exit(code); + } +} + +async function read_dir(path) { + if (globalThis.Deno) { + let items = []; + for await (let item of Deno.readDir(path, { withFileTypes: true })) { + items.push(item.name); + } + return items; + } else { + let { readdir } = await import("fs/promises"); + return readdir(path); + } +} + +function join_path(a, b) { + if (a.endsWith("/")) return a + b; + return a + "/" + b; +} + +async function read_file(path) { + if (globalThis.Deno) { + return Deno.readTextFile(path); + } else { + let { readFile } = await import("fs/promises"); + let contents = await readFile(path); + return contents.toString(); + } +} \ No newline at end of file From 7ca932dc2fcf107bfd4657edbc7081376a90ad41 Mon Sep 17 00:00:00 2001 From: NicklasXYZ Date: Sun, 17 Sep 2023 14:06:59 +0200 Subject: [PATCH 2/6] Update examples --- src/gleam_community/maths/arithmetics.gleam | 96 ++++----- src/gleam_community/maths/combinatorics.gleam | 69 ++++--- src/gleam_community/maths/conversion.gleam | 29 +-- src/gleam_community/maths/elementary.gleam | 100 +++++----- src/gleam_community/maths/metrics.gleam | 156 +++++---------- src/gleam_community/maths/piecewise.gleam | 185 ++++++++++++------ src/gleam_community/maths/sequences.gleam | 51 ++--- src/gleam_community/maths/tests.gleam | 39 ++-- .../gleam/gleam_community_maths_metrics.gleam | 29 ++- .../gleam/gleam_community_maths_special.gleam | 84 ++++++-- 10 files changed, 466 insertions(+), 372 deletions(-) diff --git a/src/gleam_community/maths/arithmetics.gleam b/src/gleam_community/maths/arithmetics.gleam index aee0d34..1b60d19 100644 --- a/src/gleam_community/maths/arithmetics.gleam +++ b/src/gleam_community/maths/arithmetics.gleam @@ -60,16 +60,16 @@ import gleam_community/maths/piecewise /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/arithmetics /// /// pub fn example() { -/// intx.lcm(1, 1) +/// arithmetics.lcm(1, 1) /// |> should.equal(1) /// -/// intx.lcm(100, 10) +/// arithmetics.lcm(100, 10) /// |> should.equal(10) /// -/// intx.gcd(-36, -17) +/// arithmetics.gcd(-36, -17) /// |> should.equal(1) /// } /// @@ -109,16 +109,16 @@ pub fn do_gcd(x: Int, y: Int) -> Int { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/arithmetics /// /// pub fn example() { -/// intx.lcm(1, 1) +/// arithmetics.lcm(1, 1) /// |> should.equal(1) /// -/// intx.lcm(100, 10) +/// arithmetics.lcm(100, 10) /// |> should.equal(100) /// -/// intx.lcm(-36, -17) +/// arithmetics.lcm(-36, -17) /// |> should.equal(612) /// } /// @@ -147,16 +147,16 @@ pub fn lcm(x: Int, y: Int) -> Int { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/arithmetics /// /// pub fn example() { -/// intx.divisors(4) +/// arithmetics.divisors(4) /// |> should.equal([1, 2, 4]) /// -/// intx.divisors(6) +/// arithmetics.divisors(6) /// |> should.equal([1, 2, 3, 6]) /// -/// intx.proper_divisors(13) +/// arithmetics.proper_divisors(13) /// |> should.equal([1, 13]) /// } /// @@ -201,16 +201,16 @@ pub fn find_divisors(n: Int) -> List(Int) { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/arithmetics /// /// pub fn example() { -/// intx.proper_divisors(4) +/// arithmetics.proper_divisors(4) /// |> should.equal([1, 2]) /// -/// intx.proper_divisors(6) +/// arithmetics.proper_divisors(6) /// |> should.equal([1, 2, 3]) /// -/// intx.proper_divisors(13) +/// arithmetics.proper_divisors(13) /// |> should.equal([1]) /// } /// @@ -239,23 +239,23 @@ pub fn proper_divisors(n: Int) -> List(Int) { /// \sum_{i=1}^n x_i /// \\] /// -/// In the formula, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. +/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float_list +/// import gleam_community/maths/arithmetics /// /// pub fn example () { /// // An empty list returns an error /// [] -/// |> float_list.sum() +/// |> arithmetics.float_sum() /// |> should.equal(0.0) /// /// // Valid input returns a result /// [1.0, 2.0, 3.0] -/// |> float_list.sum() +/// |> arithmetics.float_sum() /// |> should.equal(6.0) /// } ///
@@ -287,23 +287,23 @@ pub fn float_sum(arr: List(Float)) -> Float { /// \sum_{i=1}^n x_i /// \\] /// -/// In the formula, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. +/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int_list +/// import gleam_community/maths/arithmetics /// /// pub fn example () { /// // An empty list returns 0 /// [] -/// |> int_list.sum() +/// |> arithmetics.int_sum() /// |> should.equal(0) /// /// // Valid input returns a result /// [1, 2, 3] -/// |> int_list.sum() +/// |> arithmetics.int_sum() /// |> should.equal(6) /// } ///
@@ -335,23 +335,23 @@ pub fn int_sum(arr: List(Int)) -> Int { /// \prod_{i=1}^n x_i /// \\] /// -/// In the formula, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. +/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float_list +/// import gleam_community/maths/arithmetics /// /// pub fn example () { /// // An empty list returns 0.0 /// [] -/// |> float_list.sum() +/// |> arithmetics.float_product() /// |> should.equal(0.0) /// /// // Valid input returns a result /// [1.0, 2.0, 3.0] -/// |> float_list.product() +/// |> arithmetics.float_product() /// |> should.equal(6.0) /// } ///
@@ -383,23 +383,23 @@ pub fn float_product(arr: List(Float)) -> Float { /// \prod_{i=1}^n x_i /// \\] /// -/// In the formula, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. +/// In the formula, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int_list +/// import gleam_community/maths/arithmetics /// /// pub fn example () { /// // An empty list returns 0 /// [] -/// |> int_list.product() +/// |> arithmetics.int_product() /// |> should.equal(0) /// /// // Valid input returns a result /// [1, 2, 3] -/// |> int_list.product() +/// |> arithmetics.int_product() /// |> should.equal(6) /// } ///
@@ -432,23 +432,23 @@ pub fn int_product(arr: List(Int)) -> Int { /// \\] /// /// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$ elements. -/// That is, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. +/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$. /// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float_list +/// import gleam_community/maths/arithmetics /// /// pub fn example () { /// [] -/// |> float_list.cumulative_sum() +/// |> arithmetics.float_cumulative_sum() /// |> should.equal([]) /// /// // Valid input returns a result /// [1.0, 2.0, 3.0] -/// |> float_list.cumulative_sum() +/// |> arithmetics.float_cumulative_sum() /// |> should.equal([1.0, 3.0, 6.0]) /// } ///
@@ -481,23 +481,23 @@ pub fn float_cumulative_sum(arr: List(Float)) -> List(Float) { /// \\] /// /// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative sum of $$n$$ elements. -/// That is, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. +/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$. /// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int_list +/// import gleam_community/maths/arithmetics /// /// pub fn example () { /// [] -/// |> int_list.cumulative_sum() +/// |> arithmetics.int_cumulative_sum() /// |> should.equal([]) /// /// // Valid input returns a result /// [1, 2, 3] -/// |> int_list.cumulative_sum() +/// |> arithmetics.int_cumulative_sum() /// |> should.equal([1, 3, 6]) /// } ///
@@ -530,24 +530,24 @@ pub fn int_cumulative_sum(arr: List(Int)) -> List(Int) { /// \\] /// /// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of $$n$$ elements. -/// That is, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. +/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{R}$$ is the value in the input list indexed by $$i$$. /// The value $$v_j$$ is thus the sum of the $$1$$ to $$j$$ first elements in the given list. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float_list +/// import gleam_community/maths/arithmetics /// /// pub fn example () { /// // An empty list returns an error /// [] -/// |> float_list.cumulative_product() +/// |> arithmetics.float_cumulative_product() /// |> should.equal([]) /// /// // Valid input returns a result /// [1.0, 2.0, 3.0] -/// |> float_list.cumulative_product() +/// |> arithmetics.float_cumulative_product() /// |> should.equal([1.0, 2.0, 6.0]) /// } ///
@@ -580,24 +580,24 @@ pub fn float_cumumlative_product(arr: List(Float)) -> List(Float) { /// \\] /// /// In the formula, $$v_j$$ is the $$j$$'th element in the cumulative product of $$n$$ elements. -/// That is, $$n$$ is the length of the list and $$x_i$$ is the value in the input list indexed by $$i$$. +/// That is, $$n$$ is the length of the list and $$x_i \in \mathbb{Z}$$ is the value in the input list indexed by $$i$$. /// The value $$v_j$$ is thus the product of the $$1$$ to $$j$$ first elements in the given list. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int_list +/// import gleam_community/maths/arithmetics /// /// pub fn example () { /// // An empty list returns an error /// [] -/// |> int_list.cumulative_product() +/// |> arithmetics.int_cumulative_product() /// |> should.equal([]) /// /// // Valid input returns a result /// [1, 2, 3] -/// |> int_list.cumulative_product() +/// |> arithmetics.int_cumulative_product() /// |> should.equal([1, 2, 6]) /// } ///
diff --git a/src/gleam_community/maths/combinatorics.gleam b/src/gleam_community/maths/combinatorics.gleam index b657c7b..9963a25 100644 --- a/src/gleam_community/maths/combinatorics.gleam +++ b/src/gleam_community/maths/combinatorics.gleam @@ -56,22 +56,22 @@ import gleam/set /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/combinatorics /// /// pub fn example() { /// // Invalid input gives an error /// // Error on: n = -1 < 0 -/// intx.combination(-1, 1) +/// combinatorics.combination(-1, 1) /// |> should.be_error() /// /// // Valid input returns a result -/// intx.combination(4, 0) +/// combinatorics.combination(4, 0) /// |> should.equal(Ok(1)) /// -/// intx.combination(4, 4) +/// combinatorics.combination(4, 4) /// |> should.equal(Ok(1)) /// -/// intx.combination(4, 2) +/// combinatorics.combination(4, 2) /// |> should.equal(Ok(6)) /// } /// @@ -127,23 +127,27 @@ pub fn combination(n: Int, k: Int) -> Result(Int, String) { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/combinatorics /// /// pub fn example() { /// // Invalid input gives an error -/// intx.factorial(-1) +/// combinatorics.factorial(-1) /// |> should.be_error() /// /// // Valid input returns a result -/// intx.factorial(0) +/// combinatorics.factorial(0) /// |> should.equal(Ok(1)) -/// intx.factorial(1) +/// +/// combinatorics.factorial(1) /// |> should.equal(Ok(1)) -/// intx.factorial(2) +/// +/// combinatorics.factorial(2) /// |> should.equal(Ok(2)) -/// intx.factorial(3) +/// +/// combinatorics.factorial(3) /// |> should.equal(Ok(6)) -/// intx.factorial(4) +/// +/// combinatorics.factorial(4) /// |> should.equal(Ok(24)) /// } /// @@ -192,22 +196,22 @@ pub fn factorial(n) -> Result(Int, String) { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/combinatorics /// /// pub fn example() { /// // Invalid input gives an error /// // Error on: n = -1 < 0 -/// intx.permutation(-1, 1) +/// combinatorics.permutation(-1, 1) /// |> should.be_error() /// /// // Valid input returns a result -/// intx.permutation(4, 0) +/// combinatorics.permutation(4, 0) /// |> should.equal(Ok(1)) /// -/// intx.permutation(4, 4) +/// combinatorics.permutation(4, 4) /// |> should.equal(Ok(1)) /// -/// intx.permutation(4, 2) +/// combinatorics.permutation(4, 2) /// |> should.equal(Ok(12)) /// } /// @@ -256,10 +260,14 @@ pub fn permutation(n: Int, k: Int) -> Result(Int, String) { /// Example: /// /// import gleeunit/should -/// import gleam/list -/// import gleam_community/maths/list as listx +/// import gleam/set +/// import gleam_community/maths/combinatorics /// /// pub fn example () { +/// let assert Ok(result) = combinatorics.list_combination([1, 2, 3, 4], 3) +/// result +/// |> set.from_list() +/// |> should.equal(set.from_list([[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]])) /// } /// /// @@ -309,16 +317,27 @@ fn do_list_combination(arr: List(a), k: Int, prefix: List(a)) -> List(List(a)) { /// /// /// -/// Generate all permutations based on a given list. +/// Generate all permutations of a given list. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam/list -/// import gleam_community/maths/list as listx +/// import gleam/set +/// import gleam_community/maths/combinatorics /// /// pub fn example () { +/// [1, 2, 3] +/// |> combinatorics.list_permutation() +/// |> set.from_list() +/// |> should.equal(set.from_list([ +/// [1, 2, 3], +/// [2, 1, 3], +/// [3, 1, 2], +/// [1, 3, 2], +/// [2, 3, 1], +/// [3, 2, 1], +/// ])) /// } ///
/// @@ -368,15 +387,15 @@ fn concat(lists: List(List(a))) -> List(a) { /// /// import gleeunit/should /// import gleam/list -/// import gleam_community/maths/list as listx +/// import gleam_community/maths/combinatorics /// /// pub fn example () { /// [] -/// |> listx.cartesian_product([]) +/// |> combinatorics.cartesian_product([]) /// |> should.equal([]) /// /// [1.0, 10.0] -/// |> listx.cartesian_product([1.0, 2.0]) +/// |> combinatorics.cartesian_product([1.0, 2.0]) /// |> should.equal([#(1.0, 1.0), #(1.0, 2.0), #(10.0, 1.0), #(10.0, 2.0)]) /// } /// diff --git a/src/gleam_community/maths/conversion.gleam b/src/gleam_community/maths/conversion.gleam index d45a5f2..f5cdfa8 100644 --- a/src/gleam_community/maths/conversion.gleam +++ b/src/gleam_community/maths/conversion.gleam @@ -23,7 +23,7 @@ //// //// --- //// -//// Conversion: A module containing various functions for converting between types and different quantities. +//// Conversion: A module containing various functions for converting between types and quantities. //// //// * **Misc. functions** //// * [`float_to_int`](#float_to_int) @@ -47,13 +47,13 @@ import gleam/int /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/conversion /// /// pub fn example() { -/// intx.to_float(-1) +/// conversion.int_to_float(-1) /// |> should.equal(-1.0) /// -/// intx.to_float(1) +/// conversion.int_to_float(1) /// |> should.equal(1.0) /// } /// @@ -82,16 +82,17 @@ pub fn int_to_float(x: Int) -> Float { /// /// import gleeunit/should /// import gleam/option -/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/conversion +/// import gleam_community/maths/piecewise /// /// pub fn example() { -/// floatx.to_int(12.0654) +/// conversion.float_to_int(12.0654) /// |> should.equal(12) /// /// // Note: Making the following function call is equivalent /// // but instead of returning a value of type 'Int' a value /// // of type 'Float' is returned. -/// floatx.round(12.0654, option.Some(0), option.Some(floatx.RoundToZero)) +/// piecewise.round(12.0654, option.Some(0), option.Some(piecewise.RoundToZero)) /// |> should.equal(Ok(12.0)) /// } /// @@ -123,11 +124,12 @@ fn do_to_int(a: Float) -> Int /// Example /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/conversion +/// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.to_radian(360.) -/// |> should.equal(2. *. floatx.pi()) +/// conversion.degrees_to_radians(360.) +/// |> should.equal(2. *. elementary.pi()) /// } /// /// @@ -154,13 +156,14 @@ pub fn degrees_to_radians(x: Float) -> Float { /// Example /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/conversion +/// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.to_degree(0.0) +/// conversion.radians_to_degrees(0.0) /// |> should.equal(0.0) /// -/// floatx.to_degree(2. *. floatx.pi()) +/// conversion.radians_to_degrees(2. *. elementary.pi()) /// |> should.equal(360.) /// } /// diff --git a/src/gleam_community/maths/elementary.gleam b/src/gleam_community/maths/elementary.gleam index 88ffb92..1455731 100644 --- a/src/gleam_community/maths/elementary.gleam +++ b/src/gleam_community/maths/elementary.gleam @@ -136,10 +136,10 @@ fn do_acos(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.acosh(1.0) +/// elementary.acosh(1.0) /// |> should.equal(Ok(0.0)) /// -/// floatx.acosh(0.0) +/// elementary.acosh(0.0) /// |> should.be_error() /// } /// @@ -188,13 +188,13 @@ fn do_acosh(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.asin(0.0) +/// elementary.asin(0.0) /// |> should.equal(Ok(0.0)) /// -/// floatx.asin(1.1) +/// elementary.asin(1.1) /// |> should.be_error() /// -/// floatx.asin(-1.1) +/// elementary.asin(-1.1) /// |> should.be_error() /// } /// @@ -242,7 +242,7 @@ fn do_asin(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.asinh(0.0) +/// elementary.asinh(0.0) /// |> should.equal(0.0) /// } /// @@ -283,7 +283,7 @@ fn do_asinh(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.atan(0.0) +/// elementary.atan(0.0) /// |> should.equal(0.0) /// } /// @@ -333,7 +333,7 @@ fn do_atan(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.atan2(0.0, 0.0) +/// elementary.atan2(0.0, 0.0) /// |> should.equal(0.0) /// } /// @@ -375,13 +375,13 @@ fn do_atan2(a: Float, b: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.atanh(0.0) +/// elementary.atanh(0.0) /// |> should.equal(Ok(0.0)) /// -/// floatx.atanh(1.0) +/// elementary.atanh(1.0) /// |> should.be_error() /// -/// floatx.atanh(-1.0) +/// elementary.atanh(-1.0) /// |> should.be_error() /// } /// @@ -429,10 +429,10 @@ fn do_atanh(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.cos(0.0) +/// elementary.cos(0.0) /// |> should.equal(1.0) /// -/// floatx.cos(floatx.pi()) +/// elementary.cos(elementary.pi()) /// |> should.equal(-1.0) /// } /// @@ -474,7 +474,7 @@ fn do_cos(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.cosh(0.0) +/// elementary.cosh(0.0) /// |> should.equal(0.0) /// } /// @@ -515,10 +515,10 @@ fn do_cosh(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.sin(0.0) +/// elementary.sin(0.0) /// |> should.equal(0.0) /// -/// floatx.sin(0.5 *. floatx.pi()) +/// elementary.sin(0.5 *. elementary.pi()) /// |> should.equal(1.0) /// } /// @@ -561,7 +561,7 @@ fn do_sin(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.sinh(0.0) +/// elementary.sinh(0.0) /// |> should.equal(0.0) /// } /// @@ -603,7 +603,7 @@ fn do_sinh(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.tan(0.0) +/// elementary.tan(0.0) /// |> should.equal(0.0) /// } /// @@ -644,13 +644,13 @@ fn do_tan(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example () { -/// floatx.tanh(0.0) +/// elementary.tanh(0.0) /// |> should.equal(0.0) /// -/// floatx.tanh(25.0) +/// elementary.tanh(25.0) /// |> should.equal(1.0) /// -/// floatx.tanh(-25.0) +/// elementary.tanh(-25.0) /// |> should.equal(-1.0) /// } /// @@ -692,7 +692,7 @@ fn do_tanh(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.exponential(0.0) +/// elementary.exponential(0.0) /// |> should.equal(1.0) /// } /// @@ -734,13 +734,13 @@ fn do_exponential(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example () { -/// floatx.natural_logarithm(1.0) +/// elementary.natural_logarithm(1.0) /// |> should.equal(Ok(0.0)) /// -/// floatx.natural_logarithm(floatx.e()) +/// elementary.natural_logarithm(elementary.e()) /// |> should.equal(Ok(1.0)) /// -/// floatx.natural_logarithm(-1.0) +/// elementary.natural_logarithm(-1.0) /// |> should.be_error() /// } /// @@ -791,13 +791,13 @@ fn do_natural_logarithm(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example () { -/// floatx.logarithm(1.0, option.Some(10.0)) +/// elementary.logarithm(1.0, option.Some(10.0)) /// |> should.equal(Ok(0.0)) /// -/// floatx.logarithm(floatx.e(), option.Some(floatx.e())) +/// elementary.logarithm(elementary.e(), option.Some(elementary.e())) /// |> should.equal(Ok(1.0)) /// -/// floatx.logarithm(-1.0, option.Some(2.0)) +/// elementary.logarithm(-1.0, option.Some(2.0)) /// |> should.be_error() /// } /// @@ -859,13 +859,13 @@ pub fn logarithm(x: Float, base: option.Option(Float)) -> Result(Float, String) /// import gleam_community/maths/elementary /// /// pub fn example () { -/// floatx.logarithm_2(1.0) +/// elementary.logarithm_2(1.0) /// |> should.equal(Ok(0.0)) /// -/// floatx.logarithm_2(2.0) +/// elementary.logarithm_2(2.0) /// |> should.equal(Ok(1.0)) /// -/// floatx.logarithm_2(-1.0) +/// elementary.logarithm_2(-1.0) /// |> should.be_error() /// } /// @@ -914,13 +914,13 @@ fn do_logarithm_2(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example () { -/// floatx.logarithm_10(1.0) +/// elementary.logarithm_10(1.0) /// |> should.equal(Ok(0.0)) /// -/// floatx.logarithm_10(10.0) +/// elementary.logarithm_10(10.0) /// |> should.equal(Ok(1.0)) /// -/// floatx.logarithm_10(-1.0) +/// elementary.logarithm_10(-1.0) /// |> should.be_error() /// } /// @@ -975,13 +975,13 @@ fn do_logarithm_10(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.power(2., -1.) +/// elementary.power(2., -1.) /// |> should.equal(Ok(0.5)) /// -/// floatx.power(2., 2.) +/// elementary.power(2., 2.) /// |> should.equal(Ok(4.0)) /// -/// floatx.power(-1., 0.5) +/// elementary.power(-1., 0.5) /// |> should.be_error() /// } /// @@ -1037,13 +1037,13 @@ fn do_ceiling(a: Float) -> Float /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.square_root(1.0) +/// elementary.square_root(1.0) /// |> should.equal(Ok(1.0)) /// -/// floatx.square_root(4.0) +/// elementary.square_root(4.0) /// |> should.equal(Ok(2.0)) /// -/// floatx.square_root(-1.0) +/// elementary.square_root(-1.0) /// |> should.be_error() /// } /// @@ -1089,13 +1089,13 @@ pub fn square_root(x: Float) -> Result(Float, String) { /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.cube_root(1.0) +/// elementary.cube_root(1.0) /// |> should.equal(Ok(1.0)) /// -/// floatx.cube_root(27.0) +/// elementary.cube_root(27.0) /// |> should.equal(Ok(3.0)) /// -/// floatx.cube_root(-1.0) +/// elementary.cube_root(-1.0) /// |> should.be_error() /// } /// @@ -1141,16 +1141,16 @@ pub fn cube_root(x: Float) -> Result(Float, String) { /// import gleam_community/maths/elementary /// /// pub fn example() { -/// floatx.nth_root(1.0, 2) +/// elementary.nth_root(1.0, 2) /// |> should.equal(Ok(1.0)) /// -/// floatx.nth_root(27.0, 3) +/// elementary.nth_root(27.0, 3) /// |> should.equal(Ok(3.0)) /// -/// floatx.nth_root(256.0, 4) +/// elementary.nth_root(256.0, 4) /// |> should.equal(Ok(4.0)) /// -/// floatx.nth_root(-1.0, 2) +/// elementary.nth_root(-1.0, 2) /// |> should.be_error() /// } /// @@ -1235,12 +1235,12 @@ pub fn tau() -> Float { /// Example /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/elementary /// /// pub fn example() { /// // Test that the constant is approximately equal to 2.7128... -/// floatx.e() -/// |> floatx.is_close(2.7128, 0.0, 0.000001) +/// elementary.e() +/// |> elementary.is_close(2.7128, 0.0, 0.000001) /// |> should.be_true() /// } /// diff --git a/src/gleam_community/maths/metrics.gleam b/src/gleam_community/maths/metrics.gleam index 8d07c8f..5b2c4b0 100644 --- a/src/gleam_community/maths/metrics.gleam +++ b/src/gleam_community/maths/metrics.gleam @@ -23,12 +23,11 @@ //// //// --- //// -//// Metrics: A module offering functions for calculating distances and types of metrics. +//// Metrics: A module offering functions for calculating distances and other types of metrics. //// //// * **Distances** //// * [`norm`](#norm) -//// * [`float_manhatten_distance`](#float_manhatten_distance) -//// * [`int_manhatten_distance`](#int_manhatten_distance) +//// * [`manhatten_distance`](#float_manhatten_distance) //// * [`minkowski_distance`](#minkowski_distance) //// * [`euclidean_distance`](#euclidean_distance) //// * **Basic statistical measures** @@ -65,20 +64,21 @@ import gleam/float /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list +/// import gleam_community/maths/elementary +/// import gleam_community/maths/metrics +/// import gleam_community/maths/tests /// /// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) +/// let assert Ok(tol) = elementary.power(-10.0, -6.0) /// /// [1.0, 1.0, 1.0] -/// |> float_list.norm(1.0) -/// |> floatx.is_close(3.0, 0.0, tol) +/// |> metrics.norm(1.0) +/// |> tests.is_close(3.0, 0.0, tol) /// |> should.be_true() /// /// [1.0, 1.0, 1.0] -/// |> float_list.norm(-1.0) -/// |> floatx.is_close(0.3333333333333333, 0.0, tol) +/// |> metrics.norm(-1.0) +/// |> tests.is_close(0.3333333333333333, 0.0, tol) /// |> should.be_true() /// } /// @@ -127,23 +127,24 @@ pub fn norm(arr: List(Float), p: Float) -> Float { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list +/// import gleam_community/maths/elementary +/// import gleam_community/maths/metrics +/// import gleam_community/maths/tests /// /// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) +/// let assert Ok(tol) = elementary.power(-10.0, -6.0) /// /// // Empty lists returns 0.0 -/// float_list.manhatten_distance([], []) +/// metrics.float_manhatten_distance([], []) /// |> should.equal(Ok(0.0)) /// /// // Differing lengths returns error -/// float_list.manhatten_distance([], [1.0]) +/// metrics.manhatten_distance([], [1.0]) /// |> should.be_error() /// -/// let assert Ok(result) = float_list.manhatten_distance([0.0, 0.0], [1.0, 2.0]) +/// let assert Ok(result) = metrics.manhatten_distance([0.0, 0.0], [1.0, 2.0]) /// result -/// |> floatx.is_close(3.0, 0.0, tol) +/// |> tests.is_close(3.0, 0.0, tol) /// |> should.be_true() /// } /// @@ -154,74 +155,13 @@ pub fn norm(arr: List(Float), p: Float) -> Float { /// /// /// -pub fn float_manhatten_distance( +pub fn manhatten_distance( xarr: List(Float), yarr: List(Float), ) -> Result(Float, String) { minkowski_distance(xarr, yarr, 1.0) } -///
-/// -/// Spot a typo? Open an issue! -/// -///
-/// -/// Calculcate the Manhatten distance between two lists (representing vectors): -/// -/// \\[ -/// \sum_{i=1}^n \left|x_i - y_i \right| -/// \\] -/// -/// In the formula, $$n$$ is the length of the two lists and $$x_i, y_i$$ are the values in the respective input lists indexed by $$i, j$$. -/// -///
-/// Example: -/// -/// import gleeunit/should -/// import gleam_community/maths/int_list -/// -/// pub fn example () { -/// // Empty lists returns 0 -/// int_list.manhatten_distance([], []) -/// |> should.equal(Ok(0)) -/// -/// // Differing lengths returns error -/// int_list.manhatten_distance([], [1]) -/// |> should.be_error() -/// -/// let assert Ok(result) = int_list.manhatten_distance([0, 0], [1, 2]) -/// result -/// |> should.equal(3) -/// } -///
-/// -///
-/// -/// Back to top ↑ -/// -///
-/// -pub fn int_manhatten_distance( - xarr: List(Int), - yarr: List(Int), -) -> Result(Int, String) { - let xlen: Int = list.length(xarr) - let ylen: Int = list.length(yarr) - case xlen == ylen { - False -> - "Invalid input argument: length(xarr) != length(yarr). Valid input is when length(xarr) == length(yarr)." - |> Error - True -> - list.zip(xarr, yarr) - |> list.map(fn(tuple: #(Int, Int)) -> Int { - piecewise.int_absolute_value(pair.first(tuple) - pair.second(tuple)) - }) - |> arithmetics.int_sum() - |> Ok - } -} - ///
/// /// Spot a typo? Open an issue! @@ -242,27 +182,28 @@ pub fn int_manhatten_distance( /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list +/// import gleam_community/maths/elementary +/// import gleam_community/maths/metrics +/// import gleam_community/maths/tests /// /// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) +/// let assert Ok(tol) = elementary.power(-10.0, -6.0) /// /// // Empty lists returns 0.0 -/// float_list.minkowski_distance([], [], 1.0) +/// metrics.minkowski_distance([], [], 1.0) /// |> should.equal(Ok(0.0)) /// /// // Differing lengths returns error -/// float_list.minkowski_distance([], [1.0], 1.0) +/// metrics.minkowski_distance([], [1.0], 1.0) /// |> should.be_error() /// /// // Test order < 1 -/// float_list.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0) +/// metrics.minkowski_distance([0.0, 0.0], [0.0, 0.0], -1.0) /// |> should.be_error() /// -/// let assert Ok(result) = float_list.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0) +/// let assert Ok(result) = metrics.minkowski_distance([0.0, 0.0], [1.0, 2.0], 1.0) /// result -/// |> floatx.is_close(3.0, 0.0, tol) +/// |> tests.is_close(3.0, 0.0, tol) /// |> should.be_true() /// } /// @@ -318,23 +259,24 @@ pub fn minkowski_distance( /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list +/// import gleam_community/maths/elementary +/// import gleam_community/maths/metrics +/// import gleam_community/maths/tests /// /// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) +/// let assert Ok(tol) = elementary.power(-10.0, -6.0) /// /// // Empty lists returns 0.0 -/// float_list.euclidean_distance([], []) +/// metrics.euclidean_distance([], []) /// |> should.equal(Ok(0.0)) /// /// // Differing lengths returns error -/// float_list.euclidean_distance([], [1.0]) +/// metrics.euclidean_distance([], [1.0]) /// |> should.be_error() /// -/// let assert Ok(result) = float_list.euclidean_distance([0.0, 0.0], [1.0, 2.0]) +/// let assert Ok(result) = metrics.euclidean_distance([0.0, 0.0], [1.0, 2.0]) /// result -/// |> floatx.is_close(2.23606797749979, 0.0, tol) +/// |> tests.is_close(2.23606797749979, 0.0, tol) /// |> should.be_true() /// } /// @@ -371,17 +313,17 @@ pub fn euclidean_distance( /// Example: /// /// import gleeunit/should -/// import gleam_stats/stats +/// import gleam_community/maths/metrics /// /// pub fn example () { /// // An empty list returns an error /// [] -/// |> stats.mean() +/// |> metrics.mean() /// |> should.be_error() /// /// // Valid input returns a result /// [1., 2., 3.] -/// |> stats.mean() +/// |> metrics.mean() /// |> should.equal(Ok(2.)) /// } /// @@ -419,21 +361,21 @@ pub fn mean(arr: List(Float)) -> Result(Float, String) { /// Example: /// /// import gleeunit/should -/// import gleam_stats/stats +/// import gleam_community/maths/metrics /// /// pub fn example () { /// // An empty list returns an error /// [] -/// |> stats.median() +/// |> metrics.median() /// |> should.be_error() /// /// // Valid input returns a result /// [1., 2., 3.] -/// |> stats.median() +/// |> metrics.median() /// |> should.equal(Ok(2.)) /// /// [1., 2., 3., 4.] -/// |> stats.median() +/// |> metrics.median() /// |> should.equal(Ok(2.5)) /// } /// @@ -495,7 +437,7 @@ pub fn median(arr: List(Float)) -> Result(Float, String) { /// Example: /// /// import gleeunit/should -/// import gleam_stats/stats +/// import gleam_community/maths/metrics /// /// pub fn example () { /// // Degrees of freedom @@ -503,12 +445,12 @@ pub fn median(arr: List(Float)) -> Result(Float, String) { /// /// // An empty list returns an error /// [] -/// |> stats.var(ddof) +/// |> metrics.variance(ddof) /// |> should.be_error() /// /// // Valid input returns a result /// [1., 2., 3.] -/// |> stats.var(ddof) +/// |> metrics.variance(ddof) /// |> should.equal(Ok(1.)) /// } /// @@ -571,7 +513,7 @@ pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) { /// Example: /// /// import gleeunit/should -/// import gleam_stats/stats +/// import gleam_community/maths/metrics /// /// pub fn example () { /// // Degrees of freedom @@ -579,12 +521,12 @@ pub fn variance(arr: List(Float), ddof: Int) -> Result(Float, String) { /// /// // An empty list returns an error /// [] -/// |> stats.std(ddof) +/// |> metrics.standard_deviationddof) /// |> should.be_error() /// /// // Valid input returns a result /// [1., 2., 3.] -/// |> stats.std(ddof) +/// |> metrics.standard_deviation(ddof) /// |> should.equal(Ok(1.)) /// } /// diff --git a/src/gleam_community/maths/piecewise.gleam b/src/gleam_community/maths/piecewise.gleam index 3345f20..f20500b 100644 --- a/src/gleam_community/maths/piecewise.gleam +++ b/src/gleam_community/maths/piecewise.gleam @@ -93,16 +93,16 @@ import gleam_community/maths/elementary /// /// import gleeunit/should /// import gleam/option -/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/piecewise /// /// pub fn example() { -/// floatx.ceiling(12.0654, option.Some(1)) +/// piecewise.ceiling(12.0654, option.Some(1)) /// |> should.equal(Ok(12.1)) /// -/// floatx.ceiling(12.0654, option.Some(2)) +/// piecewise.ceiling(12.0654, option.Some(2)) /// |> should.equal(Ok(12.07)) /// -/// floatx.ceiling(12.0654, option.Some(3)) +/// piecewise.ceiling(12.0654, option.Some(3)) /// |> should.equal(Ok(12.066)) /// } /// @@ -148,16 +148,16 @@ pub fn ceiling(x: Float, digits: option.Option(Int)) -> Result(Float, String) { /// /// import gleeunit/should /// import gleam/option -/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/piecewise /// /// pub fn example() { -/// floatx.floor(12.0654, option.Some(1)) +/// piecewise.floor(12.0654, option.Some(1)) /// |> should.equal(Ok(12.0)) /// -/// floatx.floor(12.0654, option.Some(2)) +/// piecewise.floor(12.0654, option.Some(2)) /// |> should.equal(Ok(12.06)) /// -/// floatx.floor(12.0654, option.Some(3)) +/// piecewise.floor(12.0654, option.Some(3)) /// |> should.equal(Ok(12.065)) /// } /// @@ -203,16 +203,16 @@ pub fn floor(x: Float, digits: option.Option(Int)) -> Result(Float, String) { /// /// import gleeunit/should /// import gleam/option -/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/piecewise /// /// pub fn example() { -/// floatx.truncate(12.0654, option.Some(1)) +/// piecewise.truncate(12.0654, option.Some(1)) /// |> should.equal(Ok(12.0)) /// -/// floatx.truncate(12.0654, option.Some(2)) +/// piecewise.truncate(12.0654, option.Some(2)) /// |> should.equal(Ok(12.0)) /// -/// floatx.truncate(12.0654, option.Some(3)) +/// piecewise.truncate(12.0654, option.Some(3)) /// |> should.equal(Ok(12.0)) /// } /// @@ -319,34 +319,34 @@ pub fn truncate(x: Float, digits: option.Option(Int)) -> Result(Float, String) { /// /// import gleeunit/should /// import gleam/option -/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/piecewise /// /// pub fn example() { /// // The default number of digits is 0 if None is provided -/// floatx.round(12.0654, option.None, option.Some(floatx.RoundNearest)) +/// piecewise.round(12.0654, option.None, option.Some(piecewise.RoundNearest)) /// |> should.equal(Ok(12.0)) /// /// // The default rounding mode is "RoundNearest" if None is provided -/// floatx.round(12.0654, option.None, option.None) +/// piecewise.round(12.0654, option.None, option.None) /// |> should.equal(Ok(12.0)) /// /// // Try different rounding modes -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundNearest)) +/// piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundNearest)) /// |> should.equal(Ok(12.07)) /// -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundTiesAway)) +/// piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundTiesAway)) /// |> should.equal(Ok(12.07)) /// -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundTiesUp)) +/// piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundTiesUp)) /// |> should.equal(Ok(12.07)) /// -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundToZero)) +/// piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundToZero)) /// |> should.equal(Ok(12.06)) /// -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundDown)) +/// piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundDown)) /// |> should.equal(Ok(12.06)) /// -/// floatx.round(12.0654, option.Some(2), option.Some(floatx.RoundUp)) +/// piecewise.round(12.0654, option.Some(2), option.Some(piecewise.RoundUp)) /// |> should.equal(Ok(12.07)) /// } /// @@ -482,6 +482,28 @@ fn round_up(p: Float, x: Float) -> Float { @external(javascript, "../../maths.mjs", "ceiling") fn do_ceiling(a: Float) -> Float +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The absolute value: +/// +/// \\[ +/// \forall x, y \in \mathbb{R}, \\; |x| \in \mathbb{R}_{+}. +/// \\] +/// +/// The function takes an input $$x$$ and returns a positive float value. +/// +/// +/// +///
+/// +/// Back to top ↑ +/// +///
+/// pub fn float_absolute_value(x: Float) -> Float { case x >. 0.0 { True -> x @@ -489,6 +511,28 @@ pub fn float_absolute_value(x: Float) -> Float { } } +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The absolute value: +/// +/// \\[ +/// \forall x, y \in \mathbb{Z}, \\; |x| \in \mathbb{Z}_{+}. +/// \\] +/// +/// The function takes an input $$x$$ and returns a positive integer value. +/// +/// +/// +///
+/// +/// Back to top ↑ +/// +///
+/// pub fn int_absolute_value(x: Int) -> Int { case x > 0 { True -> x @@ -515,13 +559,13 @@ pub fn int_absolute_value(x: Int) -> Int { /// Example /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/piecewise /// /// pub fn example() { -/// floatx.absolute_difference(-10.0, 10.0) +/// piecewise.float_absolute_difference(-10.0, 10.0) /// |> should.equal(20.0) /// -/// floatx.absolute_difference(0.0, -2.0) +/// piecewise.float_absolute_difference(0.0, -2.0) /// |> should.equal(2.0) /// } /// @@ -555,13 +599,13 @@ pub fn float_absolute_difference(a: Float, b: Float) -> Float { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/piecewise /// /// pub fn example() { -/// intx.absolute_difference(-10, 10) +/// piecewise.absolute_difference(-10, 10) /// |> should.equal(20) /// -/// intx.absolute_difference(0, -2) +/// piecewise.absolute_difference(0, -2) /// |> should.equal(2) /// } /// @@ -583,7 +627,9 @@ pub fn int_absolute_difference(a: Int, b: Int) -> Int { /// ///
/// -/// The sign function that returns the sign of the input, indicating whether it is positive (+1), negative (-1), or zero (0). +/// The function takes an input $$x \in \mathbb{R}$$ and returns the sign of +/// the input, indicating whether it is positive (+1.0), negative (-1.0), or +/// zero (0.0). /// ///
/// @@ -617,8 +663,9 @@ fn do_float_sign(a: Float) -> Float /// ///
/// -/// The sign function which returns the sign of the input, indicating -/// whether it is positive (+1), negative (-1), or zero (0). +/// The function takes an input $$x \in \mathbb{Z}$$ and returns the sign of +/// the input, indicating whether it is positive (+1), negative (-1), or zero +/// (0). /// ///
/// @@ -738,19 +785,22 @@ pub fn int_flip_sign(x: Int) -> Int { /// ///
/// -/// The minimum function that takes two arguments $$x, y \in \mathbb{R}$$ and returns the smallest of the two. +/// The minimum function takes two arguments $$x, y$$ along with a function +/// for comparing $$x, y$$. The function returns the smallest of the two given +/// values. /// ///
/// Example /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx +/// import gleam/float +/// import gleam_community/maths/piecewise /// /// pub fn example() { -/// floatx.minimum(2.0, 1.5) +/// piecewise.minimum(2.0, 1.5, float.compare) /// |> should.equal(1.5) /// -/// floatx.minimum(1.5, 2.0) +/// piecewise.minimum(1.5, 2.0, float.compare) /// |> should.equal(1.5) /// } ///
@@ -781,19 +831,22 @@ pub fn minimum(x: a, y: a, compare: fn(a, a) -> order.Order) -> a { /// /// /// -/// The maximum function that takes two arguments $$x, y \in \mathbb{R}$$ and returns the largest of the two. +/// The maximum function takes two arguments $$x, y$$ along with a function +/// for comparing $$x, y$$. The function returns the largest of the two given +/// values. /// ///
/// Example /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx +/// import gleam/float +/// import gleam_community/maths/piecewise /// /// pub fn example() { -/// floatx.maximum(2.0, 1.5) +/// piecewise.maximum(2.0, 1.5, float.compare) /// |> should.equal(1.5) /// -/// floatx.maximum(1.5, 2.0) +/// piecewise.maximum(1.5, 2.0, float.compare) /// |> should.equal(1.5) /// } ///
@@ -818,19 +871,22 @@ pub fn maximum(x: a, y: a, compare: fn(a, a) -> order.Order) -> a { /// /// /// -/// The minmax function that takes two arguments $$x, y \in \mathbb{R}$$ and returns a tuple with the smallest value first and largest second. +/// The minmax function takes two arguments $$x, y$$ along with a function +/// for comparing $$x, y$$. The function returns a tuple with the smallest +/// value first and largest second. /// ///
/// Example /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx +/// import gleam/float +/// import gleam_community/maths/piecewise /// /// pub fn example() { -/// floatx.minmax(2.0, 1.5) +/// piecewise.minmax(2.0, 1.5, float.compare) /// |> should.equal(#(1.5, 2.0)) /// -/// floatx.minmax(1.5, 2.0) +/// piecewise.minmax(1.5, 2.0, float.compare) /// |> should.equal(#(1.5, 2.0)) /// } ///
@@ -851,23 +907,24 @@ pub fn minmax(x: a, y: a, compare: fn(a, a) -> order.Order) -> #(a, a) { /// /// /// -/// Returns the minimum value of a list. +/// Returns the minimum value of a given list. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int_list +/// import gleam/int +/// import gleam_community/maths/piecewise /// /// pub fn example () { /// // An empty lists returns an error /// [] -/// |> int_list.minimum() +/// |> piecewise.list_minimum(int.compare) /// |> should.be_error() /// /// // Valid input returns a result /// [4, 4, 3, 2, 1] -/// |> int_list.minimum() +/// |> piecewise.list_minimum(int.compare) /// |> should.equal(Ok(1)) /// } ///
@@ -908,23 +965,24 @@ pub fn list_minimum( /// /// /// -/// Returns the maximum value of a list. +/// Returns the maximum value of a given list. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float_list +/// import gleam/float +/// import gleam_community/maths/piecewise /// /// pub fn example () { /// // An empty lists returns an error /// [] -/// |> float_list.maximum() +/// |> piecewise.list_maximum(float.compare) /// |> should.be_error() /// /// // Valid input returns a result /// [4.0, 4.0, 3.0, 2.0, 1.0] -/// |> float_list.maximum() +/// |> piecewise.list_maximum(float.compare) /// |> should.equal(Ok(4.0)) /// } ///
@@ -972,23 +1030,24 @@ pub fn list_maximum( /// /// /// -/// Returns the indices of the minimum values in a list. +/// Returns the indices of the minimum values in a given list. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float_list +/// import gleam/float +/// import gleam_community/maths/piecewise /// /// pub fn example () { /// // An empty lists returns an error /// [] -/// |> float_list.arg_minimum() +/// |> piecewise.arg_minimum(float.compare) /// |> should.be_error() /// /// // Valid input returns a result /// [4.0, 4.0, 3.0, 2.0, 1.0] -/// |> float_list.arg_minimum() +/// |> piecewise.arg_minimum(float.compare) /// |> should.equal(Ok([4])) /// } ///
@@ -1041,23 +1100,24 @@ pub fn arg_minimum( /// /// /// -/// Returns the indices of the maximum values in a list. +/// Returns the indices of the maximum values in a given list. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float_list +/// import gleam/float +/// import gleam_community/maths/piecewise /// /// pub fn example () { /// // An empty lists returns an error /// [] -/// |> float_list.arg_maximum() +/// |> piecewise.arg_maximum(float.compare) /// |> should.be_error() /// /// // Valid input returns a result /// [4.0, 4.0, 3.0, 2.0, 1.0] -/// |> float_list.arg_maximum() +/// |> piecewise.arg_maximum(float.compare) /// |> should.equal(Ok([0, 1])) /// } ///
@@ -1110,23 +1170,24 @@ pub fn arg_maximum( /// /// /// -/// Returns a tuple consisting of the minimum and maximum value of a list. +/// Returns a tuple consisting of the minimum and maximum values of a given list. /// ///
/// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float_list +/// import gleam/float +/// import gleam_community/maths/piecewise /// /// pub fn example () { /// // An empty lists returns an error /// [] -/// |> float_list.extrema() +/// |> piecewise.extrema(float.compare) /// |> should.be_error() /// /// // Valid input returns a result /// [4.0, 4.0, 3.0, 2.0, 1.0] -/// |> float_list.extrema() +/// |> piecewise.extrema(float.compare) /// |> should.equal(Ok(#(1.0, 4.0))) /// } ///
diff --git a/src/gleam_community/maths/sequences.gleam b/src/gleam_community/maths/sequences.gleam index 4ed0812..0ebb705 100644 --- a/src/gleam_community/maths/sequences.gleam +++ b/src/gleam_community/maths/sequences.gleam @@ -49,20 +49,20 @@ import gleam/list /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float_list +/// import gleam_community/maths/sequences /// /// pub fn example () { -/// float_list.arange(1.0, 5.0, 1.0) +/// sequences.arange(1.0, 5.0, 1.0) /// |> should.equal([1.0, 2.0, 3.0, 4.0]) /// /// // No points returned since /// // start smaller than stop and positive step -/// float_list.arange(5.0, 1.0, 1.0) +/// sequences.arange(5.0, 1.0, 1.0) /// |> should.equal([]) /// /// // Points returned since /// // start smaller than stop but negative step -/// float_list.arange(5.0, 1.0, -1.0) +/// sequences.arange(5.0, 1.0, -1.0) /// |> should.equal([5.0, 4.0, 3.0, 2.0]) /// } /// @@ -104,20 +104,21 @@ pub fn arange(start: Float, stop: Float, step: Float) -> List(Float) { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list +/// import gleam_community/maths/elementary +/// import gleam_community/maths/sequences +/// import gleam_community/maths/tests /// /// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) -/// let assert Ok(linspace) = float_list.linear_space(10.0, 50.0, 5, True) +/// let assert Ok(tol) = elementary.power(-10.0, -6.0) +/// let assert Ok(linspace) = sequences.linear_space(10.0, 50.0, 5, True) /// let assert Ok(result) = -/// float_list.all_close(linspace, [10.0, 20.0, 30.0, 40.0, 50.0], 0.0, tol) +/// tests.all_close(linspace, [10.0, 20.0, 30.0, 40.0, 50.0], 0.0, tol) /// result /// |> list.all(fn(x) { x == True }) /// |> should.be_true() /// /// // A negative number of points (-5) does not work -/// float_list.linear_space(10.0, 50.0, -5, True) +/// sequences.linear_space(10.0, 50.0, -5, True) /// |> should.be_error() /// } /// @@ -183,20 +184,21 @@ pub fn linear_space( /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list +/// import gleam_community/maths/elementary +/// import gleam_community/maths/sequences +/// import gleam_community/maths/tests /// /// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) -/// let assert Ok(logspace) = float_list.logarithmic_space(1.0, 3.0, 3, True, 10.0) +/// let assert Ok(tol) = elementary.power(-10.0, -6.0) +/// let assert Ok(logspace) = sequences.logarithmic_space(1.0, 3.0, 3, True, 10.0) /// let assert Ok(result) = -/// float_list.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) +/// tests.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) /// result /// |> list.all(fn(x) { x == True }) /// |> should.be_true() /// /// // A negative number of points (-3) does not work -/// float_list.logarithmic_space(1.0, 3.0, -3, False, 10.0) +/// sequences.logarithmic_space(1.0, 3.0, -3, False, 10.0) /// |> should.be_error() /// } /// @@ -243,27 +245,28 @@ pub fn logarithmic_space( /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx -/// import gleam_community/maths/float_list +/// import gleam_community/maths/elementary +/// import gleam_community/maths/sequences +/// import gleam_community/maths/tests /// /// pub fn example () { -/// let assert Ok(tol) = floatx.power(-10.0, -6.0) -/// let assert Ok(logspace) = float_list.geometric_space(10.0, 1000.0, 3, True) +/// let assert Ok(tol) = elementary.power(-10.0, -6.0) +/// let assert Ok(logspace) = sequences.geometric_space(10.0, 1000.0, 3, True) /// let assert Ok(result) = -/// float_list.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) +/// tests.all_close(logspace, [10.0, 100.0, 1000.0], 0.0, tol) /// result /// |> list.all(fn(x) { x == True }) /// |> should.be_true() /// /// // Input (start and stop can't be equal to 0.0) -/// float_list.geometric_space(0.0, 1000.0, 3, False) +/// sequences.geometric_space(0.0, 1000.0, 3, False) /// |> should.be_error() /// -/// float_list.geometric_space(-1000.0, 0.0, 3, False) +/// sequences.geometric_space(-1000.0, 0.0, 3, False) /// |> should.be_error() /// /// // A negative number of points (-3) does not work -/// float_list.geometric_space(10.0, 1000.0, -3, False) +/// sequences.geometric_space(10.0, 1000.0, -3, False) /// |> should.be_error() /// } /// diff --git a/src/gleam_community/maths/tests.gleam b/src/gleam_community/maths/tests.gleam index 8ff3107..4ae75c4 100644 --- a/src/gleam_community/maths/tests.gleam +++ b/src/gleam_community/maths/tests.gleam @@ -61,7 +61,7 @@ import gleam_community/maths/arithmetics /// Example /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/tests /// /// 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/float_list +/// import gleam_community/maths/tests /// /// 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 -/// float_list.all_close(xarr, yarr, rtol, atol) +/// tests.all_close(xarr, yarr, rtol, atol) /// |> fn(zarr: Result(List(Bool), String)) -> Result(Bool, Nil) { /// case zarr { /// Ok(arr) -> @@ -173,7 +173,7 @@ pub fn all_close( /// /// /// -/// Determine if a given value $$a$$ is fractional. +/// Determine if a given value is fractional. /// /// `True` is returned if the given value is fractional, otherwise `False` is returned. /// @@ -181,9 +181,14 @@ pub fn all_close( /// Example /// /// import gleeunit/should -/// import gleam_community/maths/float as floatx +/// import gleam_community/maths/tests /// /// pub fn example () { +/// tests.is_fractional(0.3333) +/// |> should.equal(True) +/// +/// tests.is_fractional(1.0) +/// |> should.equal(False) /// } /// /// @@ -213,15 +218,15 @@ fn do_ceiling(a: Float) -> Float /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/tests /// /// pub fn example() { /// // Check if 4 is a power of 2 (it is) -/// intx.is_power(4, 2) +/// tests.is_power(4, 2) /// |> should.equal(True) /// /// // Check if 5 is a power of 2 (it is not) -/// intx.is_power(5, 2) +/// tests.is_power(5, 2) /// |> should.equal(False) /// } /// @@ -261,13 +266,13 @@ pub fn is_power(x: Int, y: Int) -> Bool { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/tests /// /// pub fn example() { -/// intx.is_perfect(6) +/// tests.is_perfect(6) /// |> should.equal(True) /// -/// intx.is_perfect(28) +/// tests.is_perfect(28) /// |> should.equal(True) /// } /// @@ -303,13 +308,13 @@ fn do_sum(arr: List(Int)) -> Int { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/tests /// /// pub fn example() { -/// intx.is_even(-3) +/// tests.is_even(-3) /// |> should.equal(False) /// -/// intx.is_even(-4) +/// tests.is_even(-4) /// |> should.equal(True) /// } /// @@ -336,13 +341,13 @@ pub fn is_even(x: Int) -> Bool { /// Example: /// /// import gleeunit/should -/// import gleam_community/maths/int as intx +/// import gleam_community/maths/tests /// /// pub fn example() { -/// intx.is_odd(-3) +/// tests.is_odd(-3) /// |> should.equal(True) /// -/// intx.is_odd(-4) +/// tests.is_odd(-4) /// |> should.equal(False) /// } /// diff --git a/test/gleam/gleam_community_maths_metrics.gleam b/test/gleam/gleam_community_maths_metrics.gleam index 0c91b6d..0872c4e 100644 --- a/test/gleam/gleam_community_maths_metrics.gleam +++ b/test/gleam/gleam_community_maths_metrics.gleam @@ -58,34 +58,33 @@ pub fn float_list_manhatten_test() { let assert Ok(tol) = elementary.power(-10.0, -6.0) // Empty lists returns 0.0 - metrics.float_manhatten_distance([], []) + metrics.manhatten_distance([], []) |> should.equal(Ok(0.0)) // Differing lengths returns error - metrics.float_manhatten_distance([], [1.0]) + metrics.manhatten_distance([], [1.0]) |> should.be_error() // Manhatten distance (p = 1) - let assert Ok(result) = - metrics.float_manhatten_distance([0.0, 0.0], [1.0, 2.0]) + let assert Ok(result) = metrics.manhatten_distance([0.0, 0.0], [1.0, 2.0]) result |> tests.is_close(3.0, 0.0, tol) |> should.be_true() } -pub fn int_list_manhatten_test() { - // Empty lists returns 0 - metrics.int_manhatten_distance([], []) - |> should.equal(Ok(0)) +// pub fn int_list_manhatten_test() { +// // Empty lists returns 0 +// metrics.int_manhatten_distance([], []) +// |> should.equal(Ok(0)) - // Differing lengths returns error - metrics.int_manhatten_distance([], [1]) - |> should.be_error() +// // Differing lengths returns error +// metrics.int_manhatten_distance([], [1]) +// |> should.be_error() - let assert Ok(result) = metrics.int_manhatten_distance([0, 0], [1, 2]) - result - |> should.equal(3) -} +// let assert Ok(result) = metrics.int_manhatten_distance([0, 0], [1, 2]) +// result +// |> should.equal(3) +// } pub fn float_list_minkowski_test() { let assert Ok(tol) = elementary.power(-10.0, -6.0) diff --git a/test/gleam/gleam_community_maths_special.gleam b/test/gleam/gleam_community_maths_special.gleam index 6aa3587..78d84ee 100644 --- a/test/gleam/gleam_community_maths_special.gleam +++ b/test/gleam/gleam_community_maths_special.gleam @@ -1,30 +1,92 @@ +import gleam_community/maths/elementary import gleam_community/maths/special import gleam_community/maths/tests import gleeunit import gleeunit/should import gleam/result -import gleam/io pub fn main() { gleeunit.main() } pub fn float_beta_function_test() { - io.debug("") - io.debug("TODO: Implement tests for 'float.beta'.") + let assert Ok(tol) = elementary.power(-10.0, -6.0) + + // Valid input returns a result + special.beta(-0.5, 0.5) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + special.beta(0.5, 0.5) + |> tests.is_close(3.1415926535897927, 0.0, tol) + |> should.be_true() + + special.beta(0.5, -0.5) + |> tests.is_close(0.0, 0.0, tol) + |> should.be_true() + + special.beta(5.0, 5.0) + |> tests.is_close(0.0015873015873015873, 0.0, tol) + |> should.be_true() } pub fn float_error_function_test() { - io.debug("") - io.debug("TODO: Implement tests for 'float.erf'.") + let assert Ok(tol) = elementary.power(-10.0, -6.0) + + // Valid input returns a result + special.erf(-0.5) + |> tests.is_close(-0.5204998778130465, 0.0, tol) + |> should.be_true() + + special.erf(0.5) + |> tests.is_close(0.5204998778130465, 0.0, tol) + |> should.be_true() + + special.erf(1.0) + |> tests.is_close(0.8427007929497148, 0.0, tol) + |> should.be_true() + + special.erf(2.0) + |> tests.is_close(0.9953222650189527, 0.0, tol) + |> should.be_true() + + special.erf(10.0) + |> tests.is_close(1.0, 0.0, tol) + |> should.be_true() } pub fn float_gamma_function_test() { - io.debug("") - io.debug("TODO: Implement tests for 'float.gamma'.") + let assert Ok(tol) = elementary.power(-10.0, -6.0) + + // Valid input returns a result + special.gamma(-0.5) + |> tests.is_close(-3.5449077018110318, 0.0, tol) + |> should.be_true() + + special.gamma(0.5) + |> tests.is_close(1.7724538509055159, 0.0, tol) + |> should.be_true() + + special.gamma(1.0) + |> tests.is_close(1.0, 0.0, tol) + |> should.be_true() + + special.gamma(2.0) + |> tests.is_close(1.0, 0.0, tol) + |> should.be_true() + + special.gamma(3.0) + |> tests.is_close(2.0, 0.0, tol) + |> should.be_true() + + special.gamma(10.0) + |> tests.is_close(362_880.0, 0.0, tol) + |> should.be_true() } pub fn float_incomplete_gamma_function_test() { + let assert Ok(tol) = elementary.power(-10.0, -6.0) + // Invalid input gives an error // 1st arg is invalid special.incomplete_gamma(-1.0, 1.0) @@ -37,21 +99,21 @@ pub fn float_incomplete_gamma_function_test() { // Valid input returns a result special.incomplete_gamma(1.0, 0.0) |> result.unwrap(-999.0) - |> tests.is_close(0.0, 0.0, 0.01) + |> tests.is_close(0.0, 0.0, tol) |> should.be_true() special.incomplete_gamma(1.0, 2.0) |> result.unwrap(-999.0) - |> tests.is_close(0.864664716763387308106, 0.0, 0.01) + |> tests.is_close(0.864664716763387308106, 0.0, tol) |> should.be_true() special.incomplete_gamma(2.0, 3.0) |> result.unwrap(-999.0) - |> tests.is_close(0.8008517265285442280826, 0.0, 0.01) + |> tests.is_close(0.8008517265285442280826, 0.0, tol) |> should.be_true() special.incomplete_gamma(3.0, 4.0) |> result.unwrap(-999.0) - |> tests.is_close(1.523793388892911312363, 0.0, 0.01) + |> tests.is_close(1.523793388892911312363, 0.0, tol) |> should.be_true() } From e7608ed33b226352a38a29b6f1ba31e3433e7ed4 Mon Sep 17 00:00:00 2001 From: NicklasXYZ Date: Sun, 17 Sep 2023 14:15:10 +0200 Subject: [PATCH 3/6] Update README --- README.md | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 089d352..138339f 100644 --- a/README.md +++ b/README.md @@ -10,27 +10,42 @@ The library supports both targets: Erlang and JavaScript. ## Quickstart ```gleam -import gleam_community/maths/float as floatx -import gleam_community/maths/int as intx -import gleam_community/maths/float_list -import gleam_community/maths/int_list +import gleam_community/maths/elementary +import gleam_community/maths/arithmetics +import gleam_community/maths/piecewise +import gleam_community/maths/tests +import gleam/float pub fn main() { // Evaluate the sine function - floatx.sin(floatx.pi()) + elementary.sin(floatx.pi()) // Returns Float: 0.0 // Find the greatest common divisor - intx.gcd(54, 24) + arithmetics.gcd(54, 24) // Returns Int: 6 // Find the minimum and maximum of a list - float_list.extrema([10.0, 3.0, 50.0, 20.0, 3.0]) + piecewise.extrema([10.0, 3.0, 50.0, 20.0, 3.0], float.compare) // Returns Tuple: Ok(#(3.0, 50.0)) // Find the list indices of the smallest value - int_list.arg_minimum([10, 3, 50, 20, 3]) - // Returns List: Ok([1, 4]) + piecewise.arg_minimum([10, 3, 50, 20, 3], float.compare) + // Returns List: Ok([1, 4]) + + // Determine if a number if fractional: + tests.is_fractional(0.3333) + // Returns Bool: True + + // Determine if 28 is a power of 3 + tests.is_power(28, 3) + // Returns Bool: False + + // Generate all k = 1 combinations of [1, 2] + [1, 2] + |> combinatorics.list_combination(1) + |> should.equal(Ok([[1, 2]])) + // Returns: List: Ok([[1], [2]]) } ``` From 676dbce62b437393da6dc61b17ddc16a48fd6718 Mon Sep 17 00:00:00 2001 From: NicklasXYZ Date: Sun, 17 Sep 2023 14:15:35 +0200 Subject: [PATCH 4/6] Update README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 138339f..9cf1578 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ pub fn main() { piecewise.arg_minimum([10, 3, 50, 20, 3], float.compare) // Returns List: Ok([1, 4]) - // Determine if a number if fractional: + // Determine if a number is fractional tests.is_fractional(0.3333) // Returns Bool: True @@ -44,7 +44,6 @@ pub fn main() { // Generate all k = 1 combinations of [1, 2] [1, 2] |> combinatorics.list_combination(1) - |> should.equal(Ok([[1, 2]])) // Returns: List: Ok([[1], [2]]) } From 25ecb737ec3133c13f1244216f5a9fa30cda5e52 Mon Sep 17 00:00:00 2001 From: NicklasXYZ Date: Sun, 17 Sep 2023 14:16:10 +0200 Subject: [PATCH 5/6] Update README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 9cf1578..8fa12a3 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,7 @@ pub fn main() { // Returns Bool: False // Generate all k = 1 combinations of [1, 2] - [1, 2] - |> combinatorics.list_combination(1) + combinatorics.list_combination([1, 2], 1) // Returns: List: Ok([[1], [2]]) } From a4eb5504af958b0d0f2c3375723962d558d84bd3 Mon Sep 17 00:00:00 2001 From: NicklasXYZ Date: Sun, 17 Sep 2023 14:16:36 +0200 Subject: [PATCH 6/6] Update README --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8fa12a3..f591766 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ pub fn main() { // Find the greatest common divisor arithmetics.gcd(54, 24) - // Returns Int: 6 + // Returns Int: 6 // Find the minimum and maximum of a list piecewise.extrema([10.0, 3.0, 50.0, 20.0, 3.0], float.compare) @@ -31,11 +31,11 @@ pub fn main() { // Find the list indices of the smallest value piecewise.arg_minimum([10, 3, 50, 20, 3], float.compare) - // Returns List: Ok([1, 4]) + // Returns List: Ok([1, 4]) // Determine if a number is fractional tests.is_fractional(0.3333) - // Returns Bool: True + // Returns Bool: True // Determine if 28 is a power of 3 tests.is_power(28, 3) @@ -43,7 +43,7 @@ pub fn main() { // Generate all k = 1 combinations of [1, 2] combinatorics.list_combination([1, 2], 1) - // Returns: List: Ok([[1], [2]]) + // Returns List: Ok([[1], [2]]) } ```