commit
message
Added Formulas.FINV
author
Ben Vogt <[email protected]>
date
2017-02-19 21:07:56
stats
4 file(s) changed,
162 insertions(+),
8 deletions(-)
files
src/RawFormulas/Math.ts
src/RawFormulas/RawFormulas.ts
tests/FormulasTest.ts
tests/SheetFormulaTest.ts
1diff --git a/src/RawFormulas/Math.ts b/src/RawFormulas/Math.ts
2index 55ef2d8..daf72f2 100644
3--- a/src/RawFormulas/Math.ts
4+++ b/src/RawFormulas/Math.ts
5@@ -1291,12 +1291,15 @@ var FDIST$LEFTTAILED = function (...values) : number|undefined|boolean {
6 (Math.pow(df1 * x + df2, df1 + df2))) /
7 (x * betafn(df1/2, df2/2));
8 }
9+ function betaln(x, y) {
10+ return gammaln(x) + gammaln(y) - gammaln(x + y);
11+ }
12 function betafn(x, y) {
13 // ensure arguments are positive
14 if (x <= 0 || y <= 0)
15 return undefined;
16 // make sure x + y doesn't exceed the upper limit of usable values
17- return (x + y > 170) ? Math.exp(x/*TODO: y?*/) : gammafn(x) * gammafn(y) / gammafn(x + y);
18+ return (x + y > 170) ? Math.exp(betaln(x, y)) : gammafn(x) * gammafn(y) / gammafn(x + y);
19 }
20 ArgsChecker.checkLength(values, 4);
21 var x = TypeCaster.firstValueAsNumber(values[0]);
22@@ -1374,6 +1377,152 @@ function erf(x) {
23 }
24
25
26+/**
27+ * Returns the inverse of the (right-tailed) F probability distribution. If p = FDIST(x,...), then FINV(p,...) = x. The
28+ * F distribution can be used in an F-test that compares the degree of variability in two data sets.
29+ * @param values[0] probability - A probability associated with the F cumulative distribution.
30+ * @param values[1] deg_freedom1 - Required. The numerator degrees of freedom.
31+ * @param values[2] deg_freedom2 - Required. The denominator degrees of freedom.
32+ * @returns {number} inverse of the (right-tailed) F probability distribution
33+ * @constructor
34+ */
35+var FINV = function (...values) : number {
36+ function betacf(x, a, b) {
37+ var fpmin = 1e-30;
38+ var m = 1;
39+ var qab = a + b;
40+ var qap = a + 1;
41+ var qam = a - 1;
42+ var c = 1;
43+ var d = 1 - qab * x / qap;
44+ var m2, aa, del, h;
45+
46+ // These q's will be used in factors that occur in the coefficients
47+ if (Math.abs(d) < fpmin)
48+ d = fpmin;
49+ d = 1 / d;
50+ h = d;
51+
52+ for (; m <= 100; m++) {
53+ m2 = 2 * m;
54+ aa = m * (b - m) * x / ((qam + m2) * (a + m2));
55+ // One step (the even one) of the recurrence
56+ d = 1 + aa * d;
57+ if (Math.abs(d) < fpmin)
58+ d = fpmin;
59+ c = 1 + aa / c;
60+ if (Math.abs(c) < fpmin)
61+ c = fpmin;
62+ d = 1 / d;
63+ h *= d * c;
64+ aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
65+ // Next step of the recurrence (the odd one)
66+ d = 1 + aa * d;
67+ if (Math.abs(d) < fpmin)
68+ d = fpmin;
69+ c = 1 + aa / c;
70+ if (Math.abs(c) < fpmin)
71+ c = fpmin;
72+ d = 1 / d;
73+ del = d * c;
74+ h *= del;
75+ if (Math.abs(del - 1.0) < 3e-7)
76+ break;
77+ }
78+
79+ return h;
80+ }
81+ function ibeta(x, a, b) : number {
82+ // Factors in front of the continued fraction.
83+ var bt = (x === 0 || x === 1) ? 0 :
84+ Math.exp(gammaln(a + b) - gammaln(a) -
85+ gammaln(b) + a * Math.log(x) + b *
86+ Math.log(1 - x));
87+ if (x < 0 || x > 1)
88+ // WARNING: I changed this to 0, because TS complains about doing numerical operations on boolean values.
89+ // Still safe in javascript, but not TS.
90+ return 0;
91+ if (x < (a + 1) / (a + b + 2))
92+ // Use continued fraction directly.
93+ return bt * betacf(x, a, b) / a;
94+ // else use continued fraction after making the symmetry transformation.
95+ return 1 - bt * betacf(1 - x, b, a) / b;
96+ }
97+ function gammaln(x) {
98+ var j = 0;
99+ var cof = [
100+ 76.18009172947146, -86.50532032941677, 24.01409824083091,
101+ -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5
102+ ];
103+ var ser = 1.000000000190015;
104+ var xx, y, tmp;
105+ tmp = (y = xx = x) + 5.5;
106+ tmp -= (xx + 0.5) * Math.log(tmp);
107+ for (; j < 6; j++)
108+ ser += cof[j] / ++y;
109+ return Math.log(2.5066282746310005 * ser / xx) - tmp;
110+ }
111+ function ibetainv(p, a, b) {
112+ var EPS = 1e-8;
113+ var a1 = a - 1;
114+ var b1 = b - 1;
115+ var j = 0;
116+ var lna, lnb, pp, t, u, err, x, al, h, w, afac;
117+ if (p <= 0)
118+ return 0;
119+ if (p >= 1)
120+ return 1;
121+ if (a >= 1 && b >= 1) {
122+ pp = (p < 0.5) ? p : 1 - p;
123+ t = Math.sqrt(-2 * Math.log(pp));
124+ x = (2.30753 + t * 0.27061) / (1 + t* (0.99229 + t * 0.04481)) - t;
125+ if (p < 0.5)
126+ x = -x;
127+ al = (x * x - 3) / 6;
128+ h = 2 / (1 / (2 * a - 1) + 1 / (2 * b - 1));
129+ w = (x * Math.sqrt(al + h) / h) - (1 / (2 * b - 1) - 1 / (2 * a - 1)) *
130+ (al + 5 / 6 - 2 / (3 * h));
131+ x = a / (a + b * Math.exp(2 * w));
132+ } else {
133+ lna = Math.log(a / (a + b));
134+ lnb = Math.log(b / (a + b));
135+ t = Math.exp(a * lna) / a;
136+ u = Math.exp(b * lnb) / b;
137+ w = t + u;
138+ if (p < t / w)
139+ x = Math.pow(a * w * p, 1 / a);
140+ else
141+ x = 1 - Math.pow(b * w * (1 - p), 1 / b);
142+ }
143+ afac = -gammaln(a) - gammaln(b) + gammaln(a + b);
144+ for(; j < 10; j++) {
145+ if (x === 0 || x === 1)
146+ return x;
147+ err = ibeta(x, a, b) - p;
148+ t = Math.exp(a1 * Math.log(x) + b1 * Math.log(1 - x) + afac);
149+ u = err / t;
150+ x -= (t = u / (1 - 0.5 * Math.min(1, u * (a1 / x - b1 / (1 - x)))));
151+ if (x <= 0)
152+ x = 0.5 * (x + t);
153+ if (x >= 1)
154+ x = 0.5 * (x + t + 1);
155+ if (Math.abs(t) < EPS * x && j > 0)
156+ break;
157+ }
158+ return x;
159+ }
160+ function inv(x, df1, df2) {
161+ return df2 / (df1 * (1 / ibetainv(x, df1 / 2, df2 / 2) - 1));
162+ }
163+ ArgsChecker.checkLength(values, 3);
164+ var probability = TypeCaster.firstValueAsNumber(values[0]);
165+ if (probability <= 0.0 || probability > 1.0) {
166+ // TODO: Throw num error.
167+ }
168+ var d1 = TypeCaster.firstValueAsNumber(values[1]);
169+ var d2 = TypeCaster.firstValueAsNumber(values[2]);
170+ return inv(1.0 - probability, d1, d2);
171+};
172
173 /**
174 * Calculates the depreciation of an asset for a specified period using the double-declining balance method.
175@@ -1674,6 +1823,7 @@ export {
176 ERF,
177 ERFC,
178 FDIST$LEFTTAILED,
179+ FINV,
180 FISHER,
181 FISHERINV,
182 INT,
183diff --git a/src/RawFormulas/RawFormulas.ts b/src/RawFormulas/RawFormulas.ts
184index 28896e6..34d5201 100644
185--- a/src/RawFormulas/RawFormulas.ts
186+++ b/src/RawFormulas/RawFormulas.ts
187@@ -27,6 +27,7 @@ import {
188 ERF,
189 ERFC,
190 FDIST$LEFTTAILED,
191+ FINV,
192 FISHER,
193 FISHERINV,
194 INT,
195@@ -131,8 +132,7 @@ var EOMONTH = function (start_date, months) {
196 };
197 var EXPONDIST = Formula["EXPONDIST"];
198 var __COMPLEX = {
199- "F.DIST": FDIST$LEFTTAILED,
200- "F.INV": Formula["FINV"]
201+ "F.DIST": FDIST$LEFTTAILED
202 };
203 var YEARFRAC = Formula["YEARFRAC"];
204
205@@ -206,6 +206,7 @@ export {
206 EXACT,
207 EXPONDIST,
208 FALSE,
209+ FINV,
210 FISHER,
211 FISHERINV,
212 FLOOR,
213diff --git a/tests/FormulasTest.ts b/tests/FormulasTest.ts
214index 560f282..6698913 100644
215--- a/tests/FormulasTest.ts
216+++ b/tests/FormulasTest.ts
217@@ -4,7 +4,7 @@ import { ABS, ACCRINT, ACOS, ACOSH, ACOTH, AND, ARABIC, ASIN, ASINH, ATAN, ATAN2
218 CORREL, COS, PI, COSH, COT, COTH, COUNT, COUNTA, COUNTIF, COUNTIFS, COUNTUNIQUE,
219 COVARIANCEP, COVARIANCES, CUMIPMT, CUMPRINC, DATE, DATEVALUE, DAY, DAYS, DAYS360,
220 DB, DDB, DEC2BIN, DEC2HEX, DEC2OCT, DEGREES, DELTA, DEVSQ, DOLLAR, DOLLARDE, DOLLARFR, EDATE,
221- EFFECT, EOMONTH, ERF, ERFC, EVEN, EXACT, EXPONDIST, FALSE, FLOOR, __COMPLEX, FISHER, FISHERINV, IF,
222+ EFFECT, EOMONTH, ERF, ERFC, EVEN, EXACT, EXPONDIST, FINV, FALSE, FLOOR, __COMPLEX, FISHER, FISHERINV, IF,
223 INT, ISEVEN, ISODD, LN, LOG, LOG10, MAX, MAXA, MEDIAN, MIN, MINA, MOD, NOT, TRUE, ODD, OR,
224 POWER, ROUND, ROUNDDOWN, ROUNDUP, SIN, SINH, SPLIT, SQRT, SQRTPI, SUM, SUMIF, SUMPRODUCT, RADIANS,
225 SUMSQ, SUMX2MY2, SUMX2PY2, TAN, TANH, TRUNC, XOR, YEARFRAC } from "../src/RawFormulas/RawFormulas"
226@@ -988,7 +988,8 @@ catchAndAssertEquals(function() {
227 }, ERRORS.NA_ERROR);
228
229
230-assertEquals(__COMPLEX["F.INV"](0.42, 2, 3), 0.6567804059458624);
231+// Test FINV
232+assertEquals(FINV(0.42, 2, 3), 1.174597274485816);
233
234
235 // Test FISHER
236diff --git a/tests/SheetFormulaTest.ts b/tests/SheetFormulaTest.ts
237index 7449915..69ea836 100644
238--- a/tests/SheetFormulaTest.ts
239+++ b/tests/SheetFormulaTest.ts
240@@ -261,10 +261,10 @@ testFormula('=F.DIST(15.35, 7, 6, true)', 0.9980694465675269);
241 * F.DIST is left-tailed. FDIST is right-tailed.
242 */
243
244-// Test F.INV
245-testFormula('=F.INV(0.42, 2, 3)', 0.6567804059458624);
246-
247 // Test FINV
248+testFormula('=FINV(0.42, 2, 3)', 1.174597274485816);
249+
250+// Test F.INV
251 // TODO: This should work.
252 /*
253 * FINV Calculates the inverse of the right-tailed F probability distribution. Also called the Fisher-Snedecor distribution or Snedecor’s F distribution.