Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stdlib): Reimplement Number.gamma and Number.factorial #2182

Merged
merged 4 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions compiler/test/stdlib/number.test.gr
Original file line number Diff line number Diff line change
Expand Up @@ -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))
75 changes: 75 additions & 0 deletions stdlib/number.gr
Original file line number Diff line number Diff line change
Expand Up @@ -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.
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
*
* @param z: The value to interpolate
* @returns The gamma of the given value
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
*
* @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.
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
*
* @param n: The value to factorialize
* @returns The factorial of the given value
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
*
* @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)
}
}
56 changes: 56 additions & 0 deletions stdlib/number.md
Original file line number Diff line number Diff line change
Expand Up @@ -1659,3 +1659,59 @@ Examples:
Number.tan(0) == 0
```

### Number.**gamma**

<details disabled>
<summary tabindex="-1">Added in <code>next</code></summary>
No other changes yet.
</details>

```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**

<details disabled>
<summary tabindex="-1">Added in <code>next</code></summary>
No other changes yet.
</details>

```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

Loading