Last active
March 17, 2021 14:31
-
-
Save MaxGraey/701a44fcc87ee1e9346b96912a597f5f to your computer and use it in GitHub Desktop.
Calc beta incomleate function by Lentz's algorithm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type i32 = number; | |
type f64 = number; | |
function isNeg(x: f64): boolean { | |
return x < 0.0 && x == Math.floor(x); | |
} | |
// Returns the value ln(gamma(x)). | |
function logGamma(x: f64): f64 { | |
let t: f64, r: f64; | |
t = x + 5.5; | |
t -= (x + 0.5) * Math.log(t); | |
r = 1.000000000190015; | |
r += 76.180091729471460e+0 / (x + 1.0); | |
r -= 86.505320329416770e+0 / (x + 2.0); | |
r += 24.014098240830910e+0 / (x + 3.0); | |
r -= 1.2317395724501550e+0 / (x + 4.0); | |
r += 0.1208650973866179e-2 / (x + 5.0); | |
r -= 0.5395239384953000e-5 / (x + 6.0); | |
return Math.log(2.5066282746310005 * r / x) - t; | |
// let d = [ | |
// 2.48574089138753565546e-5, | |
// 1.05142378581721974210e+0, | |
// -3.45687097222016235469e+0, | |
// 4.51227709466894823700e+0, | |
// -2.98285225323576655721e+0, | |
// 1.05639711577126713077e+0, | |
// -1.95428773191645869583e-1, | |
// 1.70970543404441224307e-2, | |
// -5.71926117404305781283e-4, | |
// 4.63399473359905636708e-6, | |
// -2.71994908488607703910e-9 | |
// ]; | |
// let s = d[0], z = x - 1.0; | |
// for (let k = 1; k <= 10; k++) { | |
// s += d[k] / (z + k); | |
// } | |
// z += 0.5; | |
// return Math.log(1.860382734205265717 * s) - z + z * Math.log1p(x + 9.400511); | |
} | |
function betainc(a: f64, b: f64, x: f64): f64 { | |
if (x < 0.0 || x > 1.0) return NaN; | |
if (isNeg(a) || isNeg(b) || isNeg(a + b)) return NaN; | |
const EPS = 1e-9; | |
if (x < EPS) return 0; | |
if (x > 1 - EPS) return 1; | |
if (a < EPS && b < EPS) return 0.5; | |
if (a < EPS) return 1; | |
if (b < EPS) return 0; | |
// According to https://dlmf.nist.gov/8.17#v, the continued fraction converges rapidly for x < (a + 1) / (a + b + 2) | |
// If x>=(a + 1) / (a + b + 2), we use Ix(a, b) = I(1 - x, b, a) | |
if (x > (a + 1.0) / (a + b + 2.0)) { | |
return 1.0 - betainc(b, a, 1.0 - x); | |
} | |
// We use the Lentz's algorithm to calculate the continued fraction | |
// f = 1 + d[1] / (1 + d[2] / (1 + d[3] / (...))) | |
// The implementation is based on the following formulas: | |
// u[0] = 1, v[0] = 0, f[0] = 1 | |
// u[i] = 1 + d[i] / u[i - 1] | |
// v[i] = 1 / (1 + d[i] * v[i - 1]) | |
// f[i] = f[i - 1] * u[i] * v[i] | |
const MAX_ITERS = 400; | |
const norm = (z: f64) => ( | |
Math.abs(z) < 1e-30 ? 1e-30 : z | |
); | |
function logBeta(a: f64, b: f64): f64 { | |
return logGamma(a) + logGamma(b) - logGamma(a + b); | |
} | |
let u = 2.0, v = 1.0, f = 2.0, d = 1.0; // d[0] | |
for (let i = 1; i <= MAX_ITERS; i++) { | |
let m = i >>> 1; | |
let am = a + m; | |
let am2 = am + m; | |
if ((i & 1) === 0) { | |
d = m * (b - m) * x / ((am2 - 1.0) * am2); // d[2m] | |
} else { | |
d = -(am * (am + b) * x) / (am2 * (am2 + 1.0)); // d[2m + 1] | |
} | |
u = norm(1.0 + d / u); | |
v = 1.0 / norm(1.0 + d * v); | |
let uv = u * v; | |
f *= uv; | |
if (Math.abs(uv - 1.0) <= Number.EPSILON) break; | |
} | |
// Ix(a, b) = x^a * (1-x)^b / (a*B(a, b)) * 1 / (1 + d[1] / (1 + d[2] / (1 + d[3] / (...)))) | |
return Math.exp(Math.log(x) * a + Math.log1p(-x) * b - logBeta(a, b)) / a * (f - 1.0); | |
} | |
function test(a: f64, b: f64, x: f64, expected: f64) { | |
let res = betainc(a, b, x); | |
console.log(`\nactual betainc(${a}, ${b}, ${x}) = ${res}`); | |
console.log(`expected betainc(${a}, ${b}, ${x}) = ${expected}`); | |
console.log(`|error| = ${ Math.abs((expected || 0) - (res || 0)).toExponential(1) }`); | |
} | |
test(0.5, 1.0, 0.0, 0.0); | |
test(0.5, 0.5, 1.0, 1.0); | |
test(1.0, 3.0, 0.5, 0.875); | |
test(1.4, 3.1, 0.5, 0.8148904036225296); | |
test(0.5, 5.0, 0.2, 0.8550723945959197); | |
test(0.5, 5.0, 0.5, 0.9898804402645662); | |
test(0.001, 0.3333, 0.1111, 0.9953402935460681); | |
test(0.999, 1.567, 1.46e-8, 2.3287434894343686e-08); | |
test(1.234e-8, 1.0, 0.1, 0.9999999715861003); | |
test(128.5, 3.0, 0.5, 4.4580244557709776e-36); | |
test(0.5, 4.0, 0.9999999, 0.9999999999999999); | |
test(20, 30, 0.001, 2.750687665855991e-47); | |
test(20, 30, 0.0001, 2.819953178893307e-67); | |
test(0.5, 33.0, 1.0000001, NaN); | |
/* Results: | |
actual betainc(0.5, 1, 0) = 0 | |
expected betainc(0.5, 1, 0) = 0 | |
|error| = 0.0e+0 | |
actual betainc(0.5, 0.5, 1) = 1 | |
expected betainc(0.5, 0.5, 1) = 1 | |
|error| = 0.0e+0 | |
actual betainc(1, 3, 0.5) = 0.8749999999999999 | |
expected betainc(1, 3, 0.5) = 0.875 | |
|error| = 1.1e-16 | |
actual betainc(1.4, 3.1, 0.5) = 0.8148904036225282 | |
expected betainc(1.4, 3.1, 0.5) = 0.8148904036225296 | |
|error| = 1.4e-15 | |
actual betainc(0.5, 5, 0.2) = 0.8550723945958834 | |
expected betainc(0.5, 5, 0.2) = 0.8550723945959197 | |
|error| = 3.6e-14 | |
actual betainc(0.5, 5, 0.5) = 0.9898804402645667 | |
expected betainc(0.5, 5, 0.5) = 0.9898804402645662 | |
|error| = 4.4e-16 | |
actual betainc(0.001, 0.3333, 0.1111) = 0.9953402935460702 | |
expected betainc(0.001, 0.3333, 0.1111) = 0.9953402935460681 | |
|error| = 2.1e-15 | |
actual betainc(0.999, 1.567, 1.46e-8) = 2.3287434894343845e-8 | |
expected betainc(0.999, 1.567, 1.46e-8) = 2.3287434894343686e-8 | |
|error| = 1.6e-22 | |
actual betainc(1.234e-8, 1, 0.1) = 0.9999999715861008 | |
expected betainc(1.234e-8, 1, 0.1) = 0.9999999715861003 | |
|error| = 4.4e-16 | |
actual betainc(128.5, 3, 0.5) = 4.458024455776908e-36 | |
expected betainc(128.5, 3, 0.5) = 4.4580244557709776e-36 | |
|error| = 5.9e-48 | |
actual betainc(0.5, 4, 0.9999999) = 1 | |
expected betainc(0.5, 4, 0.9999999) = 0.9999999999999999 | |
|error| = 1.1e-16 | |
actual betainc(20, 30, 0.001) = 2.7506876659140094e-47 | |
expected betainc(20, 30, 0.001) = 2.750687665855991e-47 | |
|error| = 5.8e-58 | |
actual betainc(20, 30, 0.0001) = 2.8199531789528524e-67 | |
expected betainc(20, 30, 0.0001) = 2.819953178893307e-67 | |
|error| = 6.0e-78 | |
actual betainc(0.5, 33, 1.0000001) = NaN | |
expected betainc(0.5, 33, 1.0000001) = NaN | |
|error| = 0.0e+0 | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment