Browse Source

Merge pull request #645 from rben01/math-functions

Math functions
David Peter 11 tháng trước cách đây
mục cha
commit
2d6e1e2635

+ 4 - 0
book/build.py

@@ -166,6 +166,10 @@ list_of_functions(
                 "title": "Statistics",
                 "modules": ["math::statistics"],
             },
+            {
+                "title": "Combinatorics",
+                "modules": ["math::combinatorics"],
+            },
             {
                 "title": "Random sampling, distributions",
                 "modules": ["core::random", "math::distributions"],

+ 95 - 1
book/src/list-functions-math.md

@@ -1,6 +1,6 @@
 # Mathematical functions
 
-[Basics](#basics) · [Transcendental functions](#transcendental-functions) · [Trigonometry](#trigonometry) · [Statistics](#statistics) · [Random sampling, distributions](#random-sampling-distributions) · [Number theory](#number-theory) · [Numerical methods](#numerical-methods) · [Percentage calculations](#percentage-calculations) · [Geometry](#geometry) · [Algebra](#algebra) · [Trigonometry (extra)](#trigonometry-(extra))
+[Basics](#basics) · [Transcendental functions](#transcendental-functions) · [Trigonometry](#trigonometry) · [Statistics](#statistics) · [Combinatorics](#combinatorics) · [Random sampling, distributions](#random-sampling-distributions) · [Number theory](#number-theory) · [Numerical methods](#numerical-methods) · [Percentage calculations](#percentage-calculations) · [Geometry](#geometry) · [Algebra](#algebra) · [Trigonometry (extra)](#trigonometry-(extra))
 
 ## Basics
 
@@ -272,6 +272,37 @@ Truncate in centimeters.
 
 </details>
 
+### `fract` (Fractional part)
+Returns the fractional part of \\( x \\), i.e. the remainder when divided by 1.
+	If \\( x < 0 \\), then so will be `fract(x)`. Note that due to floating point error, a
+	number’s fractional part can be slightly “off”; for instance, `fract(1.2) ==
+	0.1999...996 != 0.2`.
+More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.fract).
+
+```nbt
+fn fract(x: Scalar) -> Scalar
+```
+
+<details>
+<summary>Examples</summary>
+
+<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code"  onclick=" window.open('https://numbat.dev/?q=fract%280%2E0%29')""></button></div><code class="language-nbt hljs numbat">fract(0.0)
+
+    = 0
+</code></pre>
+
+<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code"  onclick=" window.open('https://numbat.dev/?q=fract%285%2E5%29')""></button></div><code class="language-nbt hljs numbat">fract(5.5)
+
+    = 0.5
+</code></pre>
+
+<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code"  onclick=" window.open('https://numbat.dev/?q=fract%28%2D5%2E5%29')""></button></div><code class="language-nbt hljs numbat">fract(-5.5)
+
+    = -0.5
+</code></pre>
+
+</details>
+
 ### `mod` (Modulo)
 Calculates the least nonnegative remainder of \\( a (\mod b) \\).
 More information [here](https://doc.rust-lang.org/std/primitive.f64.html#method.rem_euclid).
@@ -597,6 +628,69 @@ fn median<D: Dim>(xs: List<D>) -> D
 
 </details>
 
+## Combinatorics
+
+Defined in: `math::combinatorics`
+
+### `factorial` (Factorial)
+The product of the integers 1 through n. Numbat also supports calling this via the postfix operator `n!`.
+More information [here](https://en.wikipedia.org/wiki/Factorial).
+
+```nbt
+fn factorial(n: Scalar) -> Scalar
+```
+
+<details>
+<summary>Examples</summary>
+
+<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code"  onclick=" window.open('https://numbat.dev/?q=factorial%284%29')""></button></div><code class="language-nbt hljs numbat">factorial(4)
+
+    = 24
+</code></pre>
+
+<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code"  onclick=" window.open('https://numbat.dev/?q=4%21')""></button></div><code class="language-nbt hljs numbat">4!
+
+    = 24
+</code></pre>
+
+</details>
+
+### `falling_factorial` (Falling factorial)
+Equal to \\( n⋅(n-1)⋅…⋅(n-k+2)⋅(n-k+1) \\) (k terms total). If n is an integer, this is the number of k-element permutations from a set of size n. k must always be an integer.
+More information [here](https://en.wikipedia.org/wiki/Falling_and_rising_factorials).
+
+```nbt
+fn falling_factorial(n: Scalar, k: Scalar) -> Scalar
+```
+
+<details>
+<summary>Examples</summary>
+
+<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code"  onclick=" window.open('https://numbat.dev/?q=falling%5Ffactorial%284%2C%202%29')""></button></div><code class="language-nbt hljs numbat">falling_factorial(4, 2)
+
+    = 12
+</code></pre>
+
+</details>
+
+### `binom` (Binomial coefficient)
+Equal to falling_factorial(n, k)/k!, this is the coefficient of \\( x^k \\) in the series expansion of \\( (1+x)^n \\) (see “binomial series”). If n is an integer, then this this is the number of k-element subsets of a set of size n, often read "n choose k". k must always be an integer.
+More information [here](https://en.wikipedia.org/wiki/Binomial_coefficient).
+
+```nbt
+fn binom(n: Scalar, k: Scalar) -> Scalar
+```
+
+<details>
+<summary>Examples</summary>
+
+<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code"  onclick=" window.open('https://numbat.dev/?q=binom%285%2C%202%29')""></button></div><code class="language-nbt hljs numbat">binom(5, 2)
+
+    = 10
+</code></pre>
+
+</details>
+
 ## Random sampling, distributions
 
 Defined in: `core::random`, `math::distributions`

+ 22 - 0
book/src/list-functions-other.md

@@ -129,6 +129,28 @@ fn is_nonzero<D: Dim>(value: D) -> Bool
 
 </details>
 
+### `is_integer`
+Returns true if the input is an integer.
+
+```nbt
+fn is_integer(x: Scalar) -> Bool
+```
+
+<details>
+<summary>Examples</summary>
+
+<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code"  onclick=" window.open('https://numbat.dev/?q=is%5Finteger%283%29')""></button></div><code class="language-nbt hljs numbat">is_integer(3)
+
+    = true    [Bool]
+</code></pre>
+
+<pre><div class="buttons"><button class="fa fa-play play-button" title="Run this code" aria-label="Run this code"  onclick=" window.open('https://numbat.dev/?q=is%5Finteger%28pi%29')""></button></div><code class="language-nbt hljs numbat">is_integer(pi)
+
+    = false    [Bool]
+</code></pre>
+
+</details>
+
 ## Quantities
 
 Defined in: `core::quantities`

+ 11 - 1
examples/tests/core.nbt

@@ -58,7 +58,7 @@ assert_eq(ceil(-1.2), -1)
 assert_eq(1.2 m |> ceil_in(m), 2 m)
 assert_eq(1.2 m |> ceil_in(cm), 120 cm)
 
-# trunc, trunc_in
+# trunc, trunc_in, fract
 
 assert_eq(trunc(1.2), 1)
 assert_eq(trunc(1.8), 1)
@@ -67,3 +67,13 @@ assert_eq(trunc(-1.8), -1)
 
 assert_eq(1.8 m |> trunc_in(m), 1 m)
 assert_eq(1.8 m |> trunc_in(cm), 180 cm)
+
+assert_eq(fract(1.2), 0.2, 1e-12)
+assert_eq(fract(1.8), 0.8, 1e-12)
+assert_eq(fract(0), 0)
+assert_eq(fract(1), 0)
+assert_eq(fract(1e10), 0)
+assert_eq(fract(-1.2), -0.2, 1e-12)
+assert_eq(fract(-1.8), -0.8, 1e-12)
+assert(is_nan(fract(NaN)))
+assert(is_nan(fract(inf)))

+ 78 - 0
examples/tests/math_functions.nbt

@@ -108,6 +108,84 @@ assert_eq(gamma(2.5), 1.329_340_388, 1e-8)
 assert_eq(gamma(3), 2)
 assert_eq(gamma(4), 6)
 
+# factorial
+
+assert_eq(factorial(0), 1)
+assert_eq(factorial(1), 1)
+assert_eq(factorial(2), 2)
+assert_eq(factorial(3), 6)
+assert_eq(factorial(4), 24)
+assert_eq(factorial(20), 2432902008176640000)
+
+# falling factorial
+
+assert_eq(falling_factorial(0, 0), 1)
+assert_eq(falling_factorial(1, 0), 1)
+assert_eq(falling_factorial(2, 0), 1)
+assert_eq(falling_factorial(42.5, 0), 1)
+
+assert_eq(falling_factorial(0, 1), 0)
+assert_eq(falling_factorial(1, 1), 1)
+assert_eq(falling_factorial(2, 1), 2)
+assert_eq(falling_factorial(42.5, 1), 42.5)
+
+assert_eq(falling_factorial(0, 2), 0)
+assert_eq(falling_factorial(1, 2), 0)
+assert_eq(falling_factorial(2, 2), 2)
+assert_eq(falling_factorial(42.5, 2), 1763.75)
+
+assert_eq(falling_factorial(4, 0), 1)
+assert_eq(falling_factorial(4, 1), 4)
+assert_eq(falling_factorial(4, 2), 12)
+assert_eq(falling_factorial(4, 3), 24)
+assert_eq(falling_factorial(4, 4), 24)
+assert_eq(falling_factorial(4, 5), 0)
+assert_eq(falling_factorial(4, 6), 0)
+
+assert_eq(falling_factorial(20, 0), 1)
+assert_eq(falling_factorial(20, 1), 20)
+assert_eq(falling_factorial(20, 2), 380)
+assert_eq(falling_factorial(20, 20), 2432902008176640000)
+assert_eq(falling_factorial(20, 21), 0)
+
+# binomial coefficient
+assert_eq(binom(0, -1), 0)
+assert_eq(binom(0, 0), 1)
+assert_eq(binom(0, 1), 0)
+
+assert_eq(binom(1, -1), 0)
+assert_eq(binom(1, 0), 1)
+assert_eq(binom(1, 1), 1)
+assert_eq(binom(1, 2), 0)
+
+assert_eq(binom(2, -1), 0)
+assert_eq(binom(2, 0), 1)
+assert_eq(binom(2, 1), 2)
+assert_eq(binom(2, 2), 1)
+assert_eq(binom(2, 3), 0)
+
+assert_eq(binom(3, -1), 0)
+assert_eq(binom(3, 0), 1)
+assert_eq(binom(3, 1), 3)
+assert_eq(binom(3, 2), 3)
+assert_eq(binom(3, 3), 1)
+assert_eq(binom(3, 4), 0)
+
+assert_eq(binom(4, -1), 0)
+assert_eq(binom(4, 0), 1)
+assert_eq(binom(4, 1), 4)
+assert_eq(binom(4, 2), 6)
+assert_eq(binom(4, 3), 4)
+assert_eq(binom(4, 4), 1)
+assert_eq(binom(4, 5), 0)
+
+assert_eq(binom(1.5, -1), 0)
+assert_eq(binom(1.5, 0), 1)
+assert_eq(binom(1.5, 1), 1.5)
+assert_eq(binom(1.5, 2), 0.375)
+assert_eq(binom(1.5, 3), -0.0625)
+assert_eq(binom(1.5, 4), 0.0234375)
+
 # maximum
 
 assert_eq(maximum([1]), 1)

+ 11 - 0
numbat/modules/core/functions.nbt

@@ -79,6 +79,17 @@ fn trunc(x: Scalar) -> Scalar
 @example("trunc_in(cm, 5.7 m)", "Truncate in centimeters.")
 fn trunc_in<D: Dim>(base: D, value: D) -> D = trunc(value / base) × base
 
+@name("Fractional part")
+@description("Returns the fractional part of $x$, i.e. the remainder when divided by 1.
+	If $x < 0$, then so will be `fract(x)`. Note that due to floating point error, a
+	number’s fractional part can be slightly “off”; for instance, `fract(1.2) ==
+	0.1999...996 != 0.2`.")
+@url("https://doc.rust-lang.org/std/primitive.f64.html#method.fract")
+@example("fract(0.0)")
+@example("fract(5.5)")
+@example("fract(-5.5)")
+fn fract(x: Scalar) -> Scalar
+
 @name("Modulo")
 @description("Calculates the least nonnegative remainder of $a (\\mod b)$.")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.rem_euclid")

+ 9 - 1
numbat/modules/core/numbers.nbt

@@ -1,3 +1,6 @@
+use core::scalar
+use core::functions
+
 @description("Returns true if the input is `NaN`.")
 @url("https://doc.rust-lang.org/std/primitive.f64.html#method.is_nan")
 @example("is_nan(37)")
@@ -23,4 +26,9 @@ fn is_zero<D: Dim>(value: D) -> Bool = value == 0
 @description("Returns true unless the input is 0 (zero).")
 @example("is_nonzero(37)")
 @example("is_nonzero(0)")
-fn is_nonzero<D: Dim>(value: D) -> Bool = !is_zero(value)
+fn is_nonzero<D: Dim>(value: D) -> Bool = !is_zero(value)
+
+@description("Returns true if the input is an integer.")
+@example("is_integer(3)")
+@example("is_integer(pi)")
+fn is_integer(x: Scalar) -> Bool = is_zero(fract(x))

+ 35 - 0
numbat/modules/math/combinatorics.nbt

@@ -0,0 +1,35 @@
+use core::error
+use core::functions
+use core::numbers
+use math::transcendental
+
+@name("Factorial")
+@description("The product of the integers 1 through n. Numbat also supports calling this via the postfix operator `n!`.")
+@url("https://en.wikipedia.org/wiki/Factorial")
+@example("factorial(4)")
+@example("4!")
+fn factorial(n: Scalar) -> Scalar = n!
+
+@name("Falling factorial")
+@description("Equal to $n⋅(n-1)⋅…⋅(n-k+2)⋅(n-k+1)$ (k terms total). If n is an integer, this is the number of k-element permutations from a set of size n. k must always be an integer.")
+@url("https://en.wikipedia.org/wiki/Falling_and_rising_factorials")
+@example("falling_factorial(4, 2)")
+fn falling_factorial(n: Scalar, k: Scalar) -> Scalar =
+	if k < 0 || !is_integer(k) then
+		error("in falling_factorial(n, k), k must be a nonnegative integer")
+	else if is_zero(k) then
+		1
+	else
+		n * falling_factorial(n-1, k-1)
+
+@name("Binomial coefficient")
+@description("Equal to falling_factorial(n, k)/k!, this is the coefficient of $x^k$ in the series expansion of $(1+x)^n$ (see “binomial series”). If n is an integer, then this this is the number of k-element subsets of a set of size n, often read \"n choose k\". k must always be an integer.")
+@url("https://en.wikipedia.org/wiki/Binomial_coefficient")
+@example("binom(5, 2)")
+fn binom(n: Scalar, k: Scalar) -> Scalar =
+	if !is_integer(k) then
+		error("in binom(n, k), k must be an integer")
+	else if k < 0 || (k > n && is_integer(n)) then
+		0
+	else
+		falling_factorial(n, k) / k!

+ 1 - 0
numbat/modules/prelude.nbt

@@ -18,6 +18,7 @@ use math::number_theory
 use math::distributions
 use math::geometry
 use math::percentage_calculations
+use math::combinatorics
 
 use units::si
 use units::time

+ 1 - 0
numbat/src/ffi/functions.rs

@@ -48,6 +48,7 @@ pub(crate) fn functions() -> &'static HashMap<String, ForeignFunction> {
         insert_function!(floor, 1..=1);
         insert_function!(ceil, 1..=1);
         insert_function!(trunc, 1..=1);
+        insert_function!(fract, 1..=1);
 
         insert_function!(sin, 1..=1);
         insert_function!(cos, 1..=1);

+ 1 - 0
numbat/src/ffi/math.rs

@@ -34,6 +34,7 @@ simple_scalar_math_function!(round, round);
 simple_scalar_math_function!(floor, floor);
 simple_scalar_math_function!(ceil, ceil);
 simple_scalar_math_function!(trunc, trunc);
+simple_scalar_math_function!(fract, fract);
 
 simple_scalar_math_function!(sin, sin);
 simple_scalar_math_function!(cos, cos);