spreadsheet
typeScript/javascript spreadsheet parser, with formulas.
git clone https://git.vogt.world/spreadsheet.git
Log | Files | README.md
← Commit log
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.