From 906272e909785381920f00e1c43ca17eae92dc70 Mon Sep 17 00:00:00 2001 From: NicklasXYZ Date: Sun, 22 Jan 2023 14:35:48 +0100 Subject: [PATCH] Tidy up float module --- src/gleam_community/maths/float.gleam | 164 ++++++++++++------ .../gleam_community_maths_float_test.gleam | 11 +- 2 files changed, 117 insertions(+), 58 deletions(-) diff --git a/src/gleam_community/maths/float.gleam b/src/gleam_community/maths/float.gleam index 479ad82..c661683 100644 --- a/src/gleam_community/maths/float.gleam +++ b/src/gleam_community/maths/float.gleam @@ -90,7 +90,7 @@ import gleam/option /// /// /// -/// The ceiling function rounds a given input value $$x \in \mathbb{R}$$ towards $$+\infty$$ at a specified number of digits. +/// The ceiling function rounds a given input value $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is larger than or equal to the input $$x$$. /// /// Note: The ceiling function is used as an alias for the rounding function [`round`](#round) with rounding mode `"Up"`. /// @@ -146,7 +146,7 @@ pub fn ceiling(x: Float, digits: option.Option(Int)) -> Result(Float, String) { /// /// /// -/// The floor function rounds a given input value $$x \in \mathbb{R}$$ towards $$-\infty$$ at a specified number of digits. +/// The floor function rounds input $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is less than or equal to the input $$x$$ /// /// Note: The floor function is used as an alias for the rounding function [`round`](#round) with rounding mode `"Down"`. /// @@ -201,7 +201,7 @@ pub fn floor(x: Float, digits: option.Option(Int)) -> Result(Float, String) { /// /// /// -/// The truncate function rounds a given input value $$x \in \mathbb{R}$$ towards $$0$$ at a specified number of digits. +/// The truncate function rounds a given input $$x \in \mathbb{R}$$ to the nearest integer value (at the specified digit) that is less than or equal to the absolute value of the input $$x$$. /// /// Note: The truncate function is used as an alias for the rounding function [`round`](#round) with rounding mode `"ToZero"`. /// @@ -392,8 +392,10 @@ pub fn round( case digits { option.Some(a) -> { assert Ok(p) = power(10.0, int.to_float(a)) + // Round the given input x using at the specified digit do_round(p, x, mode) } + // Round the given input x using at the default digit option.None -> do_round(1.0, x, mode) } } @@ -404,9 +406,9 @@ fn do_round( mode: option.Option(String), ) -> Result(Float, String) { case mode { - // Rounding mode choices + // Determine the rounding mode option.Some("Nearest") -> - round_nearest(p, x) + round_to_nearest(p, x) |> Ok option.Some("TiesAway") -> round_ties_away(p, x) @@ -423,9 +425,9 @@ fn do_round( option.Some("Up") -> round_up(p, x) |> Ok - // Default rounding mode. The default is "Nearest" + // Otherwise, use the Default rounding mode option.None -> - round_nearest(p, x) + round_to_nearest(p, x) |> Ok _ -> "Invalid rounding mode. Valid input is 'Nearest', 'TiesAway', 'TiesUp', 'ToZero', 'Down', 'Up'." @@ -433,69 +435,65 @@ fn do_round( } } -fn round_nearest(p: Float, x: Float) -> Float { +fn round_to_nearest(p: Float, x: Float) -> Float { let xabs = float.absolute_value(x) *. p - let rem = xabs -. truncate_float(xabs) - case rem { - _ if rem >. 0.5 -> sign(x) *. truncate_float(xabs +. 1.0) /. p - _ if rem == 0.5 -> { + let xabs_truncated = truncate_float(xabs) + let remainder = xabs -. xabs_truncated + case remainder { + _ if remainder >. 0.5 -> sign(x) *. truncate_float(xabs +. 1.0) /. p + _ if remainder == 0.5 -> { assert Ok(is_even) = int.modulo(to_int(xabs), 2) case is_even == 0 { - True -> sign(x) *. truncate_float(xabs) /. p + True -> sign(x) *. xabs_truncated /. p False -> sign(x) *. truncate_float(xabs +. 1.0) /. p } } - _ -> sign(x) *. truncate_float(xabs) /. p + _ -> sign(x) *. xabs_truncated /. p } } fn round_ties_away(p: Float, x: Float) -> Float { let xabs = float.absolute_value(x) *. p - let rem = xabs -. truncate_float(xabs) - case rem { - _ if rem >=. 0.5 -> sign(x) *. truncate_float(xabs +. 1.0) /. p + let remainder = xabs -. truncate_float(xabs) + case remainder { + _ if remainder >=. 0.5 -> sign(x) *. truncate_float(xabs +. 1.0) /. p _ -> sign(x) *. truncate_float(xabs) /. p } } fn round_ties_up(p: Float, x: Float) -> Float { let xabs = float.absolute_value(x) *. p - let rem = xabs -. truncate_float(xabs) - case rem { - _ if rem >=. 0.5 && x >=. 0.0 -> sign(x) *. truncate_float(xabs +. 1.0) /. p - _ -> sign(x) *. truncate_float(xabs) /. p + let xabs_truncated = truncate_float(xabs) + let remainder = xabs -. xabs_truncated + case remainder { + _ if remainder >=. 0.5 && x >=. 0.0 -> + sign(x) *. truncate_float(xabs +. 1.0) /. p + _ -> sign(x) *. xabs_truncated /. p } } +// Rounding mode: ToZero / Truncate fn round_to_zero(p: Float, x: Float) -> Float { truncate_float(x *. p) /. p } -fn round_down(p: Float, x: Float) -> Float { - do_floor(x *. p) /. p -} - -fn round_up(p: Float, x: Float) -> Float { - do_ceiling(x *. p) /. p -} - fn truncate_float(x: Float) -> Float { do_truncate_float(x) } if erlang { - external fn do_ceiling(Float) -> Float = - "math" "ceil" + external fn do_truncate_float(Float) -> Float = + "erlang" "trunc" } if javascript { - external fn do_ceiling(Float) -> Float = - "../floatx.mjs" "ceil" + external fn do_to_int(Float) -> Int = + "../floatx.mjs" "trunc" } -if erlang { - external fn do_truncate_float(Float) -> Float = - "erlang" "trunc" +// Rounding mode: Down / Floor +fn round_down(p: Float, x: Float) -> Float { + do_floor(x *. p) /. p } if erlang { @@ -508,7 +506,56 @@ if javascript { "../floatx.mjs" "floor" } -fn to_int(x: Float) -> Int { +// Rounding mode: Up / Ceiling +fn round_up(p: Float, x: Float) -> Float { + do_ceiling(x *. p) /. p +} + +if erlang { + external fn do_ceiling(Float) -> Float = + "math" "ceil" +} + +if javascript { + external fn do_ceiling(Float) -> Float = + "../floatx.mjs" "ceil" +} + +///
+/// +/// Spot a typo? Open an issue! +/// +///
+/// +/// The function returns the integral part of a given floating point value. +/// That is, everything after the decimal point of a given floating point value is discarded and only the integer value before the decimal point is returned. +/// +///
+/// Example +/// +/// import gleeunit/should +/// import gleam/option +/// import gleam_community/maths/float as floatx +/// +/// pub fn example() { +/// floatx.to_int(12.0654) +/// |> should.equal(12) +/// +/// // Note: Making the following function call is equivalent +/// // but instead of returning a value of type 'Int' a value +/// // of type 'Float' is returned. +/// floatx.round(12.0654, option.Some(0), option.Some("ToZero")) +/// |> should.equal(Ok(12.0)) +/// } +///
+/// +///
+/// +/// Back to top ↑ +/// +///
+/// +pub fn to_int(x: Float) -> Int { do_to_int(x) } @@ -517,6 +564,11 @@ if erlang { "erlang" "trunc" } +if javascript { + external fn do_to_int(Float) -> Int = + "../floatx.mjs" "to_int" +} + ///
/// /// Spot a typo? Open an issue! @@ -1029,7 +1081,7 @@ if javascript { /// import gleam_community/maths/float as floatx /// /// pub fn example() { -/// floatx.exp(0.0) +/// floatx.exponential(0.0) /// |> should.equal(1.0) /// } /// @@ -1040,17 +1092,17 @@ if javascript { /// ///
/// -pub fn exp(x: Float) -> Float { - do_exp(x) +pub fn exponential(x: Float) -> Float { + do_exponential(x) } if erlang { - external fn do_exp(Float) -> Float = + external fn do_exponential(Float) -> Float = "math" "exp" } if javascript { - external fn do_exp(Float) -> Float = + external fn do_exponential(Float) -> Float = "../floatx.mjs" "exp" } @@ -1553,11 +1605,8 @@ pub fn nth_root(x: Float, n: Int) -> Result(Float, String) { /// /// /// A function to compute the hypotenuse of a right-angled triangle: $$\sqrt[2](x^2 + y^2)$$. -/// /// The function can also be used to calculate the Euclidean distance in 2 dimensions. /// -/// Naive (unfused) and corrected (unfused) in [https://arxiv.org/pdf/1904.09481.pdf]("An Improved Algorithm for Hypot(A, B)" by Borges, C. F) -/// ///
/// Example /// @@ -1853,7 +1902,7 @@ pub fn to_radian(x: Float) -> Float { /// /// /// -/// The min function. +/// The minimum function that takes two arguments $$x$$ and $$y$$ and returns the smallest of the two. /// ///
/// Example @@ -1889,7 +1938,7 @@ pub fn minimum(x: Float, y: Float) -> Float { /// /// /// -/// The min function. +/// The maximum function that takes two arguments $$x$$ and $$y$$ and returns the largest of the two. /// ///
/// Example @@ -1925,7 +1974,7 @@ pub fn maximum(x: Float, y: Float) -> Float { /// /// /// -/// The minmax function. +/// The minmax function that takes two arguments $$x$$ and $$y$$ and returns a tuple with the smallest value first and largest second. /// ///
/// Example @@ -1958,7 +2007,7 @@ pub fn minmax(x: Float, y: Float) -> #(Float, Float) { /// /// /// -/// The sign function which returns the sign of the input, indicating whether it is positive, negative, or zero. +/// The sign function that returns the sign of the input, indicating whether it is positive (+1), negative (-1), or zero (0). /// ///
/// @@ -2085,7 +2134,7 @@ pub fn erf(x: Float) -> Float { // Formula 7.1.26 given in Abramowitz and Stegun. let t = 1.0 /. { 1.0 +. p *. x } let y = - 1.0 -. { { { { a5 *. t +. a4 } *. t +. a3 } *. t +. a2 } *. t +. a1 } *. t *. exp( + 1.0 -. { { { { a5 *. t +. a4 } *. t +. a3 } *. t +. a2 } *. t +. a1 } *. t *. exponential( -1.0 *. x *. x, ) sign *. y @@ -2140,7 +2189,7 @@ fn gamma_lanczos(x: Float) -> Float { let t: Float = z +. lanczos_g +. 0.5 assert Ok(v1) = power(2.0 *. pi(), 0.5) assert Ok(v2) = power(t, z +. 0.5) - v1 *. v2 *. exp(-1.0 *. t) *. x + v1 *. v2 *. exponential(-1.0 *. t) *. x } } } @@ -2166,7 +2215,13 @@ pub fn incomplete_gamma(a: Float, x: Float) -> Result(Float, String) { case a >. 0.0 && x >=. 0.0 { True -> { assert Ok(v) = power(x, a) - v *. exp(-1.0 *. x) *. incomplete_gamma_sum(a, x, 1.0 /. a, 0.0, 1.0) + v *. exponential(-1.0 *. x) *. incomplete_gamma_sum( + a, + x, + 1.0 /. a, + 0.0, + 1.0, + ) |> Ok } @@ -2247,8 +2302,6 @@ pub fn tau() -> Float { /// /// Euler's number $$e \approx 2.71828\dots$$. /// -/// Note: If the input value $$x$$ is too large an overflow error might occur. -/// ///
/// Example /// @@ -2256,6 +2309,7 @@ pub fn tau() -> Float { /// import gleam_community/maths/float as floatx /// /// pub fn example() { +/// // Test that the constant is approximately equal to 2.7128... /// floatx.e() /// |> floatx.is_close(2.7128, 0.0, 0.000001) /// |> should.be_true() @@ -2269,7 +2323,7 @@ pub fn tau() -> Float { ///
/// pub fn e() -> Float { - exp(1.0) + exponential(1.0) } ///
diff --git a/test/gleam/gleam_community_maths_float_test.gleam b/test/gleam/gleam_community_maths_float_test.gleam index a144b64..a7ff737 100644 --- a/test/gleam/gleam_community_maths_float_test.gleam +++ b/test/gleam/gleam_community_maths_float_test.gleam @@ -209,16 +209,16 @@ pub fn float_exponential_test() { assert Ok(tol) = floatx.power(-10.0, -6.0) // Check that the function agrees, at some arbitrary input // points, with known function values - floatx.exp(0.0) + floatx.exponential(0.0) |> floatx.is_close(1.0, 0.0, tol) |> should.be_true() - floatx.exp(0.5) + floatx.exponential(0.5) |> floatx.is_close(1.648721, 0.0, tol) |> should.be_true() // An (overflow) error might occur when given an input // value that will result in a too large output value - // e.g. floatx.exp(1000.0) but this is a property of the + // e.g. floatx.exponential(1000.0) but this is a property of the // runtime. } @@ -1076,3 +1076,8 @@ pub fn float_is_close_test() { floatx.is_close(val, ref_val, rtol, atol) |> should.be_true() } + +pub fn float_to_int_test() { + floatx.to_int(12.0654) + |> should.equal(12) +}