commit
message
[SERIESSUM] work-in-progress, requires changes to Parser.ts to allow array token to follow coma
author
Ben Vogt <[email protected]>
date
2017-09-09 23:37:57
stats
6 file(s) changed,
62 insertions(+),
7 deletions(-)
files
TODO.md
src/Formulas/AllFormulas.ts
src/Formulas/Math.ts
src/Parser/Parser.ts
tests/Formulas/MathTest.ts
tests/SheetFormulaTest.ts
1diff --git a/TODO.md b/TODO.md
2index bba2802..a45b253 100644
3--- a/TODO.md
4+++ b/TODO.md
5@@ -16,6 +16,10 @@ For example `=#N/A` should force an error to be thrown inside the cell.
6 ### Parser/Sheet should be able to be initialized with js range notation (`[]`) or regular range notation (`{}`)
7
8
9+### Parser should be able to parse arrays without `eval`
10+Right now, arrays and reference literals in a formula are parsed using JS `eval`. This means, if we have references inside, or non-JS parsing values like TRUE or FALSE, they will cause ReferenceErrors. For example, `=SUM([M1, 10])` would throw `[ReferenceError: M1 is not defined]` because M1 is not a variables. Instead of using `eval`, we should parse the opening of an array, and the closeing of an array, and use recursion to see how deep we are, evaluating the tokens inside in the sam way we parse formulas and functions.
11+
12+
13 ### Meta-Formulas to write
14 Many of these formulas can be written by allowing the Sheet and Parser to return Cell objects in addition to primitive types. There are some memory issues with doing this. If a user calls something like `ISNA(A1:A99999)` we really only need the first cell. So we should return cell objects in some cases, but it would be easier in most cases to have context aware formulas, so if they need a cell, or a reference, we simply skip looking up a reference, and instead return a reference, or just a single cell. One way to do this would be to have formula functions, and then on the side have formula args. So before we lookup a large range of cells, we can check to see if it needs all of them, or if it just cares about the first one. So for `ISNA` we could look at `FormulaArgs.ISNA[0]` to get `Value` so we know that it needs only a single argument that is not an array, so if we call it with `ISNA(A1:A99999)`, it would really only lookup `A1`. This might be premature optimization however.
15
16@@ -31,7 +35,6 @@ Many of these formulas can be written by allowing the Sheet and Parser to return
17
18
19 ### Formulas to write
20-* SERIESSUM
21 * SUBTOTAL
22 * CRITBINOM
23 * F.DIST.RT
24diff --git a/src/Formulas/AllFormulas.ts b/src/Formulas/AllFormulas.ts
25index 800378f..c0d5276 100644
26--- a/src/Formulas/AllFormulas.ts
27+++ b/src/Formulas/AllFormulas.ts
28@@ -72,7 +72,8 @@ import {
29 MROUND,
30 FACTDOUBLE,
31 UNARY_PERCENT,
32- MULTINOMIAL
33+ MULTINOMIAL,
34+ SERIESSUM
35 } from "./Math";
36 import {
37 FREQUENCY,
38@@ -511,5 +512,6 @@ export {
39 ISFORMULA,
40 ADDRESS,
41 COLUMNS,
42- ROWS
43+ ROWS,
44+ SERIESSUM
45 }
46\ No newline at end of file
47diff --git a/src/Formulas/Math.ts b/src/Formulas/Math.ts
48index 667e1ef..bc535b6 100644
49--- a/src/Formulas/Math.ts
50+++ b/src/Formulas/Math.ts
51@@ -1383,6 +1383,31 @@ let MULTINOMIAL = function (...values) {
52 return _fact(sum) / divisor;
53 };
54
55+
56+/**
57+ * Returns a sum of powers of the number x in accordance with the following formula.
58+ * @param x - The number as an independent variable.
59+ * @param n - The starting power.
60+ * @param m - The number to increment by
61+ * @param coefficients - A series of coefficients. For each coefficient the series sum is extended by one section. You
62+ * can only enter coefficients using cell references.
63+ * @returns {number}
64+ * @constructor
65+ */
66+let SERIESSUM = function (x, n, m, coefficients) {
67+ ArgsChecker.checkLength(arguments, 4, "SERIESSUM");
68+ x = TypeConverter.firstValueAsNumber(x);
69+ n = TypeConverter.firstValueAsNumber(n);
70+ m = TypeConverter.firstValueAsNumber(m);
71+ coefficients = Filter.flattenAndThrow(coefficients).map(TypeConverter.valueToNumber);
72+ let result = coefficients[0] * Math.pow(x, n);
73+ for (let i = 1; i < coefficients.length; i++) {
74+ result += coefficients[i] * Math.pow(x, n + i * m);
75+ }
76+ return result;
77+};
78+
79+
80 export {
81 ABS,
82 ACOS,
83@@ -1457,5 +1482,6 @@ export {
84 MROUND,
85 FACTDOUBLE,
86 UNARY_PERCENT,
87- MULTINOMIAL
88+ MULTINOMIAL,
89+ SERIESSUM
90 }
91\ No newline at end of file
92diff --git a/src/Parser/Parser.ts b/src/Parser/Parser.ts
93index 6b2a9bd..c2a6f6c 100644
94--- a/src/Parser/Parser.ts
95+++ b/src/Parser/Parser.ts
96@@ -1240,7 +1240,7 @@ let Parser = (function () {
97 32, $V9,
98 34, $Va,
99 36, $Vb,
100- 12, $Vd,
101+ 12, $Vd
102 ]),
103 ObjectFromPairs.of([
104 2, 13,
105@@ -1261,6 +1261,9 @@ let Parser = (function () {
106 34, $Va,
107 36, $Vb,
108 12, $Vd,
109+ // NOTE: Ben this is where you are. When the parser captures an entire array, it should be able to reduce the
110+ // array, and continue on parsing normally. So we should have [LexActions.REDUCE, X]
111+ 29, [LexActions.REDUCE, 1]
112 ]),
113 extendRules($Vu, [LexActions.REDUCE, 42]),
114 extendRules($Vt, [LexActions.REDUCE, 34], ObjectFromPairs.of([
115diff --git a/tests/Formulas/MathTest.ts b/tests/Formulas/MathTest.ts
116index b28e3bc..2102b4b 100644
117--- a/tests/Formulas/MathTest.ts
118+++ b/tests/Formulas/MathTest.ts
119@@ -72,7 +72,8 @@ import {
120 MROUND,
121 FACTDOUBLE,
122 UNARY_PERCENT,
123- MULTINOMIAL
124+ MULTINOMIAL,
125+ SERIESSUM
126 } from "../../src/Formulas/Math";
127 import * as ERRORS from "../../src/Errors";
128 import {
129@@ -1513,4 +1514,19 @@ test("MULTINOMIAL", function(){
130 catchAndAssertEquals(function() {
131 MULTINOMIAL.apply(this, []);
132 }, ERRORS.NA_ERROR);
133+});
134+
135+
136+test("SERIESSUM", function(){
137+ assertEquals(SERIESSUM(1, 0, 1, [4, 5, 6]), 15);
138+ assertEquals(SERIESSUM(1, 0, 1, [4, 5, 6, 10, 22]), 47);
139+ assertEquals(SERIESSUM(3, 0, 2, [4, 5, 6]), 535);
140+ assertEquals(SERIESSUM(3, 0, 2, [4, 5, 6, 10]), 7825);
141+ assertEquals(SERIESSUM(3, 2, 2, [4, 5, 6, 10]), 70425);
142+ catchAndAssertEquals(function() {
143+ SERIESSUM.apply(this, [1, 0, 1, [4, 5, 6], 10])
144+ }, ERRORS.NA_ERROR);
145+ catchAndAssertEquals(function() {
146+ SERIESSUM.apply(this, [1, 0, 1])
147+ }, ERRORS.NA_ERROR);
148 });
149\ No newline at end of file
150diff --git a/tests/SheetFormulaTest.ts b/tests/SheetFormulaTest.ts
151index 2d99ed9..b9bd912 100644
152--- a/tests/SheetFormulaTest.ts
153+++ b/tests/SheetFormulaTest.ts
154@@ -1046,6 +1046,10 @@ test("Sheet ROWS", function(){
155 assertFormulaEquals('=ROWS(B1:M44)', 44);
156 });
157
158+test("Sheet SERIESSUM", function(){
159+ assertFormulaEquals('=SERIESSUM(1, 0, 1, [4, 5, 6])', 15);
160+});
161+
162 test("Sheet parsing error", function(){
163 assertFormulaEqualsError('= 10e', PARSE_ERROR);
164 assertFormulaEqualsError('= SUM(', PARSE_ERROR);