Organize modules

This commit is contained in:
NicklasXYZ 2023-01-03 20:26:10 +01:00
parent 62eb90da2b
commit 086cf17088
14 changed files with 4090 additions and 2162 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,393 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css" integrity="sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js" integrity="sha384-PwRUT/YqbnEjkZO0zZxNqcxACrXe+j766U2amXcgMg5457rve2Y7I6ZJSm2A0mS4" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
//// // customised options
//// // auto-render specific keys, e.g.:
//// delimiters: [
//// {left: '$$', right: '$$', display: false},
//// {left: '\\[', right: '\\]', display: true}
//// ],
//// // rendering keys, e.g.:
//// throwOnError : false
//// });
//// });
////</script>
////<style>
//// .katex { font-size: 1.1em; }
////</style>
////
//// A module containing several different kinds of mathematical functions
//// applying to lists of real numbers.
////
//// Function naming has been adopted from <a href="https://en.wikipedia.org/wiki/C_mathematical_functions"> C mathematical function</a>.
////
//// ---
////
//// * **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
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Determine if a list of values are close to or equivalent to a
/// another list of reference values.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Returns the indices of the minimum values in a list.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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]))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Returns the indices of the maximum values in a list.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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]))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Returns the maximum value of a list.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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.))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Returns the minimum value of a list.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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.))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Returns the minimum value of a list.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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.))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
// }
// }
}

View file

@ -0,0 +1,455 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css" integrity="sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js" integrity="sha384-PwRUT/YqbnEjkZO0zZxNqcxACrXe+j766U2amXcgMg5457rve2Y7I6ZJSm2A0mS4" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
//// // customised options
//// // auto-render specific keys, e.g.:
//// delimiters: [
//// {left: '$$', right: '$$', display: false},
//// {left: '\\[', right: '\\]', display: true}
//// ],
//// // rendering keys, e.g.:
//// throwOnError : false
//// });
//// });
////</script>
////<style>
//// .katex { font-size: 1.1em; }
////</style>
////
//// A module containing several different kinds of mathematical functions
//// applying to integer numbers.
////
//// Function naming has been adopted from <a href="https://en.wikipedia.org/wiki/C_mathematical_functions"> C mathematical function</a>.
////
//// ---
////
//// * **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
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The min function.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn min(x: Int, y: Int) -> Int {
case x < y {
True -> x
False -> y
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The min function.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn max(x: Int, y: Int) -> Int {
case x > y {
True -> x
False -> y
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The minmax function.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn minmax(x: Int, y: Int) -> #(Int, Int) {
#(min(x, y), max(x, y))
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// The sign function which returns the sign of the input, indicating
/// whether it is positive, negative, or zero.
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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"
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn flipsign(x: Int) -> Int {
-1 * x
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// 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.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// A combinatorial function for computing the total number of combinations of $$n$$
/// elements, that is $$n!$$.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// A combinatorial function for computing the number of $$k$$-permuations (without repetitions)
/// of $$n$$ elements:
///
/// \\[
/// P(n, k) = \frac{n!}{(n - k)!}
/// \\]
///
/// <details>
/// <summary>Example:</summary>
///
/// 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))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// 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.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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)
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
pub fn absdiff(a: Int, b: Int) -> Int {
a - b
|> int.absolute_value()
}

View file

@ -0,0 +1,328 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css" integrity="sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js" integrity="sha384-PwRUT/YqbnEjkZO0zZxNqcxACrXe+j766U2amXcgMg5457rve2Y7I6ZJSm2A0mS4" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
//// // customised options
//// // auto-render specific keys, e.g.:
//// delimiters: [
//// {left: '$$', right: '$$', display: false},
//// {left: '\\[', right: '\\]', display: true}
//// ],
//// // rendering keys, e.g.:
//// throwOnError : false
//// });
//// });
////</script>
////<style>
//// .katex { font-size: 1.1em; }
////</style>
////
//// A module containing several different kinds of mathematical functions
//// applying to lists of real numbers.
////
//// Function naming has been adopted from <a href="https://en.wikipedia.org/wiki/C_mathematical_functions"> C mathematical function</a>.
////
//// ---
////
//// * **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
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Returns the indices of the minimum values in a list.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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]))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Returns the indices of the maximum values in a list.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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]))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Returns the maximum value of a list.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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.))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Returns the minimum value of a list.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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.))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Returns the minimum value of a list.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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.))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
// }
// }
}

View file

@ -0,0 +1,87 @@
////<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.css" integrity="sha384-vKruj+a13U8yHIkAyGgK1J3ArTLzrFGBbBc0tDp4ad/EyewESeXE/Iv67Aj8gKZ0" crossorigin="anonymous">
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/katex.min.js" integrity="sha384-PwRUT/YqbnEjkZO0zZxNqcxACrXe+j766U2amXcgMg5457rve2Y7I6ZJSm2A0mS4" crossorigin="anonymous"></script>
////<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.4/dist/contrib/auto-render.min.js" integrity="sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05" crossorigin="anonymous"></script>
////<script>
//// document.addEventListener("DOMContentLoaded", function() {
//// renderMathInElement(document.body, {
//// // customised options
//// // auto-render specific keys, e.g.:
//// delimiters: [
//// {left: '$$', right: '$$', display: false},
//// {left: '\\[', right: '\\]', display: true}
//// ],
//// // rendering keys, e.g.:
//// throwOnError : false
//// });
//// });
////</script>
////<style>
//// .katex { font-size: 1.1em; }
////</style>
////
//// A module containing several different kinds of mathematical functions
//// applying to lists of real numbers.
////
//// Function naming has been adopted from <a href="https://en.wikipedia.org/wiki/C_mathematical_functions"> C mathematical function</a>.
////
//// ---
////
//// * **Miscellaneous functions**
//// * [`trim`](#trim)
import gleam/list
import gleam/int
import gleam/float
/// <div style="text-align: right;">
/// <a href="https://github.com/nicklasxyz/gleam_stats/issues">
/// <small>Spot a typo? Open an issue!</small>
/// </a>
/// </div>
///
/// Trim a list to a certain size given min/max indices. The min/max indices
/// are inclusive.
///
/// <details>
/// <summary>Example:</summary>
///
/// 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.]))
/// }
/// </details>
///
/// <div style="text-align: right;">
/// <a href="#">
/// <small>Back to top </small>
/// </a>
/// </div>
///
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
}
}
}

410
temp.gleam Normal file
View file

@ -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-Diaconiss 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]))
}

592
test/eunit_progress.erl Normal file
View file

@ -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}.

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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"
}

View file

@ -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.

View file

@ -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);
}

View file

@ -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)
}