Skip to content

Commit 9a0a8e7

Browse files
committed
feat: add known special cases of gamma
1 parent 203904c commit 9a0a8e7

File tree

2 files changed

+42
-17
lines changed

2 files changed

+42
-17
lines changed

src/function/probability/factorial.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ import { deepMap } from '../../utils/collection.js'
22
import { factory } from '../../utils/factory.js'
33

44
const name = 'factorial'
5-
const dependencies = ['typed', 'gamma']
5+
const dependencies = ['typed', 'isInteger', 'bignumber', 'gamma']
66

7-
export const createFactorial = /* #__PURE__ */ factory(name, dependencies, ({ typed, gamma }) => {
7+
export const createFactorial = /* #__PURE__ */ factory(name, dependencies, ({
8+
typed, isInteger, bignumber, gamma
9+
}) => {
810
/**
911
* Compute the factorial of a value
1012
*
@@ -40,6 +42,8 @@ export const createFactorial = /* #__PURE__ */ factory(name, dependencies, ({ ty
4042
if (n.isNegative()) {
4143
throw new Error('Value must be non-negative')
4244
}
45+
if (!n.isFinite()) return bignumber(Infinity)
46+
if (!isInteger(n)) throw new Error('Value must be integer')
4347

4448
return gamma(n.plus(1))
4549
},

src/function/probability/gamma.js

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@ import { gammaG, gammaNumber, gammaP } from '../../plain/number/index.js'
33

44
const name = 'gamma'
55
const dependencies = [
6-
'typed', 'config', 'multiplyScalar', 'pow', 'bernoulli',
7-
'BigNumber', 'Complex'
6+
'typed', 'config', 'BigNumber', 'Complex',
7+
'isInteger', 'bernoulli', 'equal'
88
]
99

1010
export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({
11-
typed, config, multiplyScalar, pow, bernoulli,
12-
BigNumber, Complex
11+
typed, config, BigNumber, Complex,
12+
isInteger, bernoulli, equal
1313
}) => {
1414
/**
1515
* Compute the gamma function of a value using Lanczos approximation for
@@ -78,18 +78,41 @@ export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({
7878
return x.mul(twoPiSqrt).mul(tpow).mul(expt)
7979
}
8080

81+
const piB = BigNumber.acos(-1)
82+
const halflog2piB = piB.times(2).ln().div(2)
83+
const sqrtpiB = piB.sqrt()
84+
const zeroB = new BigNumber(0)
85+
const twoB = new BigNumber(2)
86+
const neg2B = new BigNumber(-2)
87+
8188
return typed(name, {
8289
number: gammaNumber,
8390
Complex: gammaComplex,
8491
BigNumber: function (n) {
85-
if (n.isInteger()) {
92+
// Handle special values here
93+
if (!n.isFinite()) {
94+
return new BigNumber(n.isNegative() ? NaN : Infinity)
95+
}
96+
if (isInteger(n)) {
8697
return (n.isNegative() || n.isZero())
8798
? new BigNumber(Infinity)
88-
: bigFactorial(n.minus(1))
99+
: bigFactorial(n.round().minus(1))
89100
}
90-
91-
if (!n.isFinite()) {
92-
return new BigNumber(n.isNegative() ? NaN : Infinity)
101+
let nhalf = n.minus(0.5)
102+
if (equal(nhalf, zeroB)) return sqrtpiB
103+
if (isInteger(nhalf)) {
104+
nhalf = nhalf.round() // in case there was roundoff error coming in
105+
let doubleFactorial = nhalf.abs().times(2).minus(1)
106+
// todo: replace following when factorial implementation finalized
107+
let factor = doubleFactorial.minus(2)
108+
while (factor.greaterThan(1)) {
109+
doubleFactorial = doubleFactorial.times(factor)
110+
factor = factor.minus(2)
111+
}
112+
if (nhalf.lessThan(0)) {
113+
return neg2B.pow(nhalf.abs()).times(sqrtpiB).div(doubleFactorial)
114+
}
115+
return doubleFactorial.times(sqrtpiB).div(twoB.pow(nhalf))
93116
}
94117

95118
return gammaBig(n)
@@ -101,8 +124,6 @@ export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({
101124
// but adapted to work only for real inputs.
102125
// Note for future the same core algorithm can be used for 1/gamma
103126
// or log-gamma, see details in the paper.
104-
const Pi = BigNumber.acos(-1)
105-
const halflog2p = Pi.times(2).ln().div(2)
106127
// Our goal is to compute relTol digits of gamma
107128
let digits = Math.abs(Math.log10(config.relTol))
108129
const bits = Math.min(digits * 10 / 3, 3)
@@ -118,7 +139,7 @@ export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({
118139
let inaccurate = true
119140
let mainsum = new BigNumber(0)
120141
let shifted = z.plus(r)
121-
console.log('Trying for', digits, 'with shift', r)
142+
// console.trace('Trying for', digits, 'with shift', r)
122143
while (inaccurate) {
123144
const needPrec = digits + 3 + Math.floor(
124145
shifted.ln().times(shifted).log().toNumber())
@@ -161,7 +182,7 @@ export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({
161182
}
162183
// OK, we should have an accurate mainsum
163184
const shiftGamma = mainsum
164-
.plus(halflog2p)
185+
.plus(halflog2piB)
165186
.minus(shifted)
166187
.plus(shifted.ln().times(shifted.minus(0.5)))
167188
// TODO: when implementation of rising factorial settled,
@@ -174,8 +195,8 @@ export const createGamma = /* #__PURE__ */ factory(name, dependencies, ({
174195
}
175196
let gamma
176197
if (reflect) {
177-
const angleFactor = n.mod(2).times(Pi).sin()
178-
gamma = shiftGamma.neg().exp().times(Pi).times(rising).div(angleFactor)
198+
const angleFactor = n.mod(2).times(piB).sin()
199+
gamma = shiftGamma.neg().exp().times(piB).times(rising).div(angleFactor)
179200
} else gamma = shiftGamma.exp().div(rising)
180201
return gamma.toDecimalPlaces(digits)
181202
}

0 commit comments

Comments
 (0)