From 42a98c18f07302e78b9fe2937c79524992ced75f Mon Sep 17 00:00:00 2001 From: Spotandjake Date: Thu, 24 Oct 2024 22:02:21 -0400 Subject: [PATCH 1/3] feat(stdlib): Reimplement `Number.gamma` and `Number.factorial` --- compiler/test/stdlib/number.test.gr | 33 +++++++++++++ stdlib/number.gr | 75 +++++++++++++++++++++++++++++ stdlib/number.md | 56 +++++++++++++++++++++ 3 files changed, 164 insertions(+) diff --git a/compiler/test/stdlib/number.test.gr b/compiler/test/stdlib/number.test.gr index 2b67caccf..7efc0d830 100644 --- a/compiler/test/stdlib/number.test.gr +++ b/compiler/test/stdlib/number.test.gr @@ -1057,3 +1057,36 @@ assert Number.isClose( assert Number.isNaN(Number.tan(Infinity)) assert Number.isNaN(Number.tan(-Infinity)) assert Number.isNaN(Number.tan(NaN)) + +// Number.gamma +// Note: Currently gamma will overflow the memory when using a bigint as such there are no tests for this +assert Number.gamma(1) == 1 +assert Number.gamma(3) == 2 +assert Number.gamma(10) == 362880 +assert Number.isClose(Number.gamma(0.5), Number.sqrt(Number.pi)) +assert Number.isClose(Number.gamma(1/2), Number.sqrt(Number.pi)) +assert Number.isClose(Number.gamma(5/2), (3/4) * Number.sqrt(Number.pi)) +assert Number.isClose(Number.gamma(7/2), (15/8) * Number.sqrt(Number.pi)) +assert Number.isClose(Number.gamma(-0.5), -2 * Number.sqrt(Number.pi)) +assert Number.isClose(Number.gamma(-1/2), -2 * Number.sqrt(Number.pi)) +assert Number.isClose(Number.gamma(-3/2), (4/3) * Number.sqrt(Number.pi)) +assert Number.isClose(Number.gamma(-5/2), (-8/15) * Number.sqrt(Number.pi)) +assert Number.isNaN(Number.gamma(-1)) +assert Number.isNaN(Number.gamma(-10)) +assert Number.isNaN(Number.gamma(-100)) +assert Number.isNaN(Number.gamma(Infinity)) +assert Number.isNaN(Number.gamma(NaN)) + +// Number.factorial +// Note: Currently factorial will overflow the memory when using a bigint as such there are no tests for this +assert Number.factorial(0) == 1 +assert Number.factorial(10) == 3628800 +assert Number.factorial(5) == 120 +assert Number.factorial(0) == 1 +assert Number.factorial(-5) == -120 +assert Number.factorial(-10) == -3628800 +assert Number.isClose(Number.factorial(0.5), (1/2) * Number.sqrt(Number.pi)) +assert Number.isClose(Number.factorial(1/2), (1/2) * Number.sqrt(Number.pi)) +assert Number.isClose(Number.factorial(5/2), (15/8) * Number.sqrt(Number.pi)) +assert Number.isNaN(Number.factorial(Infinity)) +assert Number.isNaN(Number.factorial(NaN)) diff --git a/stdlib/number.gr b/stdlib/number.gr index 80da1f1ae..5f85c0fae 100644 --- a/stdlib/number.gr +++ b/stdlib/number.gr @@ -1118,3 +1118,78 @@ provide let tan = (radians: Number) => { WasmI32.toGrain(newFloat64(value)): Number } } + +// Math.gamma implemented using the Lanczos approximation +// https://en.wikipedia.org/wiki/Lanczos_approximation +/** + * Computes the gamma function of a value using Lanczos approximation. + * + * @param z: The value to interpolate + * @returns The gamma of the given value + * + * @since v0.7.0 + */ +provide let rec gamma = z => { + if (z == 0 || isInteger(z) && z < 0) { + NaN + } else if (isInteger(z) && z > 0) { + let mut output = 1 + for (let mut i = 1; i < z; i += 1) { + output *= i + } + output + } else { + let mut z = z + let g = 7 + let c = [> + 0.99999999999980993, + 676.5203681218851, + -1259.1392167224028, + 771.32342877765313, + -176.61502916214059, + 12.507343278686905, + -0.13857109526572012, + 9.9843695780195716e-6, + 1.5056327351493116e-7, + ] + let mut output = 0 + if (z < 0.5) { + output = pi / sin(pi * z) / gamma(1 - z) + } else if (z == 0.5) { + // Handle this case separately because it is out of the domain of Number.pow when calculating + output = 1.7724538509055159 + } else { + z -= 1 + let mut x = c[0] + for (let mut i = 1; i < g + 2; i += 1) { + x += c[i] / (z + i) + } + + let t = z + g + 0.5 + output = sqrt(2 * pi) * (t ** (z + 0.5)) * exp(t * -1) * x + } + if (abs(output) == Infinity) Infinity else output + } +} + +/** + * Computes the product of consecutive integers for an integer input and computes the gamma function for non-integer inputs. + * + * @param n: The value to factorialize + * @returns The factorial of the given value + * + * @throws InvalidArgument(String): When `n` is a negative integer + * + * @since v0.7.0 + */ +provide let rec factorial = n => { + if (isInteger(n) && n < 0) { + gamma(abs(n) + 1) * -1 + } else if (!isInteger(n) && n < 0) { + throw Exception.InvalidArgument( + "Cannot compute the factorial of a negative non-integer", + ) + } else { + gamma(n + 1) + } +} diff --git a/stdlib/number.md b/stdlib/number.md index 79c28dfb5..49217f959 100644 --- a/stdlib/number.md +++ b/stdlib/number.md @@ -1659,3 +1659,59 @@ Examples: Number.tan(0) == 0 ``` +### Number.**gamma** + +
+Added in next +No other changes yet. +
+ +```grain +gamma : (z: Number) => Number +``` + +Computes the gamma function of a value using Lanczos approximation. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`z`|`Number`|The value to interpolate| + +Returns: + +|type|description| +|----|-----------| +|`Number`|The gamma of the given value| + +### Number.**factorial** + +
+Added in next +No other changes yet. +
+ +```grain +factorial : (n: Number) => Number +``` + +Computes the product of consecutive integers for an integer input and computes the gamma function for non-integer inputs. + +Parameters: + +|param|type|description| +|-----|----|-----------| +|`n`|`Number`|The value to factorialize| + +Returns: + +|type|description| +|----|-----------| +|`Number`|The factorial of the given value| + +Throws: + +`InvalidArgument(String)` + +* When `n` is a negative integer + From cbea31eb99e5f98cede36eb548f502cf5a68f182 Mon Sep 17 00:00:00 2001 From: Spotandjake <40705786+spotandjake@users.noreply.github.com> Date: Wed, 30 Oct 2024 12:34:16 -0400 Subject: [PATCH 2/3] Apply suggestions from oscar code review Co-authored-by: Oscar Spencer --- stdlib/number.gr | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/number.gr b/stdlib/number.gr index 5f85c0fae..fc9adc033 100644 --- a/stdlib/number.gr +++ b/stdlib/number.gr @@ -1122,7 +1122,7 @@ provide let tan = (radians: Number) => { // Math.gamma implemented using the Lanczos approximation // https://en.wikipedia.org/wiki/Lanczos_approximation /** - * Computes the gamma function of a value using Lanczos approximation. + * Computes the gamma function of a value using the Lanczos approximation. * * @param z: The value to interpolate * @returns The gamma of the given value @@ -1173,7 +1173,7 @@ provide let rec gamma = z => { } /** - * Computes the product of consecutive integers for an integer input and computes the gamma function for non-integer inputs. + * Computes the factorial of an integer input or the gamma function of a non-integer input. * * @param n: The value to factorialize * @returns The factorial of the given value From 6a208eba02de832bc78269450f24c74c2f0edc75 Mon Sep 17 00:00:00 2001 From: Spotandjake Date: Wed, 30 Oct 2024 12:38:17 -0400 Subject: [PATCH 3/3] feat: Add examples --- stdlib/number.gr | 8 ++++++++ stdlib/number.md | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/stdlib/number.gr b/stdlib/number.gr index fc9adc033..c3aaffd26 100644 --- a/stdlib/number.gr +++ b/stdlib/number.gr @@ -1127,6 +1127,10 @@ provide let tan = (radians: Number) => { * @param z: The value to interpolate * @returns The gamma of the given value * + * @example Number.gamma(1) == 1 + * @example Number.gamma(3) == 2 + * @example Number.isClose(Number.gamma(0.5), Number.sqrt(Number.pi)) + * * @since v0.7.0 */ provide let rec gamma = z => { @@ -1180,6 +1184,10 @@ provide let rec gamma = z => { * * @throws InvalidArgument(String): When `n` is a negative integer * + * @example Number.factorial(0) == 1 + * @example Number.factorial(3) == 6 + * @example Number.isClose(Number.factorial(0.5), (1/2) * Number.sqrt(Number.pi)) + * * @since v0.7.0 */ provide let rec factorial = n => { diff --git a/stdlib/number.md b/stdlib/number.md index 49217f959..bc04da564 100644 --- a/stdlib/number.md +++ b/stdlib/number.md @@ -1670,7 +1670,7 @@ No other changes yet. gamma : (z: Number) => Number ``` -Computes the gamma function of a value using Lanczos approximation. +Computes the gamma function of a value using the Lanczos approximation. Parameters: @@ -1684,6 +1684,20 @@ Returns: |----|-----------| |`Number`|The gamma of the given value| +Examples: + +```grain +Number.gamma(1) == 1 +``` + +```grain +Number.gamma(3) == 2 +``` + +```grain +Number.isClose(Number.gamma(0.5), Number.sqrt(Number.pi)) +``` + ### Number.**factorial**
@@ -1695,7 +1709,7 @@ No other changes yet. factorial : (n: Number) => Number ``` -Computes the product of consecutive integers for an integer input and computes the gamma function for non-integer inputs. +Computes the factorial of an integer input or the gamma function of a non-integer input. Parameters: @@ -1715,3 +1729,17 @@ Throws: * When `n` is a negative integer +Examples: + +```grain +Number.factorial(0) == 1 +``` + +```grain +Number.factorial(3) == 6 +``` + +```grain +Number.isClose(Number.factorial(0.5), (1/2) * Number.sqrt(Number.pi)) +``` +