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.FDIST$LEFTTAILED (aka F.DIST)
author
Ben Vogt <[email protected]>
date
2017-02-19 20:39:26
stats
3 file(s) changed, 183 insertions(+), 1 deletions(-)
files
src/RawFormulas/Math.ts
src/RawFormulas/RawFormulas.ts
tests/FormulasTest.ts
  1diff --git a/src/RawFormulas/Math.ts b/src/RawFormulas/Math.ts
  2index 7bbb51b..5118f85 100644
  3--- a/src/RawFormulas/Math.ts
  4+++ b/src/RawFormulas/Math.ts
  5@@ -1145,6 +1145,169 @@ var DEVSQ = function (...values) : number {
  6   return result;
  7 };
  8 
  9+/**
 10+ * Calculates the left-tailed F probability distribution (degree of diversity) for two data sets with given input x.
 11+ * Alternately called Fisher-Snedecor distribution or Snecdor's F distribution.
 12+ * @param values[0] x - The input to the F probability distribution function. The value at which to evaluate the function.
 13+ * Must be a positive number.
 14+ * @param values[1] degrees_freedom1 - The numerator degrees of freedom.
 15+ * @param values[2] degrees_freedom2 - The denominator degrees of freedom.
 16+ * @param values[3] cumulative - Logical value that determines the form of the function. If true returns the cumulative
 17+ * distribution function. If false returns the probability density function.
 18+ * @returns {number|undefined|boolean} left-tailed F probability distribution
 19+ * @constructor
 20+ * TODO: This function should be stricter in its return type.
 21+ */
 22+var FDIST$LEFTTAILED = function (...values) : number|undefined|boolean {
 23+  function gammaln(x) {
 24+    var j = 0;
 25+    var cof = [
 26+      76.18009172947146, -86.50532032941677, 24.01409824083091,
 27+      -1.231739572450155, 0.1208650973866179e-2, -0.5395239384953e-5
 28+    ];
 29+    var ser = 1.000000000190015;
 30+    var xx, y, tmp;
 31+    tmp = (y = xx = x) + 5.5;
 32+    tmp -= (xx + 0.5) * Math.log(tmp);
 33+    for (; j < 6; j++)
 34+      ser += cof[j] / ++y;
 35+    return Math.log(2.5066282746310005 * ser / xx) - tmp;
 36+  }
 37+  function gammafn(x) {
 38+    var p = [-1.716185138865495, 24.76565080557592, -379.80425647094563,
 39+      629.3311553128184, 866.9662027904133, -31451.272968848367,
 40+      -36144.413418691176, 66456.14382024054
 41+    ];
 42+    var q = [-30.8402300119739, 315.35062697960416, -1015.1563674902192,
 43+      -3107.771671572311, 22538.118420980151, 4755.8462775278811,
 44+      -134659.9598649693, -115132.2596755535];
 45+    var fact;
 46+    var n = 0;
 47+    var xden = 0;
 48+    var xnum = 0;
 49+    var y = x;
 50+    var i, z, yi, res;
 51+    if (y <= 0) {
 52+      res = y % 1 + 3.6e-16;
 53+      if (res) {
 54+        fact = (!(y & 1) ? 1 : -1) * Math.PI / Math.sin(Math.PI * res);
 55+        y = 1 - y;
 56+      } else {
 57+        return Infinity;
 58+      }
 59+    }
 60+    yi = y;
 61+    if (y < 1) {
 62+      z = y++;
 63+    } else {
 64+      z = (y -= n = (y | 0) - 1) - 1;
 65+    }
 66+    for (i = 0; i < 8; ++i) {
 67+      xnum = (xnum + p[i]) * z;
 68+      xden = xden * z + q[i];
 69+    }
 70+    res = xnum / xden + 1;
 71+    if (yi < y) {
 72+      res /= yi;
 73+    } else if (yi > y) {
 74+      for (i = 0; i < n; ++i) {
 75+        res *= y;
 76+        y++;
 77+      }
 78+    }
 79+    if (fact) {
 80+      res = fact / res;
 81+    }
 82+    return res;
 83+  }
 84+  function betacf(x, a, b) {
 85+    var fpmin = 1e-30;
 86+    var m = 1;
 87+    var qab = a + b;
 88+    var qap = a + 1;
 89+    var qam = a - 1;
 90+    var c = 1;
 91+    var d = 1 - qab * x / qap;
 92+    var m2, aa, del, h;
 93+
 94+    // These q's will be used in factors that occur in the coefficients
 95+    if (Math.abs(d) < fpmin)
 96+      d = fpmin;
 97+    d = 1 / d;
 98+    h = d;
 99+
100+    for (; m <= 100; m++) {
101+      m2 = 2 * m;
102+      aa = m * (b - m) * x / ((qam + m2) * (a + m2));
103+      // One step (the even one) of the recurrence
104+      d = 1 + aa * d;
105+      if (Math.abs(d) < fpmin)
106+        d = fpmin;
107+      c = 1 + aa / c;
108+      if (Math.abs(c) < fpmin)
109+        c = fpmin;
110+      d = 1 / d;
111+      h *= d * c;
112+      aa = -(a + m) * (qab + m) * x / ((a + m2) * (qap + m2));
113+      // Next step of the recurrence (the odd one)
114+      d = 1 + aa * d;
115+      if (Math.abs(d) < fpmin)
116+        d = fpmin;
117+      c = 1 + aa / c;
118+      if (Math.abs(c) < fpmin)
119+        c = fpmin;
120+      d = 1 / d;
121+      del = d * c;
122+      h *= del;
123+      if (Math.abs(del - 1.0) < 3e-7)
124+        break;
125+    }
126+
127+    return h;
128+  }
129+  function ibeta(x, a, b) {
130+    // Factors in front of the continued fraction.
131+    var bt = (x === 0 || x === 1) ?  0 :
132+      Math.exp(gammaln(a + b) - gammaln(a) -
133+        gammaln(b) + a * Math.log(x) + b *
134+        Math.log(1 - x));
135+    if (x < 0 || x > 1)
136+      return false;
137+    if (x < (a + 1) / (a + b + 2))
138+    // Use continued fraction directly.
139+      return bt * betacf(x, a, b) / a;
140+    // else use continued fraction after making the symmetry transformation.
141+    return 1 - bt * betacf(1 - x, b, a) / b;
142+  }
143+  function cdf(x, df1, df2) {
144+    return ibeta((df1 * x) / (df1 * x + df2), df1 / 2, df2 / 2);
145+  }
146+  function pdf(x, df1, df2) {
147+    if (x < 0) {
148+      return undefined;
149+    }
150+    return Math.sqrt((Math.pow(df1 * x, df1) * Math.pow(df2, df2)) /
151+        (Math.pow(df1 * x + df2, df1 + df2))) /
152+      (x * betafn(df1/2, df2/2));
153+  }
154+  function betafn(x, y) {
155+    // ensure arguments are positive
156+    if (x <= 0 || y <= 0)
157+      return undefined;
158+    // make sure x + y doesn't exceed the upper limit of usable values
159+    return (x + y > 170)  ? Math.exp(x/*TODO: y?*/) : gammafn(x) * gammafn(y) / gammafn(x + y);
160+  }
161+  ArgsChecker.checkLength(values, 4);
162+  var x = TypeCaster.firstValueAsNumber(values[0]);
163+  if (x < 0) {
164+    throw new CellError(ERRORS.NUM_ERROR, "Function F.DIST parameter 1 value is " + x + ". It should be greater than or equal to 0.");
165+  }
166+  var d1 = TypeCaster.firstValueAsNumber(values[1]);
167+  var d2 = TypeCaster.firstValueAsNumber(values[2]);
168+  var cumulative = TypeCaster.firstValueAsBoolean(values[3]);
169+  return (cumulative) ? cdf(x, d1, d2) : pdf(x, d1, d2);
170+};
171+
172 
173 export {
174   ABS,
175@@ -1166,6 +1329,7 @@ export {
176   COS,
177   DEVSQ,
178   EVEN,
179+  FDIST$LEFTTAILED,
180   INT,
181   ISEVEN,
182   ISODD,
183diff --git a/src/RawFormulas/RawFormulas.ts b/src/RawFormulas/RawFormulas.ts
184index 653e7ec..c98e1c2 100644
185--- a/src/RawFormulas/RawFormulas.ts
186+++ b/src/RawFormulas/RawFormulas.ts
187@@ -21,6 +21,7 @@ import {
188   COS,
189   DEVSQ,
190   EVEN,
191+  FDIST$LEFTTAILED,
192   INT,
193   ISEVEN,
194   ISODD,
195@@ -121,7 +122,7 @@ var EOMONTH = function (start_date, months) {
196 };
197 var EXPONDIST = Formula["EXPONDIST"];
198 var __COMPLEX = {
199-  "F.DIST": Formula["FDIST"],
200+  "F.DIST": FDIST$LEFTTAILED,
201   "F.INV": Formula["FINV"]
202 };
203 var YEARFRAC = Formula["YEARFRAC"];
204diff --git a/tests/FormulasTest.ts b/tests/FormulasTest.ts
205index 37c05e1..560f282 100644
206--- a/tests/FormulasTest.ts
207+++ b/tests/FormulasTest.ts
208@@ -968,8 +968,25 @@ assertEquals(EXPONDIST(4, 0.5, false), 0.06766764161830635);
209 
210 assertEquals(FALSE(), false);
211 
212+// Test F.DIST
213 assertEquals(__COMPLEX["F.DIST"](15.35, 7, 6, false), 0.0003451054686025578);
214 assertEquals(__COMPLEX["F.DIST"](15.35, 7, 6, true), 0.9980694465675269);
215+assertEquals(__COMPLEX["F.DIST"](15.35, 7, 6, 1), 0.9980694465675269);
216+assertEquals(__COMPLEX["F.DIST"](15.35, "7", [6], 1), 0.9980694465675269);
217+assertEquals(__COMPLEX["F.DIST"](15.35, "7", [6], 10), 0.9980694465675269);
218+catchAndAssertEquals(function() {
219+  __COMPLEX["F.DIST"](15.35, 7, 6, "10");
220+}, ERRORS.VALUE_ERROR);
221+catchAndAssertEquals(function() {
222+  __COMPLEX["F.DIST"](-15.35, 7, 6, 1);
223+}, ERRORS.NUM_ERROR);
224+catchAndAssertEquals(function() {
225+  __COMPLEX["F.DIST"](15.35, 7, 6);
226+}, ERRORS.NA_ERROR);
227+catchAndAssertEquals(function() {
228+  __COMPLEX["F.DIST"]();
229+}, ERRORS.NA_ERROR);
230+
231 
232 assertEquals(__COMPLEX["F.INV"](0.42, 2, 3), 0.6567804059458624);
233