@@ -1394,253 +2024,6 @@ fn gammainc_sum(a: Float, x: Float, t: Float, s: Float, n: Float) -> Float {
}
}
-///
-///
-/// The function rounds a floating point number to a specific decimal precision.
-///
-///
-/// Example:
-///
-/// import gleeunit/should
-/// import gleam_stats/math
-///
-/// pub fn example() {
-/// math.round(0.4444, 2)
-/// |> should.equal(0.44)
-///
-/// math.round(0.4445, 2)
-/// |> should.equal(0.44)
-///
-/// math.round(0.4455, 2)
-/// |> should.equal(0.45)
-///
-/// math.round(0.4555, 2)
-/// |> should.equal(0.46)
-/// }
-///
-///
-///
-///
-pub fn round(x: Float, precision: Int) -> Float {
- assert Ok(p) = pow(10.0, int.to_float(precision))
- int.to_float(float.round(x *. p)) /. p
-}
-
-///
-///
-/// 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_stats/math
-///
-/// pub fn example() {
-/// // Invalid input gives an error
-/// // Error on: n = -1 < 0
-/// math.combination(-1, 1)
-/// |> should.be_error()
-///
-/// // Valid input returns a result
-/// math.combination(4, 0)
-/// |> should.equal(Ok(1))
-///
-/// math.combination(4, 4)
-/// |> should.equal(Ok(1))
-///
-/// math.combination(4, 2)
-/// |> should.equal(Ok(6))
-/// }
-///
-///
-///
-///
-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 + 1)
- |> list.fold(
- 1,
- fn(acc: Int, x: Int) -> Int { acc * { n + 1 - x } / x },
- )
- |> Ok
- }
- }
- }
- }
-}
-
-///
-///
-/// A combinatorial function for computing the total number of combinations of $$n$$
-/// elements, that is $$n!$$.
-///
-///
-/// Example:
-///
-/// import gleeunit/should
-/// import gleam_stats/math
-///
-/// pub fn example() {
-/// // Invalid input gives an error
-/// math.factorial(-1)
-/// |> should.be_error()
-///
-/// // Valid input returns a result
-/// math.factorial(0)
-/// |> should.equal(Ok(1))
-/// math.factorial(1)
-/// |> should.equal(Ok(1))
-/// math.factorial(2)
-/// |> should.equal(Ok(2))
-/// math.factorial(3)
-/// |> should.equal(Ok(6))
-/// math.factorial(4)
-/// |> should.equal(Ok(24))
-/// }
-///
-///
-///
-///
-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 + 1)
- |> list.fold(1, fn(acc: Int, x: Int) { acc * x })
- |> Ok
- }
- }
-}
-
-///
-///
-/// 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_stats/math
-///
-/// pub fn example() {
-/// // Invalid input gives an error
-/// // Error on: n = -1 < 0
-/// math.permutation(-1, 1)
-/// |> should.be_error()
-///
-/// // Valid input returns a result
-/// math.permutation(4, 0)
-/// |> should.equal(Ok(1))
-///
-/// math.permutation(4, 4)
-/// |> should.equal(Ok(1))
-///
-/// math.permutation(4, 2)
-/// |> should.equal(Ok(12))
-/// }
-///
-///
-///
-///
-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 -> {
- assert Ok(v1) = factorial(n)
- assert Ok(v2) = factorial(n - k)
- v1 / v2
- |> Ok
- }
- }
- }
- }
-}
-
///
///
/// Spot a typo? Open an issue!
@@ -1687,52 +2070,20 @@ pub fn tau() -> Float {
2.0 *. pi()
}
-/// Returns the absolute difference of the inputs as a positive Int.
-///
-/// ## Examples
-///
-/// ```gleam
-/// > absolute_int_difference(-10, 10)
-/// > 20
-/// ```
-///
-/// ```gleam
-/// > absolute_int_difference(0, -2)
-/// 2
-///
-pub fn absolute_int_difference(a: Int, b: Int) -> Int {
- a - b
- |> int.absolute_value()
-}
-
-/// Returns the absolute difference of the inputs as a positive Float.
-///
-/// ## Examples
-///
-/// ```gleam
-/// > absolute_float_difference(-10, 10)
-/// > 20
-/// ```
-///
-/// ```gleam
-/// > absolute_float_difference(0, -2)
-/// 2
-///
///
///
-/// The inverse cosine function:
+/// The absolute difference:
///
/// \\[
-/// \forall x \in \[-1, 1\], \\; \cos^{-1}{(x)} = y \in \[0, \pi \]
+/// \forall x, y \in \mathbb{R}, \\; |x - y| \in \mathbb{R}_{+}.
/// \\]
///
-/// 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.
+/// The function takes two inputs $$x$$ and $$y$$ and returns a positive float
+/// value which is the the absolute difference of the inputs.
///
///
/// Example:
@@ -1741,14 +2092,11 @@ pub fn absolute_int_difference(a: Int, b: Int) -> Int {
/// import gleam_stats/math
///
/// pub fn example() {
-/// math.acos(1.0)
-/// |> should.equal(Ok(0.0))
+/// math.absdiff(-10.0, 10.0)
+/// |> should.equal(20.0)
///
-/// math.acos(1.1)
-/// |> should.be_error()
-///
-/// math.acos(-1.1)
-/// |> should.be_error()
+/// math.absdiff(0.0, -2.0)
+/// |> should.equal(2.0)
/// }
///
///
@@ -1758,7 +2106,55 @@ pub fn absolute_int_difference(a: Int, b: Int) -> Int {
///
///
///
-pub fn absolute_difference(a: Float, b: Float) -> Float {
+pub fn absdiff(a: Float, b: Float) -> Float {
a -. b
|> float.absolute_value()
}
+
+///
+///
+/// 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_stats/stats
+///
+/// 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
+/// stats.isclose(val, ref_val, rtol, atol)
+/// |> should.be_true()
+/// }
+///
+///
+///
+///
+pub fn isclose(a: Float, b: Float, rtol: Float, atol: Float) -> Bool {
+ let x: Float = float.absolute_value(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
new file mode 100644
index 0000000..77b0089
--- /dev/null
+++ b/src/gleam_community/maths/float_list.gleam
@@ -0,0 +1,393 @@
+////
+////
+////
+////
+////
+////
+//// A module containing several different kinds of mathematical functions
+//// applying to lists of real numbers.
+////
+//// Function naming has been adopted from
C mathematical function .
+////
+//// ---
+////
+//// * **Miscellaneous functions**
+//// * [`allclose`](#allclose)
+//// * [`amax`](#amax)
+//// * [`amin`](#amin)
+//// * [`argmax`](#argmax)
+//// * [`argmin`](#argmin)
+//// * [`allclose`](#allclose)
+//// * [`trim`](#trim)
+
+import gleam/list
+import gleam/int
+import gleam/float
+import gleam/pair
+import gleam_community/maths/float as floatx
+
+///
+///
+/// Determine if a list of values are close to or equivalent to a
+/// another list of reference values.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// 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
+/// stats.allclose(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 allclose(
+ 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.isclose(pair.first(z), pair.second(z), rtol, atol)
+ })
+ |> Ok
+ }
+}
+
+///
+///
+/// Returns the indices of the minimum values in a list.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// pub fn example () {
+/// // An empty lists returns an error
+/// []
+/// |> stats.argmin()
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// [4., 4., 3., 2., 1.]
+/// |> stats.argmin()
+/// |> should.equal(Ok([4]))
+/// }
+///
+///
+///
+///
+pub fn argmin(arr: List(Float)) -> Result(List(Int), String) {
+ case arr {
+ [] ->
+ "Invalid input argument: The list is empty."
+ |> Error
+ _ -> {
+ assert Ok(min) =
+ arr
+ |> amin()
+ arr
+ |> list.index_map(fn(index: Int, a: Float) -> Int {
+ case a -. min {
+ 0. -> index
+ _ -> -1
+ }
+ })
+ |> list.filter(fn(index: Int) -> Bool {
+ case index {
+ -1 -> False
+ _ -> True
+ }
+ })
+ |> Ok
+ }
+ }
+}
+
+///
+///
+/// Returns the indices of the maximum values in a list.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// pub fn example () {
+/// // An empty lists returns an error
+/// []
+/// |> stats.argmax()
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// [4., 4., 3., 2., 1.]
+/// |> stats.argmax()
+/// |> should.equal(Ok([0, 1]))
+/// }
+///
+///
+///
+///
+pub fn argmax(arr: List(Float)) -> Result(List(Int), String) {
+ case arr {
+ [] ->
+ "Invalid input argument: The list is empty."
+ |> Error
+ _ -> {
+ assert Ok(max) =
+ arr
+ |> amax()
+ arr
+ |> list.index_map(fn(index: Int, a: Float) -> Int {
+ case a -. max {
+ 0. -> index
+ _ -> -1
+ }
+ })
+ |> list.filter(fn(index: Int) -> Bool {
+ case index {
+ -1 -> False
+ _ -> True
+ }
+ })
+ |> Ok
+ }
+ }
+}
+
+///
+///
+/// Returns the maximum value of a list.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// pub fn example () {
+/// // An empty lists returns an error
+/// []
+/// |> stats.amax()
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// [4., 4., 3., 2., 1.]
+/// |> stats.amax()
+/// |> should.equal(Ok(4.))
+/// }
+///
+///
+///
+///
+pub fn amax(arr: List(Float)) -> Result(Float, String) {
+ case arr {
+ [] ->
+ "Invalid input argument: The list is empty."
+ |> Error
+ _ -> {
+ assert Ok(val0) = list.at(arr, 0)
+ arr
+ |> list.fold(
+ val0,
+ fn(acc: Float, a: Float) {
+ case a >. acc {
+ True -> a
+ False -> acc
+ }
+ },
+ )
+ |> Ok
+ }
+ }
+}
+
+///
+///
+/// Returns the minimum value of a list.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// pub fn example () {
+/// // An empty lists returns an error
+/// []
+/// |> stats.amin()
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// [4., 4., 3., 2., 1.]
+/// |> stats.amin()
+/// |> should.equal(Ok(1.))
+/// }
+///
+///
+///
+///
+pub fn amin(arr: List(Float)) -> Result(Float, String) {
+ case arr {
+ [] ->
+ "Invalid input argument: The list is empty."
+ |> Error
+ _ -> {
+ assert Ok(val0) = list.at(arr, 0)
+ arr
+ |> list.fold(
+ val0,
+ fn(acc: Float, a: Float) {
+ case a <. acc {
+ True -> a
+ False -> acc
+ }
+ },
+ )
+ |> Ok
+ }
+ }
+}
+
+///
+///
+/// Returns the minimum value of a list.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// pub fn example () {
+/// // An empty lists returns an error
+/// []
+/// |> stats.amin()
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// [4., 4., 3., 2., 1.]
+/// |> stats.amin()
+/// |> should.equal(Ok(1.))
+/// }
+///
+///
+///
+///
+pub fn extrema(arr: List(Float)) -> Result(#(Float, Float), String) {
+ todo
+ // case arr {
+ // [] ->
+ // "Invalid input argument: The list is empty."
+ // |> Error
+ // _ -> {
+ // assert Ok(val0) = list.at(arr, 0)
+ // arr
+ // |> list.fold(
+ // val0,
+ // fn(acc: Float, a: Float) {
+ // case a <. acc {
+ // True -> a
+ // False -> acc
+ // }
+ // },
+ // )
+ // |> Ok
+ // }
+ // }
+}
diff --git a/src/gleam_community/maths/int.gleam b/src/gleam_community/maths/int.gleam
new file mode 100644
index 0000000..3e89f1b
--- /dev/null
+++ b/src/gleam_community/maths/int.gleam
@@ -0,0 +1,455 @@
+////
+////
+////
+////
+////
+////
+//// A module containing several different kinds of mathematical functions
+//// applying to integer numbers.
+////
+//// Function naming has been adopted from
C mathematical function .
+////
+//// ---
+////
+//// * **Mathematical functions**
+//// * [`absdiff`](#abs_diff)
+//// * [`flipsign`](#flipsign)
+//// * [`max`](#max)
+//// * [`min`](#min)
+//// * [`minmax`](#minmax)
+//// * [`round`](#round)
+//// * [`sign`](#sign)
+//// * **Combinatorial functions**
+//// * [`combination`](#combination)
+//// * [`factorial`](#factorial)
+//// * [`permutation`](#permutation)
+//// * **Tests**
+//// * [`ispow2`](#ispow2)
+//// * [`iseven`](#iseven)
+//// * [`isodd`](#isodd)
+
+import gleam/list
+import gleam/int
+import gleam/float
+
+///
+///
+/// The min function.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/math
+///
+/// pub fn example() {
+/// math.min(2.0, 1.5)
+/// |> should.equal(1.5)
+///
+/// math.min(1.5, 2.0)
+/// |> should.equal(1.5)
+/// }
+///
+///
+///
+///
+pub fn min(x: Int, y: Int) -> Int {
+ case x < y {
+ True -> x
+ False -> y
+ }
+}
+
+///
+///
+/// The min function.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/math
+///
+/// pub fn example() {
+/// math.min(2.0, 1.5)
+/// |> should.equal(1.5)
+///
+/// math.min(1.5, 2.0)
+/// |> should.equal(1.5)
+/// }
+///
+///
+///
+///
+pub fn max(x: Int, y: Int) -> Int {
+ case x > y {
+ True -> x
+ False -> y
+ }
+}
+
+///
+///
+/// The minmax function.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/math
+///
+/// pub fn example() {
+/// math.minmax(2.0, 1.5)
+/// |> should.equal(#(1.5, 2.0))
+///
+/// math.minmax(1.5, 2.0)
+/// |> should.equal(#(1.5, 2.0))
+/// }
+///
+///
+///
+///
+pub fn minmax(x: Int, y: Int) -> #(Int, Int) {
+ #(min(x, y), max(x, y))
+}
+
+///
+///
+/// The sign function which returns the sign of the input, indicating
+/// whether it is positive, negative, or zero.
+///
+///
+///
+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 =
+ "../math.mjs" "sign"
+}
+
+///
+///
+///
+///
+///
+pub fn flipsign(x: Int) -> Int {
+ -1 * x
+}
+
+///
+///
+/// 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_stats/math
+///
+/// pub fn example() {
+/// // Invalid input gives an error
+/// // Error on: n = -1 < 0
+/// math.combination(-1, 1)
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// math.combination(4, 0)
+/// |> should.equal(Ok(1))
+///
+/// math.combination(4, 4)
+/// |> should.equal(Ok(1))
+///
+/// math.combination(4, 2)
+/// |> should.equal(Ok(6))
+/// }
+///
+///
+///
+///
+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
+ }
+ }
+ }
+ }
+}
+
+///
+///
+/// A combinatorial function for computing the total number of combinations of $$n$$
+/// elements, that is $$n!$$.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/math
+///
+/// pub fn example() {
+/// // Invalid input gives an error
+/// math.factorial(-1)
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// math.factorial(0)
+/// |> should.equal(Ok(1))
+/// math.factorial(1)
+/// |> should.equal(Ok(1))
+/// math.factorial(2)
+/// |> should.equal(Ok(2))
+/// math.factorial(3)
+/// |> should.equal(Ok(6))
+/// math.factorial(4)
+/// |> should.equal(Ok(24))
+/// }
+///
+///
+///
+///
+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
+ }
+ }
+}
+
+///
+///
+/// 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_stats/math
+///
+/// pub fn example() {
+/// // Invalid input gives an error
+/// // Error on: n = -1 < 0
+/// math.permutation(-1, 1)
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// math.permutation(4, 0)
+/// |> should.equal(Ok(1))
+///
+/// math.permutation(4, 4)
+/// |> should.equal(Ok(1))
+///
+/// math.permutation(4, 2)
+/// |> should.equal(Ok(12))
+/// }
+///
+///
+///
+///
+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 -> {
+ assert Ok(v1) = factorial(n)
+ assert Ok(v2) = factorial(n - k)
+ v1 / v2
+ |> Ok
+ }
+ }
+ }
+ }
+}
+
+///
+///
+/// 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_stats/math
+///
+/// pub fn example() {
+/// math.absdiff(-10.0, 10.0)
+/// |> should.equal(20.0)
+///
+/// math.absdiff(0.0, -2.0)
+/// |> should.equal(2.0)
+/// }
+///
+///
+///
+///
+pub fn absdiff(a: Int, b: Int) -> Int {
+ a - b
+ |> int.absolute_value()
+}
diff --git a/src/gleam_community/maths/int_list.gleam b/src/gleam_community/maths/int_list.gleam
new file mode 100644
index 0000000..f8eb5bc
--- /dev/null
+++ b/src/gleam_community/maths/int_list.gleam
@@ -0,0 +1,328 @@
+////
+////
+////
+////
+////
+////
+//// A module containing several different kinds of mathematical functions
+//// applying to lists of real numbers.
+////
+//// Function naming has been adopted from
C mathematical function .
+////
+//// ---
+////
+//// * **Miscellaneous functions**
+//// * [`allclose`](#allclose)
+//// * [`amax`](#amax)
+//// * [`amin`](#amin)
+//// * [`argmax`](#argmax)
+//// * [`argmin`](#argmin)
+//// * [`allclose`](#allclose)
+//// * [`trim`](#trim)
+
+import gleam/list
+import gleam/int
+import gleam/float
+import gleam/pair
+import gleam_community/maths/int as intx
+
+///
+///
+/// Returns the indices of the minimum values in a list.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// pub fn example () {
+/// // An empty lists returns an error
+/// []
+/// |> stats.argmin()
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// [4., 4., 3., 2., 1.]
+/// |> stats.argmin()
+/// |> should.equal(Ok([4]))
+/// }
+///
+///
+///
+///
+pub fn argmin(arr: List(Float)) -> Result(List(Int), String) {
+ case arr {
+ [] ->
+ "Invalid input argument: The list is empty."
+ |> Error
+ _ -> {
+ assert Ok(min) =
+ arr
+ |> amin()
+ arr
+ |> list.index_map(fn(index: Int, a: Float) -> Int {
+ case a -. min {
+ 0. -> index
+ _ -> -1
+ }
+ })
+ |> list.filter(fn(index: Int) -> Bool {
+ case index {
+ -1 -> False
+ _ -> True
+ }
+ })
+ |> Ok
+ }
+ }
+}
+
+///
+///
+/// Returns the indices of the maximum values in a list.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// pub fn example () {
+/// // An empty lists returns an error
+/// []
+/// |> stats.argmax()
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// [4., 4., 3., 2., 1.]
+/// |> stats.argmax()
+/// |> should.equal(Ok([0, 1]))
+/// }
+///
+///
+///
+///
+pub fn argmax(arr: List(Float)) -> Result(List(Int), String) {
+ case arr {
+ [] ->
+ "Invalid input argument: The list is empty."
+ |> Error
+ _ -> {
+ assert Ok(max) =
+ arr
+ |> amax()
+ arr
+ |> list.index_map(fn(index: Int, a: Float) -> Int {
+ case a -. max {
+ 0. -> index
+ _ -> -1
+ }
+ })
+ |> list.filter(fn(index: Int) -> Bool {
+ case index {
+ -1 -> False
+ _ -> True
+ }
+ })
+ |> Ok
+ }
+ }
+}
+
+///
+///
+/// Returns the maximum value of a list.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// pub fn example () {
+/// // An empty lists returns an error
+/// []
+/// |> stats.amax()
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// [4., 4., 3., 2., 1.]
+/// |> stats.amax()
+/// |> should.equal(Ok(4.))
+/// }
+///
+///
+///
+///
+pub fn amax(arr: List(Float)) -> Result(Float, String) {
+ case arr {
+ [] ->
+ "Invalid input argument: The list is empty."
+ |> Error
+ _ -> {
+ assert Ok(val0) = list.at(arr, 0)
+ arr
+ |> list.fold(
+ val0,
+ fn(acc: Float, a: Float) {
+ case a >. acc {
+ True -> a
+ False -> acc
+ }
+ },
+ )
+ |> Ok
+ }
+ }
+}
+
+///
+///
+/// Returns the minimum value of a list.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// pub fn example () {
+/// // An empty lists returns an error
+/// []
+/// |> stats.amin()
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// [4., 4., 3., 2., 1.]
+/// |> stats.amin()
+/// |> should.equal(Ok(1.))
+/// }
+///
+///
+///
+///
+pub fn amin(arr: List(Float)) -> Result(Float, String) {
+ case arr {
+ [] ->
+ "Invalid input argument: The list is empty."
+ |> Error
+ _ -> {
+ assert Ok(val0) = list.at(arr, 0)
+ arr
+ |> list.fold(
+ val0,
+ fn(acc: Float, a: Float) {
+ case a <. acc {
+ True -> a
+ False -> acc
+ }
+ },
+ )
+ |> Ok
+ }
+ }
+}
+
+///
+///
+/// Returns the minimum value of a list.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// pub fn example () {
+/// // An empty lists returns an error
+/// []
+/// |> stats.amin()
+/// |> should.be_error()
+///
+/// // Valid input returns a result
+/// [4., 4., 3., 2., 1.]
+/// |> stats.amin()
+/// |> should.equal(Ok(1.))
+/// }
+///
+///
+///
+///
+pub fn extrema(arr: List(Float)) -> Result(#(Float, Float), String) {
+ todo
+ // case arr {
+ // [] ->
+ // "Invalid input argument: The list is empty."
+ // |> Error
+ // _ -> {
+ // assert Ok(val0) = list.at(arr, 0)
+ // arr
+ // |> list.fold(
+ // val0,
+ // fn(acc: Float, a: Float) {
+ // case a <. acc {
+ // True -> a
+ // False -> acc
+ // }
+ // },
+ // )
+ // |> Ok
+ // }
+ // }
+}
diff --git a/src/gleam_community/maths/list.gleam b/src/gleam_community/maths/list.gleam
new file mode 100644
index 0000000..ba0d74a
--- /dev/null
+++ b/src/gleam_community/maths/list.gleam
@@ -0,0 +1,87 @@
+////
+////
+////
+////
+////
+////
+//// A module containing several different kinds of mathematical functions
+//// applying to lists of real numbers.
+////
+//// Function naming has been adopted from
C mathematical function .
+////
+//// ---
+////
+//// * **Miscellaneous functions**
+//// * [`trim`](#trim)
+
+import gleam/list
+import gleam/int
+import gleam/float
+
+///
+///
+/// Trim a list to a certain size given min/max indices. The min/max indices
+/// are inclusive.
+///
+///
+/// Example:
+///
+/// import gleeunit/should
+/// import gleam_stats/stats
+///
+/// pub fn example () {
+/// // An empty lists returns an error
+/// []
+/// |> stats.trim(0, 0)
+/// |> should.be_error()
+///
+/// // Trim the list to only the middle part of list
+/// [1., 2., 3., 4., 5., 6.]
+/// |> stats.trim(1, 4)
+/// |> should.equal(Ok([2., 3., 4., 5.]))
+/// }
+///
+///
+///
+///
+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
+ }
+ }
+}
diff --git a/temp.gleam b/temp.gleam
new file mode 100644
index 0000000..aabf936
--- /dev/null
+++ b/temp.gleam
@@ -0,0 +1,410 @@
+//// Small examples ALSO used in the docs...
+
+import gleam/int
+import gleam/list
+import gleam/pair
+import gleam_stats/stats
+import gleeunit
+import gleeunit/should
+
+pub fn main() {
+ gleeunit.main()
+}
+
+pub fn example_sum_test() {
+ // An empty list returns an error
+ []
+ |> stats.sum()
+ |> should.equal(0.)
+
+ // Valid input returns a result
+ [1., 2., 3.]
+ |> stats.sum()
+ |> should.equal(6.)
+}
+
+pub fn example_mean_test() {
+ // 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.))
+}
+
+pub fn example_median_test() {
+ // 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))
+}
+
+pub fn example_hmean_test() {
+ // An empty list returns an error
+ []
+ |> stats.hmean()
+ |> should.be_error()
+
+ // List with negative numbers returns an error
+ [-1., -3., -6.]
+ |> stats.hmean()
+ |> should.be_error()
+
+ // Valid input returns a result
+ [1., 3., 6.]
+ |> stats.hmean()
+ |> should.equal(Ok(2.))
+}
+
+pub fn example_gmean_test() {
+ // An empty list returns an error
+ []
+ |> stats.gmean()
+ |> should.be_error()
+ // List with negative numbers returns an error
+ [-1., -3., -6.]
+ |> stats.gmean()
+ |> should.be_error()
+ // Valid input returns a result
+ [1., 3., 9.]
+ |> stats.gmean()
+ |> should.equal(Ok(3.))
+}
+
+pub fn example_var_test() {
+ // 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.))
+}
+
+pub fn example_std_test() {
+ // 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.))
+}
+
+pub fn example_moment_test() {
+ // An empty list returns an error
+ []
+ |> stats.moment(0)
+ |> should.be_error()
+
+ // 0th moment about the mean is 1. per definition
+ [0., 1., 2., 3., 4.]
+ |> stats.moment(0)
+ |> should.equal(Ok(1.))
+
+ // 1st moment about the mean is 0. per definition
+ [0., 1., 2., 3., 4.]
+ |> stats.moment(1)
+ |> should.equal(Ok(0.))
+
+ // 2nd moment about the mean
+ [0., 1., 2., 3., 4.]
+ |> stats.moment(2)
+ |> should.equal(Ok(2.))
+}
+
+pub fn example_skewness_test() {
+ // An empty list returns an error
+ []
+ |> stats.skewness()
+ |> should.be_error()
+
+ // No skewness
+ // -> Zero skewness
+ [1., 2., 3., 4.]
+ |> stats.skewness()
+ |> should.equal(Ok(0.))
+
+ // Right-skewed distribution
+ // -> Positive skewness
+ [1., 1., 1., 2.]
+ |> stats.skewness()
+ |> fn(x: Result(Float, String)) -> Bool {
+ case x {
+ Ok(x) -> x >. 0.
+ _ -> False
+ }
+ }
+ |> should.be_true()
+}
+
+pub fn example_kurtosis_test() {
+ // An empty list returns an error
+ []
+ |> stats.skewness()
+ |> should.be_error()
+
+ // No tail
+ // -> Fisher's definition gives kurtosis -3
+ [1., 1., 1., 1.]
+ |> stats.kurtosis()
+ |> should.equal(Ok(-3.))
+
+ // Distribution with a tail
+ // -> Higher kurtosis
+ [1., 1., 1., 2.]
+ |> stats.kurtosis()
+ |> fn(x: Result(Float, String)) -> Bool {
+ case x {
+ Ok(x) -> x >. -3.
+ _ -> False
+ }
+ }
+ |> should.be_true()
+}
+
+pub fn example_zscore_test() {
+ // An empty list returns an error
+ []
+ // Use degrees of freedom = 1
+ |> stats.zscore(1)
+ |> should.be_error()
+
+ [1., 2., 3.]
+ // Use degrees of freedom = 1
+ |> stats.zscore(1)
+ |> should.equal(Ok([-1., 0., 1.]))
+}
+
+pub fn example_percentile_test() {
+ // An empty list returns an error
+ []
+ |> stats.percentile(40)
+ |> should.be_error()
+
+ // Calculate 40th percentile
+ [15., 20., 35., 40., 50.]
+ |> stats.percentile(40)
+ |> should.equal(Ok(29.))
+}
+
+pub fn example_iqr_test() {
+ // An empty list returns an error
+ []
+ |> stats.iqr()
+ |> should.be_error()
+
+ // Valid input returns a result
+ [1., 2., 3., 4., 5.]
+ |> stats.iqr()
+ |> should.equal(Ok(3.))
+}
+
+pub fn example_freedman_diaconis_rule_test() {
+ // An empty list returns an error
+ []
+ |> stats.freedman_diaconis_rule()
+ |> should.be_error()
+
+ // Calculate histogram bin widths
+ list.range(0, 1000)
+ |> list.map(fn(x: Int) -> Float { int.to_float(x) })
+ |> stats.freedman_diaconis_rule()
+ |> should.equal(Ok(10.))
+}
+
+pub fn example_range_test() {
+ // Create a range
+ let range = stats.Range(0., 1.)
+ // Retrieve min and max values
+ let stats.Range(min, max) = range
+ min
+ |> should.equal(0.)
+ max
+ |> should.equal(1.)
+}
+
+pub fn example_bin_test() {
+ // Create a bin
+ let bin: stats.Bin = #(stats.Range(0., 1.), 999)
+ // Retrieve min and max values
+ let stats.Range(min, max) = pair.first(bin)
+ min
+ |> should.equal(0.)
+ max
+ |> should.equal(1.)
+ // Retrieve count
+ let count = pair.second(bin)
+ count
+ |> should.equal(999)
+}
+
+pub fn example_histogram_test() {
+ // An empty lists returns an error
+ []
+ |> stats.histogram(1.)
+ |> should.be_error()
+ // Create the bins of a histogram given a list of values
+ list.range(0, 100)
+ |> list.map(fn(x: Int) -> Float { int.to_float(x) })
+ // Below 25. is the bin width
+ // The Freedman-Diaconis’s Rule can be used to determine a decent value
+ |> stats.histogram(25.)
+ |> should.equal(Ok([
+ #(stats.Range(0., 25.), 25),
+ #(stats.Range(25., 50.), 25),
+ #(stats.Range(50., 75.), 25),
+ #(stats.Range(75., 100.), 25),
+ ]))
+}
+
+pub fn example_correlation_test() {
+ // An empty lists returns an error
+ stats.correlation([], [])
+ |> should.be_error()
+
+ // Lists with fewer than 2 elements return an error
+ stats.correlation([1.0], [1.0])
+ |> should.be_error()
+
+ // Lists of uneqal length return an error
+ stats.correlation([1.0, 2.0, 3.0], [1.0, 2.0])
+ |> should.be_error()
+
+ // Perfect positive correlation
+ let xarr0: List(Float) =
+ list.range(0, 100)
+ |> list.map(fn(x: Int) -> Float { int.to_float(x) })
+ let yarr0: List(Float) =
+ list.range(0, 100)
+ |> list.map(fn(x: Int) -> Float { int.to_float(x) })
+ stats.correlation(xarr0, yarr0)
+ |> should.equal(Ok(1.))
+
+ // Perfect negative correlation
+ let xarr0: List(Float) =
+ list.range(0, 100)
+ |> list.map(fn(x: Int) -> Float { -1. *. int.to_float(x) })
+ let yarr0: List(Float) =
+ list.range(0, 100)
+ |> list.map(fn(x: Int) -> Float { int.to_float(x) })
+ stats.correlation(xarr0, yarr0)
+ |> should.equal(Ok(-1.))
+}
+
+pub fn example_trim_test() {
+ // An empty lists returns an error
+ []
+ |> stats.trim(0, 0)
+ |> should.be_error()
+
+ // Trim the list to only the middle part of list
+ [1., 2., 3., 4., 5., 6.]
+ |> stats.trim(1, 4)
+ |> should.equal(Ok([2., 3., 4., 5.]))
+}
+
+pub fn example_isclose_test() {
+ 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
+ stats.isclose(val, ref_val, rtol, atol)
+ |> should.be_true()
+}
+
+pub fn example_allclose_test() {
+ 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
+ stats.allclose(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 example_amax_test() {
+ // An empty lists returns an error
+ []
+ |> stats.amax()
+ |> should.be_error()
+
+ // Valid input returns a result
+ [4., 4., 3., 2., 1.]
+ |> stats.amax()
+ |> should.equal(Ok(4.))
+}
+
+pub fn example_amin_test() {
+ // An empty lists returns an error
+ []
+ |> stats.amin()
+ |> should.be_error()
+
+ // Valid input returns a result
+ [4., 4., 3., 2., 1.]
+ |> stats.amin()
+ |> should.equal(Ok(1.))
+}
+
+pub fn example_argmax_test() {
+ // An empty lists returns an error
+ []
+ |> stats.argmax()
+ |> should.be_error()
+
+ // Valid input returns a result
+ [4., 4., 3., 2., 1.]
+ |> stats.argmax()
+ |> should.equal(Ok([0, 1]))
+}
+
+pub fn example_argmin_test() {
+ // An empty lists returns an error
+ []
+ |> stats.argmin()
+ |> should.be_error()
+
+ // Valid input returns a result
+ [4., 4., 3., 2., 1.]
+ |> stats.argmin()
+ |> should.equal(Ok([4]))
+}
diff --git a/test/eunit_progress.erl b/test/eunit_progress.erl
new file mode 100644
index 0000000..faef6b5
--- /dev/null
+++ b/test/eunit_progress.erl
@@ -0,0 +1,592 @@
+%% eunit_formatters https://github.com/seancribbs/eunit_formatters
+%% Changes made to the original code:
+%% - Embedded binomial_heap.erl file contents into current file.
+%% - ignore warnings for heap implementation to keep complete implementation.
+%% - removed "namespaced_dicts" dependant preprocessor directive,
+%% as it does not apply for our project, we just assume OTP version >= 17.
+%% This is because the previous verison uses rebar, and we won't do that.
+
+%% Copyright 2014 Sean Cribbs
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+
+
+%% @doc A listener/reporter for eunit that prints '.' for each
+%% success, 'F' for each failure, and 'E' for each error. It can also
+%% optionally summarize the failures at the end.
+-compile({nowarn_unused_function, [insert/2, to_list/1, to_list/2, size/1]}).
+-module(eunit_progress).
+-behaviour(eunit_listener).
+-define(NOTEST, true).
+-include_lib("eunit/include/eunit.hrl").
+
+-define(RED, "\e[0;31m").
+-define(GREEN, "\e[0;32m").
+-define(YELLOW, "\e[0;33m").
+-define(WHITE, "\e[0;37m").
+-define(CYAN, "\e[0;36m").
+-define(RESET, "\e[0m").
+
+-record(node,{
+ rank = 0 :: non_neg_integer(),
+ key :: term(),
+ value :: term(),
+ children = new() :: binomial_heap()
+ }).
+
+-export_type([binomial_heap/0, heap_node/0]).
+-type binomial_heap() :: [ heap_node() ].
+-type heap_node() :: #node{}.
+
+%% eunit_listener callbacks
+-export([
+ init/1,
+ handle_begin/3,
+ handle_end/3,
+ handle_cancel/3,
+ terminate/2,
+ start/0,
+ start/1
+ ]).
+
+%% -- binomial_heap.erl content start --
+
+-record(state, {
+ status = dict:new() :: euf_dict(),
+ failures = [] :: [[pos_integer()]],
+ skips = [] :: [[pos_integer()]],
+ timings = new() :: binomial_heap(),
+ colored = true :: boolean(),
+ profile = false :: boolean()
+ }).
+
+-type euf_dict() :: dict:dict().
+
+-spec new() -> binomial_heap().
+new() ->
+ [].
+
+% Inserts a new pair into the heap (or creates a new heap)
+-spec insert(term(), term()) -> binomial_heap().
+insert(Key,Value) ->
+ insert(Key,Value,[]).
+
+-spec insert(term(), term(), binomial_heap()) -> binomial_heap().
+insert(Key,Value,Forest) ->
+ insTree(#node{key=Key,value=Value},Forest).
+
+% Merges two heaps
+-spec merge(binomial_heap(), binomial_heap()) -> binomial_heap().
+merge(TS1,[]) when is_list(TS1) -> TS1;
+merge([],TS2) when is_list(TS2) -> TS2;
+merge([#node{rank=R1}=T1|TS1]=F1,[#node{rank=R2}=T2|TS2]=F2) ->
+ if
+ R1 < R2 ->
+ [T1 | merge(TS1,F2)];
+ R2 < R1 ->
+ [T2 | merge(F1, TS2)];
+ true ->
+ insTree(link(T1,T2),merge(TS1,TS2))
+ end.
+
+% Deletes the top entry from the heap and returns it
+-spec delete(binomial_heap()) -> {{term(), term()}, binomial_heap()}.
+delete(TS) ->
+ {#node{key=Key,value=Value,children=TS1},TS2} = getMin(TS),
+ {{Key,Value},merge(lists:reverse(TS1),TS2)}.
+
+% Turns the heap into list in heap order
+-spec to_list(binomial_heap()) -> [{term(), term()}].
+to_list([]) -> [];
+to_list(List) when is_list(List) ->
+ to_list([],List).
+to_list(Acc, []) ->
+ lists:reverse(Acc);
+to_list(Acc,Forest) ->
+ {Next, Trees} = delete(Forest),
+ to_list([Next|Acc], Trees).
+
+% Take N elements from the top of the heap
+-spec take(non_neg_integer(), binomial_heap()) -> [{term(), term()}].
+take(N,Trees) when is_integer(N), is_list(Trees) ->
+ take(N,Trees,[]).
+take(0,_Trees,Acc) ->
+ lists:reverse(Acc);
+take(_N,[],Acc)->
+ lists:reverse(Acc);
+take(N,Trees,Acc) ->
+ {Top,T2} = delete(Trees),
+ take(N-1,T2,[Top|Acc]).
+
+% Get an estimate of the size based on the binomial property
+-spec size(binomial_heap()) -> non_neg_integer().
+size(Forest) ->
+ erlang:trunc(lists:sum([math:pow(2,R) || #node{rank=R} <- Forest])).
+
+%% Private API
+-spec link(heap_node(), heap_node()) -> heap_node().
+link(#node{rank=R,key=X1,children=C1}=T1,#node{key=X2,children=C2}=T2) ->
+ case X1 < X2 of
+ true ->
+ T1#node{rank=R+1,children=[T2|C1]};
+ _ ->
+ T2#node{rank=R+1,children=[T1|C2]}
+ end.
+
+insTree(Tree, []) ->
+ [Tree];
+insTree(#node{rank=R1}=T1, [#node{rank=R2}=T2|Rest] = TS) ->
+ case R1 < R2 of
+ true ->
+ [T1|TS];
+ _ ->
+ insTree(link(T1,T2),Rest)
+ end.
+
+getMin([T]) ->
+ {T,[]};
+getMin([#node{key=K} = T|TS]) ->
+ {#node{key=K1} = T1,TS1} = getMin(TS),
+ case K < K1 of
+ true -> {T,TS};
+ _ -> {T1,[T|TS1]}
+ end.
+
+%% -- binomial_heap.erl content end --
+
+%% Startup
+start() ->
+ start([]).
+
+start(Options) ->
+ eunit_listener:start(?MODULE, Options).
+
+%%------------------------------------------
+%% eunit_listener callbacks
+%%------------------------------------------
+init(Options) ->
+ #state{colored=proplists:get_bool(colored, Options),
+ profile=proplists:get_bool(profile, Options)}.
+
+handle_begin(group, Data, St) ->
+ GID = proplists:get_value(id, Data),
+ Dict = St#state.status,
+ St#state{status=dict:store(GID, orddict:from_list([{type, group}|Data]), Dict)};
+handle_begin(test, Data, St) ->
+ TID = proplists:get_value(id, Data),
+ Dict = St#state.status,
+ St#state{status=dict:store(TID, orddict:from_list([{type, test}|Data]), Dict)}.
+
+handle_end(group, Data, St) ->
+ St#state{status=merge_on_end(Data, St#state.status)};
+handle_end(test, Data, St) ->
+ NewStatus = merge_on_end(Data, St#state.status),
+ St1 = print_progress(Data, St),
+ St2 = record_timing(Data, St1),
+ St2#state{status=NewStatus}.
+
+handle_cancel(_, Data, #state{status=Status, skips=Skips}=St) ->
+ Status1 = merge_on_end(Data, Status),
+ ID = proplists:get_value(id, Data),
+ St#state{status=Status1, skips=[ID|Skips]}.
+
+terminate({ok, Data}, St) ->
+ print_failures(St),
+ print_pending(St),
+ print_profile(St),
+ print_timing(St),
+ print_results(Data, St);
+terminate({error, Reason}, St) ->
+ io:nl(), io:nl(),
+ print_colored(io_lib:format("Eunit failed: ~25p~n", [Reason]), ?RED, St),
+ sync_end(error).
+
+sync_end(Result) ->
+ receive
+ {stop, Reference, ReplyTo} ->
+ ReplyTo ! {result, Reference, Result},
+ ok
+ end.
+
+%%------------------------------------------
+%% Print and collect information during run
+%%------------------------------------------
+print_progress(Data, St) ->
+ TID = proplists:get_value(id, Data),
+ case proplists:get_value(status, Data) of
+ ok ->
+ print_progress_success(St),
+ St;
+ {skipped, _Reason} ->
+ print_progress_skipped(St),
+ St#state{skips=[TID|St#state.skips]};
+ {error, Exception} ->
+ print_progress_failed(Exception, St),
+ St#state{failures=[TID|St#state.failures]}
+ end.
+
+record_timing(Data, State=#state{timings=T, profile=true}) ->
+ TID = proplists:get_value(id, Data),
+ case lists:keyfind(time, 1, Data) of
+ {time, Int} ->
+ %% It's a min-heap, so we insert negative numbers instead
+ %% of the actuals and normalize when we report on them.
+ T1 = insert(-Int, TID, T),
+ State#state{timings=T1};
+ false ->
+ State
+ end;
+record_timing(_Data, State) ->
+ State.
+
+print_progress_success(St) ->
+ print_colored(".", ?GREEN, St).
+
+print_progress_skipped(St) ->
+ print_colored("*", ?YELLOW, St).
+
+print_progress_failed(_Exc, St) ->
+ print_colored("F", ?RED, St).
+
+merge_on_end(Data, Dict) ->
+ ID = proplists:get_value(id, Data),
+ dict:update(ID,
+ fun(Old) ->
+ orddict:merge(fun merge_data/3, Old, orddict:from_list(Data))
+ end, Dict).
+
+merge_data(_K, undefined, X) -> X;
+merge_data(_K, X, undefined) -> X;
+merge_data(_K, _, X) -> X.
+
+%%------------------------------------------
+%% Print information at end of run
+%%------------------------------------------
+print_failures(#state{failures=[]}) ->
+ ok;
+print_failures(#state{failures=Fails}=State) ->
+ io:nl(),
+ io:fwrite("Failures:~n",[]),
+ lists:foldr(print_failure_fun(State), 1, Fails),
+ ok.
+
+print_failure_fun(#state{status=Status}=State) ->
+ fun(Key, Count) ->
+ TestData = dict:fetch(Key, Status),
+ TestId = format_test_identifier(TestData),
+ io:fwrite("~n ~p) ~ts~n", [Count, TestId]),
+ print_failure_reason(proplists:get_value(status, TestData),
+ proplists:get_value(output, TestData),
+ State),
+ io:nl(),
+ Count + 1
+ end.
+
+print_failure_reason({skipped, Reason}, _Output, State) ->
+ print_colored(io_lib:format(" ~ts~n", [format_pending_reason(Reason)]),
+ ?RED, State);
+print_failure_reason({error, {_Class, Term, Stack}}, Output, State) when
+ is_tuple(Term), tuple_size(Term) == 2, is_list(element(2, Term)) ->
+ print_assertion_failure(Term, Stack, Output, State),
+ print_failure_output(5, Output, State);
+print_failure_reason({error, {error, Error, Stack}}, Output, State) when is_list(Stack) ->
+ print_colored(indent(5, "Failure: ~p~n", [Error]), ?RED, State),
+ print_stack(Stack, State),
+ print_failure_output(5, Output, State);
+print_failure_reason({error, Reason}, Output, State) ->
+ print_colored(indent(5, "Failure: ~p~n", [Reason]), ?RED, State),
+ print_failure_output(5, Output, State).
+
+print_stack(Stack, State) ->
+ print_colored(indent(5, "Stacktrace:~n", []), ?CYAN, State),
+ print_stackframes(Stack, State).
+print_stackframes([{eunit_test, _, _, _} | Stack], State) ->
+ print_stackframes(Stack, State);
+print_stackframes([{eunit_proc, _, _, _} | Stack], State) ->
+ print_stackframes(Stack, State);
+print_stackframes([{Module, Function, _Arity, _Location} | Stack], State) ->
+ print_colored(indent(7, "~p.~p~n", [Module, Function]), ?CYAN, State),
+ print_stackframes(Stack, State);
+print_stackframes([], _State) ->
+ ok.
+
+
+print_failure_output(_, <<>>, _) -> ok;
+print_failure_output(_, undefined, _) -> ok;
+print_failure_output(Indent, Output, State) ->
+ print_colored(indent(Indent, "Output: ~ts", [Output]), ?CYAN, State).
+
+print_assertion_failure({Type, Props}, Stack, Output, State) ->
+ FailureDesc = format_assertion_failure(Type, Props, 5),
+ {M,F,A,Loc} = lists:last(Stack),
+ LocationText = io_lib:format(" %% ~ts:~p:in `~ts`", [proplists:get_value(file, Loc),
+ proplists:get_value(line, Loc),
+ format_function_name(M,F,A)]),
+ print_colored(FailureDesc, ?RED, State),
+ io:nl(),
+ print_colored(LocationText, ?CYAN, State),
+ io:nl(),
+ print_failure_output(5, Output, State),
+ io:nl().
+
+print_pending(#state{skips=[]}) ->
+ ok;
+print_pending(#state{status=Status, skips=Skips}=State) ->
+ io:nl(),
+ io:fwrite("Pending:~n", []),
+ lists:foreach(fun(ID) ->
+ Info = dict:fetch(ID, Status),
+ case proplists:get_value(reason, Info) of
+ undefined ->
+ ok;
+ Reason ->
+ print_pending_reason(Reason, Info, State)
+ end
+ end, lists:reverse(Skips)),
+ io:nl().
+
+print_pending_reason(Reason0, Data, State) ->
+ Text = case proplists:get_value(type, Data) of
+ group ->
+ io_lib:format(" ~ts~n", [proplists:get_value(desc, Data)]);
+ test ->
+ io_lib:format(" ~ts~n", [format_test_identifier(Data)])
+ end,
+ Reason = io_lib:format(" %% ~ts~n", [format_pending_reason(Reason0)]),
+ print_colored(Text, ?YELLOW, State),
+ print_colored(Reason, ?CYAN, State).
+
+print_profile(#state{timings=T, status=Status, profile=true}=State) ->
+ TopN = take(10, T),
+ TopNTime = abs(lists:sum([ Time || {Time, _} <- TopN ])),
+ TLG = dict:fetch([], Status),
+ TotalTime = proplists:get_value(time, TLG),
+ if TotalTime =/= undefined andalso TotalTime > 0 andalso TopN =/= [] ->
+ TopNPct = (TopNTime / TotalTime) * 100,
+ io:nl(), io:nl(),
+ io:fwrite("Top ~p slowest tests (~ts, ~.1f% of total time):", [length(TopN), format_time(TopNTime), TopNPct]),
+ lists:foreach(print_timing_fun(State), TopN),
+ io:nl();
+ true -> ok
+ end;
+print_profile(#state{profile=false}) ->
+ ok.
+
+print_timing(#state{status=Status}) ->
+ TLG = dict:fetch([], Status),
+ Time = proplists:get_value(time, TLG),
+ io:nl(),
+ io:fwrite("Finished in ~ts~n", [format_time(Time)]),
+ ok.
+
+print_results(Data, State) ->
+ Pass = proplists:get_value(pass, Data, 0),
+ Fail = proplists:get_value(fail, Data, 0),
+ Skip = proplists:get_value(skip, Data, 0),
+ Cancel = proplists:get_value(cancel, Data, 0),
+ Total = Pass + Fail + Skip + Cancel,
+ {Color, Result} = if Fail > 0 -> {?RED, error};
+ Skip > 0; Cancel > 0 -> {?YELLOW, error};
+ Pass =:= 0 -> {?YELLOW, ok};
+ true -> {?GREEN, ok}
+ end,
+ print_results(Color, Total, Fail, Skip, Cancel, State),
+ sync_end(Result).
+
+print_results(Color, 0, _, _, _, State) ->
+ print_colored(Color, "0 tests\n", State);
+print_results(Color, Total, Fail, Skip, Cancel, State) ->
+ SkipText = format_optional_result(Skip, "skipped"),
+ CancelText = format_optional_result(Cancel, "cancelled"),
+ Text = io_lib:format("~p tests, ~p failures~ts~ts~n", [Total, Fail, SkipText, CancelText]),
+ print_colored(Text, Color, State).
+
+print_timing_fun(#state{status=Status}=State) ->
+ fun({Time, Key}) ->
+ TestData = dict:fetch(Key, Status),
+ TestId = format_test_identifier(TestData),
+ io:nl(),
+ io:fwrite(" ~ts~n", [TestId]),
+ print_colored([" "|format_time(abs(Time))], ?CYAN, State)
+ end.
+
+%%------------------------------------------
+%% Print to the console with the given color
+%% if enabled.
+%%------------------------------------------
+print_colored(Text, Color, #state{colored=true}) ->
+ io:fwrite("~s~ts~s", [Color, Text, ?RESET]);
+print_colored(Text, _Color, #state{colored=false}) ->
+ io:fwrite("~ts", [Text]).
+
+%%------------------------------------------
+%% Generic data formatters
+%%------------------------------------------
+format_function_name(M, F, A) ->
+ io_lib:format("~ts:~ts/~p", [M, F, A]).
+
+format_optional_result(0, _) ->
+ [];
+format_optional_result(Count, Text) ->
+ io_lib:format(", ~p ~ts", [Count, Text]).
+
+format_test_identifier(Data) ->
+ {Mod, Fun, Arity} = proplists:get_value(source, Data),
+ Line = case proplists:get_value(line, Data) of
+ 0 -> "";
+ L -> io_lib:format(":~p", [L])
+ end,
+ Desc = case proplists:get_value(desc, Data) of
+ undefined -> "";
+ DescText -> io_lib:format(": ~ts", [DescText])
+ end,
+ io_lib:format("~ts~ts~ts", [format_function_name(Mod, Fun, Arity), Line, Desc]).
+
+format_time(undefined) ->
+ "? seconds";
+format_time(Time) ->
+ io_lib:format("~.3f seconds", [Time / 1000]).
+
+format_pending_reason({module_not_found, M}) ->
+ io_lib:format("Module '~ts' missing", [M]);
+format_pending_reason({no_such_function, {M,F,A}}) ->
+ io_lib:format("Function ~ts undefined", [format_function_name(M,F,A)]);
+format_pending_reason({exit, Reason}) ->
+ io_lib:format("Related process exited with reason: ~p", [Reason]);
+format_pending_reason(Reason) ->
+ io_lib:format("Unknown error: ~p", [Reason]).
+
+%% @doc Formats all the known eunit assertions, you're on your own if
+%% you make an assertion yourself.
+format_assertion_failure(Type, Props, I) when Type =:= assertion_failed
+ ; Type =:= assert ->
+ Keys = proplists:get_keys(Props),
+ HasEUnitProps = ([expression, value] -- Keys) =:= [],
+ HasHamcrestProps = ([expected, actual, matcher] -- Keys) =:= [],
+ if
+ HasEUnitProps ->
+ [indent(I, "Failure: ?assert(~ts)~n", [proplists:get_value(expression, Props)]),
+ indent(I, " expected: true~n", []),
+ case proplists:get_value(value, Props) of
+ false ->
+ indent(I, " got: false", []);
+ {not_a_boolean, V} ->
+ indent(I, " got: ~p", [V])
+ end];
+ HasHamcrestProps ->
+ [indent(I, "Failure: ?assertThat(~p)~n", [proplists:get_value(matcher, Props)]),
+ indent(I, " expected: ~p~n", [proplists:get_value(expected, Props)]),
+ indent(I, " got: ~p", [proplists:get_value(actual, Props)])];
+ true ->
+ [indent(I, "Failure: unknown assert: ~p", [Props])]
+ end;
+
+format_assertion_failure(Type, Props, I) when Type =:= assertMatch_failed
+ ; Type =:= assertMatch ->
+ Expr = proplists:get_value(expression, Props),
+ Pattern = proplists:get_value(pattern, Props),
+ Value = proplists:get_value(value, Props),
+ [indent(I, "Failure: ?assertMatch(~ts, ~ts)~n", [Pattern, Expr]),
+ indent(I, " expected: = ~ts~n", [Pattern]),
+ indent(I, " got: ~p", [Value])];
+
+format_assertion_failure(Type, Props, I) when Type =:= assertNotMatch_failed
+ ; Type =:= assertNotMatch ->
+ Expr = proplists:get_value(expression, Props),
+ Pattern = proplists:get_value(pattern, Props),
+ Value = proplists:get_value(value, Props),
+ [indent(I, "Failure: ?assertNotMatch(~ts, ~ts)~n", [Pattern, Expr]),
+ indent(I, " expected not: = ~ts~n", [Pattern]),
+ indent(I, " got: ~p", [Value])];
+
+format_assertion_failure(Type, Props, I) when Type =:= assertEqual_failed
+ ; Type =:= assertEqual ->
+ Expr = proplists:get_value(expression, Props),
+ Expected = proplists:get_value(expected, Props),
+ Value = proplists:get_value(value, Props),
+ [indent(I, "Failure: ?assertEqual(~w, ~ts)~n", [Expected,
+ Expr]),
+ indent(I, " expected: ~p~n", [Expected]),
+ indent(I, " got: ~p", [Value])];
+
+format_assertion_failure(Type, Props, I) when Type =:= assertNotEqual_failed
+ ; Type =:= assertNotEqual ->
+ Expr = proplists:get_value(expression, Props),
+ Value = proplists:get_value(value, Props),
+ [indent(I, "Failure: ?assertNotEqual(~p, ~ts)~n",
+ [Value, Expr]),
+ indent(I, " expected not: == ~p~n", [Value]),
+ indent(I, " got: ~p", [Value])];
+
+format_assertion_failure(Type, Props, I) when Type =:= assertException_failed
+ ; Type =:= assertException ->
+ Expr = proplists:get_value(expression, Props),
+ Pattern = proplists:get_value(pattern, Props),
+ {Class, Term} = extract_exception_pattern(Pattern), % I hate that we have to do this, why not just give DATA
+ [indent(I, "Failure: ?assertException(~ts, ~ts, ~ts)~n", [Class, Term, Expr]),
+ case proplists:is_defined(unexpected_success, Props) of
+ true ->
+ [indent(I, " expected: exception ~ts but nothing was raised~n", [Pattern]),
+ indent(I, " got: value ~p", [proplists:get_value(unexpected_success, Props)])];
+ false ->
+ Ex = proplists:get_value(unexpected_exception, Props),
+ [indent(I, " expected: exception ~ts~n", [Pattern]),
+ indent(I, " got: exception ~p", [Ex])]
+ end];
+
+format_assertion_failure(Type, Props, I) when Type =:= assertNotException_failed
+ ; Type =:= assertNotException ->
+ Expr = proplists:get_value(expression, Props),
+ Pattern = proplists:get_value(pattern, Props),
+ {Class, Term} = extract_exception_pattern(Pattern), % I hate that we have to do this, why not just give DAT
+ Ex = proplists:get_value(unexpected_exception, Props),
+ [indent(I, "Failure: ?assertNotException(~ts, ~ts, ~ts)~n", [Class, Term, Expr]),
+ indent(I, " expected not: exception ~ts~n", [Pattern]),
+ indent(I, " got: exception ~p", [Ex])];
+
+format_assertion_failure(Type, Props, I) when Type =:= command_failed
+ ; Type =:= command ->
+ Cmd = proplists:get_value(command, Props),
+ Expected = proplists:get_value(expected_status, Props),
+ Status = proplists:get_value(status, Props),
+ [indent(I, "Failure: ?cmdStatus(~p, ~p)~n", [Expected, Cmd]),
+ indent(I, " expected: status ~p~n", [Expected]),
+ indent(I, " got: status ~p", [Status])];
+
+format_assertion_failure(Type, Props, I) when Type =:= assertCmd_failed
+ ; Type =:= assertCmd ->
+ Cmd = proplists:get_value(command, Props),
+ Expected = proplists:get_value(expected_status, Props),
+ Status = proplists:get_value(status, Props),
+ [indent(I, "Failure: ?assertCmdStatus(~p, ~p)~n", [Expected, Cmd]),
+ indent(I, " expected: status ~p~n", [Expected]),
+ indent(I, " got: status ~p", [Status])];
+
+format_assertion_failure(Type, Props, I) when Type =:= assertCmdOutput_failed
+ ; Type =:= assertCmdOutput ->
+ Cmd = proplists:get_value(command, Props),
+ Expected = proplists:get_value(expected_output, Props),
+ Output = proplists:get_value(output, Props),
+ [indent(I, "Failure: ?assertCmdOutput(~p, ~p)~n", [Expected, Cmd]),
+ indent(I, " expected: ~p~n", [Expected]),
+ indent(I, " got: ~p", [Output])];
+
+format_assertion_failure(Type, Props, I) ->
+ indent(I, "~p", [{Type, Props}]).
+
+indent(I, Fmt, Args) ->
+ io_lib:format("~" ++ integer_to_list(I) ++ "s" ++ Fmt, [" "|Args]).
+
+extract_exception_pattern(Str) ->
+ ["{", Class, Term|_] = re:split(Str, "[, ]{1,2}", [unicode,{return,list}]),
+ {Class, Term}.
\ No newline at end of file
diff --git a/test/gleam/gleam_community_maths_float_test.gleam b/test/gleam/gleam_community_maths_float_test.gleam
new file mode 100644
index 0000000..a20323a
--- /dev/null
+++ b/test/gleam/gleam_community_maths_float_test.gleam
@@ -0,0 +1,764 @@
+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() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ assert Ok(result) = floatx.acos(1.0)
+ result
+ |> floatx.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ assert Ok(result) = floatx.acos(0.5)
+ result
+ |> floatx.isclose(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() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ assert Ok(result) = floatx.acosh(1.0)
+ result
+ |> floatx.isclose(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))
+
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ assert Ok(result) = floatx.asin(0.5)
+ result
+ |> floatx.isclose(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() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.asinh(0.0)
+ |> floatx.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.asinh(0.5)
+ |> floatx.isclose(0.481211, 0.0, tol)
+ |> should.be_true()
+}
+
+pub fn float_atan_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.atan(0.0)
+ |> floatx.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.atan(0.5)
+ |> floatx.isclose(0.463647, 0.0, tol)
+ |> should.be_true()
+}
+
+pub fn math_atan2_test() {
+ assert Ok(tol) = floatx.pow(-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.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.atan2(0.0, 1.0)
+ |> floatx.isclose(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.isclose(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.isclose(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.isclose(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.isclose(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.isclose(result, 0.0, tol)
+ |> should.be_true()
+}
+
+pub fn float_atanh_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ assert Ok(result) = floatx.atanh(0.0)
+ result
+ |> floatx.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ assert Ok(result) = floatx.atanh(0.5)
+ result
+ |> floatx.isclose(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() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.cos(0.0)
+ |> floatx.isclose(1.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.cos(floatx.pi())
+ |> floatx.isclose(-1.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.cos(0.5)
+ |> floatx.isclose(0.877582, 0.0, tol)
+ |> should.be_true()
+}
+
+pub fn float_cosh_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.cosh(0.0)
+ |> floatx.isclose(1.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.cosh(0.5)
+ |> floatx.isclose(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_exp_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.exp(0.0)
+ |> floatx.isclose(1.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.exp(0.5)
+ |> floatx.isclose(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.exp(1000.0) but this is a property of the
+ // runtime.
+}
+
+pub fn float_log_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.log(1.0)
+ |> should.equal(Ok(0.0))
+
+ assert Ok(result) = floatx.log(0.5)
+ result
+ |> floatx.isclose(-0.693147, 0.0, tol)
+ |> should.be_true()
+
+ // Check that we get an error when the function is evaluated
+ // outside its domain
+ floatx.log(-1.0)
+ |> should.be_error()
+}
+
+pub fn floatx_log10_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ assert Ok(result) = floatx.log10(1.0)
+ result
+ |> floatx.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ assert Ok(result) = floatx.log10(10.0)
+ result
+ |> floatx.isclose(1.0, 0.0, tol)
+ |> should.be_true()
+
+ assert Ok(result) = floatx.log10(50.0)
+ result
+ |> floatx.isclose(1.698970, 0.0, tol)
+ |> should.be_true()
+
+ // Check that we get an error when the function is evaluated
+ // outside its domain
+ floatx.log10(-1.0)
+ |> should.be_error()
+}
+
+pub fn floatx_log2_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.log2(1.0)
+ |> should.equal(Ok(0.0))
+
+ floatx.log2(2.0)
+ |> should.equal(Ok(1.0))
+
+ assert Ok(result) = floatx.log2(5.0)
+ result
+ |> floatx.isclose(2.321928, 0.0, tol)
+ |> should.be_true()
+
+ // Check that we get an error when the function is evaluated
+ // outside its domain
+ floatx.log2(-1.0)
+ |> should.be_error()
+}
+
+pub fn floatx_logb_test() {
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.logb(10.0, 10.0)
+ |> should.equal(Ok(1.0))
+
+ floatx.logb(10.0, 100.0)
+ |> should.equal(Ok(0.5))
+
+ floatx.logb(1.0, 0.25)
+ |> should.equal(Ok(0.0))
+
+ // Check that we get an error when the function is evaluated
+ // outside its domain
+ floatx.logb(1.0, 1.0)
+ |> should.be_error()
+
+ floatx.logb(10.0, 1.0)
+ |> should.be_error()
+
+ floatx.logb(-1.0, 1.0)
+ |> should.be_error()
+}
+
+pub fn float_pow_test() {
+ floatx.pow(2.0, 2.0)
+ |> should.equal(Ok(4.0))
+
+ floatx.pow(-5.0, 3.0)
+ |> should.equal(Ok(-125.0))
+
+ floatx.pow(10.5, 0.0)
+ |> should.equal(Ok(1.0))
+
+ floatx.pow(16.0, 0.5)
+ |> should.equal(Ok(4.0))
+
+ floatx.pow(2.0, -1.0)
+ |> should.equal(Ok(0.5))
+
+ floatx.pow(2.0, -1.0)
+ |> should.equal(Ok(0.5))
+
+ // floatx.pow(-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.pow(-1.0, 0.5)
+ |> should.be_error()
+
+ // Check another case with a negative base and fractional exponent
+ floatx.pow(-1.5, 1.5)
+ |> should.be_error()
+
+ // floatx.pow(0.0, -1.0) is equivalent to 1. /. 0 and is expected
+ // to be an error
+ floatx.pow(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.pow(-2.0, -1.0)
+ |> should.equal(Ok(-0.5))
+}
+
+pub fn float_sqrt_test() {
+ floatx.sqrt(1.0)
+ |> should.equal(Ok(1.0))
+
+ floatx.sqrt(9.0)
+ |> should.equal(Ok(3.0))
+
+ // An error should be returned as an imaginary number would otherwise
+ // have to be returned
+ floatx.sqrt(-1.0)
+ |> should.be_error()
+}
+
+pub fn float_cbrt_test() {
+ floatx.cbrt(1.0)
+ |> should.equal(Ok(1.0))
+
+ floatx.cbrt(27.0)
+ |> should.equal(Ok(3.0))
+
+ // An error should be returned as an imaginary number would otherwise
+ // have to be returned
+ floatx.cbrt(-1.0)
+ |> should.be_error()
+}
+
+pub fn float_hypot_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+
+ floatx.hypot(0.0, 0.0)
+ |> should.equal(0.0)
+
+ floatx.hypot(1.0, 0.0)
+ |> should.equal(1.0)
+
+ floatx.hypot(0.0, 1.0)
+ |> should.equal(1.0)
+
+ let result = floatx.hypot(11.0, 22.0)
+ result
+ |> floatx.isclose(24.596747, 0.0, tol)
+ |> should.be_true()
+}
+
+pub fn float_sin_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.sin(0.0)
+ |> floatx.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.sin(0.5 *. floatx.pi())
+ |> floatx.isclose(1.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.sin(0.5)
+ |> floatx.isclose(0.479425, 0.0, tol)
+ |> should.be_true()
+}
+
+pub fn float_sinh_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.sinh(0.0)
+ |> floatx.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.sinh(0.5)
+ |> floatx.isclose(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() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.tan(0.0)
+ |> floatx.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.tan(0.5)
+ |> floatx.isclose(0.546302, 0.0, tol)
+ |> should.be_true()
+}
+
+pub fn math_tanh_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ // Check that the function agrees, at some arbitrary input
+ // points, with known function values
+ floatx.tanh(0.0)
+ |> floatx.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.tanh(25.0)
+ |> floatx.isclose(1.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.tanh(-25.0)
+ |> floatx.isclose(-1.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.tanh(0.5)
+ |> floatx.isclose(0.462117, 0.0, tol)
+ |> should.be_true()
+}
+
+pub fn float_rad2deg_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ floatx.rad2deg(0.0)
+ |> floatx.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.rad2deg(2.0 *. floatx.pi())
+ |> floatx.isclose(360.0, 0.0, tol)
+ |> should.be_true()
+}
+
+pub fn float_deg2rads_test() {
+ assert Ok(tol) = floatx.pow(-10.0, -6.0)
+ floatx.deg2rad(0.0)
+ |> floatx.isclose(0.0, 0.0, tol)
+ |> should.be_true()
+
+ floatx.deg2rad(360.0)
+ |> floatx.isclose(2.0 *. floatx.pi(), 0.0, tol)
+ |> should.be_true()
+}
+
+pub fn float_ceil_test() {
+ floatx.ceil(0.1)
+ |> should.equal(1.0)
+
+ floatx.ceil(0.9)
+ |> should.equal(1.0)
+}
+
+pub fn float_floor_test() {
+ floatx.floor(0.1)
+ |> should.equal(0.0)
+
+ floatx.floor(0.9)
+ |> should.equal(0.0)
+}
+
+pub fn float_min_test() {
+ floatx.min(0.75, 0.5)
+ |> should.equal(0.5)
+
+ floatx.min(0.5, 0.75)
+ |> should.equal(0.5)
+
+ floatx.min(-0.75, 0.5)
+ |> should.equal(-0.75)
+
+ floatx.min(-0.75, 0.5)
+ |> should.equal(-0.75)
+}
+
+pub fn float_max_test() {
+ floatx.max(0.75, 0.5)
+ |> should.equal(0.75)
+
+ floatx.max(0.5, 0.75)
+ |> should.equal(0.75)
+
+ floatx.max(-0.75, 0.5)
+ |> should.equal(0.5)
+
+ floatx.max(-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_flipsign_test() {
+ floatx.flipsign(100.0)
+ |> should.equal(-100.0)
+
+ floatx.flipsign(0.0)
+ |> should.equal(-0.0)
+
+ floatx.flipsign(-100.0)
+ |> should.equal(100.0)
+}
+
+pub fn float_beta_test() {
+ io.debug("TODO: Implement tests for 'floatx.beta'.")
+}
+
+pub fn float_erf_test() {
+ io.debug("TODO: Implement tests for 'floatx.erf'.")
+}
+
+pub fn float_gamma_test() {
+ io.debug("TODO: Implement tests for 'floatx.gamma'.")
+}
+
+pub fn math_round_to_nearest_test() {
+ floatx.round(1.50, option.Some(0), option.Some("Nearest"))
+ |> should.equal(Ok(2.0))
+
+ floatx.round(1.75, option.Some(0), option.Some("Nearest"))
+ |> should.equal(Ok(2.0))
+
+ floatx.round(2.00, option.Some(0), option.Some("Nearest"))
+ |> should.equal(Ok(2.0))
+
+ floatx.round(3.50, option.Some(0), option.Some("Nearest"))
+ |> should.equal(Ok(4.0))
+
+ floatx.round(4.50, option.Some(0), option.Some("Nearest"))
+ |> should.equal(Ok(4.0))
+
+ floatx.round(-3.50, option.Some(0), option.Some("Nearest"))
+ |> should.equal(Ok(-4.0))
+
+ floatx.round(-4.50, option.Some(0), option.Some("Nearest"))
+ |> should.equal(Ok(-4.0))
+}
+
+pub fn math_round_up_test() {
+ floatx.round(0.45, option.Some(0), option.Some("Up"))
+ |> should.equal(Ok(1.0))
+
+ floatx.round(0.50, option.Some(0), option.Some("Up"))
+ |> should.equal(Ok(1.0))
+
+ floatx.round(0.45, option.Some(1), option.Some("Up"))
+ |> should.equal(Ok(0.5))
+
+ floatx.round(0.50, option.Some(1), option.Some("Up"))
+ |> should.equal(Ok(0.5))
+
+ floatx.round(0.455, option.Some(2), option.Some("Up"))
+ |> should.equal(Ok(0.46))
+
+ floatx.round(0.505, option.Some(2), option.Some("Up"))
+ |> should.equal(Ok(0.51))
+}
+
+pub fn math_round_down_test() {
+ floatx.round(0.45, option.Some(0), option.Some("Down"))
+ |> should.equal(Ok(0.0))
+
+ floatx.round(0.50, option.Some(0), option.Some("Down"))
+ |> should.equal(Ok(0.0))
+
+ floatx.round(0.45, option.Some(1), option.Some("Down"))
+ |> should.equal(Ok(0.4))
+
+ floatx.round(0.50, option.Some(1), option.Some("Down"))
+ |> should.equal(Ok(0.50))
+
+ floatx.round(0.4550, option.Some(2), option.Some("Down"))
+ |> should.equal(Ok(0.45))
+
+ floatx.round(0.5050, option.Some(2), option.Some("Down"))
+ |> should.equal(Ok(0.50))
+}
+
+pub fn math_round_to_zero_test() {
+ floatx.round(0.50, option.Some(0), option.Some("ToZero"))
+ |> should.equal(Ok(0.0))
+
+ floatx.round(0.75, option.Some(0), option.Some("ToZero"))
+ |> should.equal(Ok(0.0))
+
+ floatx.round(0.45, option.Some(1), option.Some("ToZero"))
+ |> should.equal(Ok(0.4))
+
+ floatx.round(0.57, option.Some(1), option.Some("ToZero"))
+ |> should.equal(Ok(0.50))
+
+ floatx.round(0.4575, option.Some(2), option.Some("ToZero"))
+ |> should.equal(Ok(0.45))
+
+ floatx.round(0.5075, option.Some(2), option.Some("ToZero"))
+ |> should.equal(Ok(0.50))
+}
+
+pub fn math_round_ties_away_test() {
+ floatx.round(-1.40, option.Some(0), option.Some("TiesAway"))
+ |> should.equal(Ok(-1.0))
+
+ floatx.round(-1.50, option.Some(0), option.Some("TiesAway"))
+ |> should.equal(Ok(-2.0))
+
+ floatx.round(-2.00, option.Some(0), option.Some("TiesAway"))
+ |> should.equal(Ok(-2.0))
+
+ floatx.round(-2.50, option.Some(0), option.Some("TiesAway"))
+ |> should.equal(Ok(-3.0))
+
+ floatx.round(1.40, option.Some(0), option.Some("TiesAway"))
+ |> should.equal(Ok(1.0))
+
+ floatx.round(1.50, option.Some(0), option.Some("TiesAway"))
+ |> should.equal(Ok(2.0))
+
+ floatx.round(2.50, option.Some(0), option.Some("TiesAway"))
+ |> should.equal(Ok(3.0))
+}
+
+pub fn math_round_ties_up_test() {
+ floatx.round(-1.40, option.Some(0), option.Some("TiesUp"))
+ |> should.equal(Ok(-1.0))
+
+ floatx.round(-1.50, option.Some(0), option.Some("TiesUp"))
+ |> should.equal(Ok(-1.0))
+
+ floatx.round(-2.00, option.Some(0), option.Some("TiesUp"))
+ |> should.equal(Ok(-2.0))
+
+ floatx.round(-2.50, option.Some(0), option.Some("TiesUp"))
+ |> should.equal(Ok(-2.0))
+
+ floatx.round(1.40, option.Some(0), option.Some("TiesUp"))
+ |> should.equal(Ok(1.0))
+
+ floatx.round(1.50, option.Some(0), option.Some("TiesUp"))
+ |> should.equal(Ok(2.0))
+
+ floatx.round(2.50, option.Some(0), option.Some("TiesUp"))
+ |> should.equal(Ok(3.0))
+}
+
+pub fn float_gammainc_test() {
+ // Invalid input gives an error
+ // 1st arg is invalid
+ floatx.gammainc(-1.0, 1.0)
+ |> should.be_error()
+
+ // 2nd arg is invalid
+ floatx.gammainc(1.0, -1.0)
+ |> should.be_error()
+
+ // Valid input returns a result
+ floatx.gammainc(1.0, 0.0)
+ |> result.unwrap(-999.0)
+ |> floatx.isclose(0.0, 0.0, 0.01)
+ |> should.be_true()
+
+ floatx.gammainc(1.0, 2.0)
+ |> result.unwrap(-999.0)
+ |> floatx.isclose(0.864664716763387308106, 0.0, 0.01)
+ |> should.be_true()
+
+ floatx.gammainc(2.0, 3.0)
+ |> result.unwrap(-999.0)
+ |> floatx.isclose(0.8008517265285442280826, 0.0, 0.01)
+ |> should.be_true()
+
+ floatx.gammainc(3.0, 4.0)
+ |> result.unwrap(-999.0)
+ |> floatx.isclose(1.523793388892911312363, 0.0, 0.01)
+ |> should.be_true()
+}
+
+pub fn float_absdiff_test() {
+ floatx.absdiff(0.0, 0.0)
+ |> should.equal(0.0)
+
+ floatx.absdiff(1.0, 2.0)
+ |> should.equal(1.0)
+
+ floatx.absdiff(2.0, 1.0)
+ |> should.equal(1.0)
+
+ floatx.absdiff(-1.0, 0.0)
+ |> should.equal(1.0)
+
+ floatx.absdiff(0.0, -1.0)
+ |> should.equal(1.0)
+
+ floatx.absdiff(10.0, 20.0)
+ |> should.equal(10.0)
+
+ floatx.absdiff(-10.0, -20.0)
+ |> should.equal(10.0)
+
+ floatx.absdiff(-10.5, 10.5)
+ |> should.equal(21.0)
+}
diff --git a/test/gleam/gleam_community_maths_int_test.gleam b/test/gleam/gleam_community_maths_int_test.gleam
new file mode 100644
index 0000000..aa937f4
--- /dev/null
+++ b/test/gleam/gleam_community_maths_int_test.gleam
@@ -0,0 +1,157 @@
+import gleam_community/maths/int as intx
+import gleeunit
+import gleeunit/should
+import gleam/result
+import gleam/io
+
+pub fn int_absdiff_test() {
+ intx.absdiff(0, 0)
+ |> should.equal(0)
+
+ intx.absdiff(1, 2)
+ |> should.equal(1)
+
+ intx.absdiff(2, 1)
+ |> should.equal(1)
+
+ intx.absdiff(-1, 0)
+ |> should.equal(1)
+
+ intx.absdiff(0, -1)
+ |> should.equal(1)
+
+ intx.absdiff(10, 20)
+ |> should.equal(10)
+
+ intx.absdiff(-10, -20)
+ |> should.equal(10)
+
+ intx.absdiff(-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_min_test() {
+ intx.min(75, 50)
+ |> should.equal(50)
+
+ intx.min(50, 75)
+ |> should.equal(50)
+
+ intx.min(-75, 50)
+ |> should.equal(-75)
+
+ intx.min(-75, 50)
+ |> should.equal(-75)
+}
+
+pub fn float_max_test() {
+ intx.max(75, 50)
+ |> should.equal(75)
+
+ intx.max(50, 75)
+ |> should.equal(75)
+
+ intx.max(-75, 50)
+ |> should.equal(50)
+
+ intx.max(-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_flipsign_test() {
+ intx.flipsign(100)
+ |> should.equal(-100)
+
+ intx.flipsign(0)
+ |> should.equal(-0)
+
+ intx.flipsign(-100)
+ |> should.equal(100)
+}
diff --git a/test/gleam_community_maths_test.gleam b/test/gleam_community_maths_test.gleam
new file mode 100644
index 0000000..e9bdd39
--- /dev/null
+++ b/test/gleam_community_maths_test.gleam
@@ -0,0 +1,9 @@
+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"
+}
diff --git a/test/gleam_community_maths_test_ffi.erl b/test/gleam_community_maths_test_ffi.erl
new file mode 100644
index 0000000..f727223
--- /dev/null
+++ b/test/gleam_community_maths_test_ffi.erl
@@ -0,0 +1,40 @@
+-module(gleam_community_maths_test_ffi).
+
+-export([
+ main/0, should_equal/2, should_not_equal/2, should_be_ok/1,
+ should_be_error/1
+]).
+
+-include_lib("eunit/include/eunit.hrl").
+
+main() ->
+ Options = [
+ no_tty, {report, {eunit_progress, [colored]}}
+ ],
+ Files = filelib:wildcard("test/**/*.{erl,gleam}"),
+ Modules = lists:map(fun filepath_to_module/1, Files),
+ case eunit:test(Modules, Options) of
+ ok -> erlang:halt(0);
+ _ -> erlang:halt(1)
+ end.
+
+filepath_to_module(Path0) ->
+ Path1 = string:replace(Path0, "test/", ""),
+ Path2 = string:replace(Path1, ".erl", ""),
+ Path3 = string:replace(Path2, ".gleam", ""),
+ Path4 = string:replace(Path3, "/", "@", all),
+ Path5 = list_to_binary(Path4),
+ binary_to_atom(Path5).
+
+should_equal(Actual, Expected) ->
+ ?assertEqual(Expected, Actual),
+ nil.
+should_not_equal(Actual, Expected) ->
+ ?assertNotEqual(Expected, Actual),
+ nil.
+should_be_ok(A) ->
+ ?assertMatch({ok, _}, A),
+ nil.
+should_be_error(A) ->
+ ?assertMatch({error, _}, A),
+ nil.
diff --git a/test/gleam_community_maths_test_ffi.mjs b/test/gleam_community_maths_test_ffi.mjs
new file mode 100755
index 0000000..9468767
--- /dev/null
+++ b/test/gleam_community_maths_test_ffi.mjs
@@ -0,0 +1,35 @@
+import { opendir } from "fs/promises";
+
+const dir = "build/dev/javascript/gleam_community_maths/dist/gleam/";
+
+export async function main() {
+ console.log("Running tests...");
+
+ 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);
+
+ for (let fnName of Object.keys(module)) {
+ if (!fnName.endsWith("_test")) continue;
+ try {
+ module[fnName]();
+ process.stdout.write(`\u001b[32m.\u001b[0m`);
+ passes++;
+ } catch (error) {
+ let moduleName = "\ngleam/" + entry.name.slice(0, -3);
+ process.stdout.write(`\n❌ ${moduleName}.${fnName}: ${error}\n`);
+ failures++;
+ }
+ }
+ }
+
+ console.log(`
+
+${passes + failures} tests
+${passes} passes
+${failures} failures`);
+ process.exit(failures ? 1 : 0);
+}
diff --git a/test/maths_test.gleam b/test/maths_test.gleam
deleted file mode 100644
index 3831e7a..0000000
--- a/test/maths_test.gleam
+++ /dev/null
@@ -1,12 +0,0 @@
-import gleeunit
-import gleeunit/should
-
-pub fn main() {
- gleeunit.main()
-}
-
-// gleeunit test functions end in `_test`
-pub fn hello_world_test() {
- 1
- |> should.equal(1)
-}