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 all 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))
83 changes: 83 additions & 0 deletions stdlib/number.gr
Original file line number Diff line number Diff line change
Expand Up @@ -1118,3 +1118,86 @@ 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 the Lanczos approximation.
*
* @param z: The value to interpolate
* @returns The gamma of the given value
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
*
* @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 => {
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 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
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
*
* @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 => {
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)
}
}
84 changes: 84 additions & 0 deletions stdlib/number.md
Original file line number Diff line number Diff line change
Expand Up @@ -1659,3 +1659,87 @@ 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 the Lanczos approximation.

Parameters:

|param|type|description|
|-----|----|-----------|
|`z`|`Number`|The value to interpolate|

Returns:

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

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

```grain
factorial : (n: Number) => Number
```

Computes the factorial of an integer input or the gamma function of a non-integer input.

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

Examples:

```grain
Number.factorial(0) == 1
```

```grain
Number.factorial(3) == 6
```

```grain
Number.isClose(Number.factorial(0.5), (1/2) * Number.sqrt(Number.pi))
```

Loading