fable 3.1.63 → 3.1.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/services/expression-parser-functions/README.md +9 -0
- package/docs/services/expression-parser-functions/beziercurvefit.md +57 -0
- package/docs/services/expression-parser-functions/bezierpoint.md +55 -0
- package/docs/services/expression-parser-functions/intercept.md +59 -0
- package/docs/services/expression-parser-functions/setvalue.md +55 -0
- package/docs/services/expression-parser-functions/slope.md +51 -0
- package/docs/services/expression-parser-functions/ternary.md +180 -0
- package/docs/services/expression-parser.md +131 -0
- package/example_applications/mathematical_playground/AppData.json +36 -1
- package/example_applications/mathematical_playground/Math-Solver-Harness.js +215 -0
- package/example_applications/mathematical_playground/TestSuite-AcidTest.json +178 -0
- package/example_applications/mathematical_playground/TestSuite-Identities.json +147 -0
- package/example_applications/mathematical_playground/TestSuite-Precision.json +127 -0
- package/example_applications/mathematical_playground/TestSuite-Spreadsheet.json +198 -0
- package/package.json +3 -2
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer-DirectiveMutation.js +299 -2
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-ExpressionTokenizer.js +1 -1
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-FunctionMap.json +5 -4
- package/source/services/Fable-Service-ExpressionParser/Fable-Service-ExpressionParser-TokenMap.json +65 -1
- package/source/services/Fable-Service-ExpressionParser.js +144 -0
- package/source/services/Fable-Service-Logic.js +33 -0
- package/source/services/Fable-Service-Math.js +78 -0
- package/test/ExpressionParser_tests.js +1059 -0
|
@@ -115,4 +115,219 @@ console.log(` Intercept (baseline): ${tmpSlopeInterceptResults.TrendIntercept}`
|
|
|
115
115
|
_ExpressionParser.solve('Month7Prediction = TrendIntercept + TrendSlope * 7', tmpSlopeInterceptResults, tmpSlopeInterceptResults, false, tmpSlopeInterceptResults);
|
|
116
116
|
console.log(` Predicted month 7 revenue: ${tmpSlopeInterceptResults.Month7Prediction}`);
|
|
117
117
|
|
|
118
|
+
/* * * * * * * * * * * * * * * * *
|
|
119
|
+
*
|
|
120
|
+
* MULTIROWMAP: Multi-Row Lookback Series Solver
|
|
121
|
+
*
|
|
122
|
+
* MULTIROWMAP lets you iterate over an array of objects (rows) and reference
|
|
123
|
+
* values from the current row, previous rows, and even future rows.
|
|
124
|
+
*
|
|
125
|
+
* Syntax:
|
|
126
|
+
* Result = MULTIROWMAP ROWS FROM <address>
|
|
127
|
+
* [SERIESSTART <n>]
|
|
128
|
+
* [SERIESSTEP <n>]
|
|
129
|
+
* VAR <name> FROM <property> [OFFSET <n>] [DEFAULT <value>]
|
|
130
|
+
* ...
|
|
131
|
+
* : <expression>
|
|
132
|
+
*
|
|
133
|
+
* OFFSET 0 (default) = current row
|
|
134
|
+
* OFFSET -1 = previous row, OFFSET -2 = two rows back, etc.
|
|
135
|
+
* OFFSET 1 = next row, OFFSET 2 = two rows ahead, etc.
|
|
136
|
+
* DEFAULT provides a fallback when the referenced row is out of bounds.
|
|
137
|
+
* SERIESSTART controls which row index to start iterating from (negative counts from end).
|
|
138
|
+
* SERIESSTEP controls the iteration step (default 1, use -1 to go backwards).
|
|
139
|
+
*
|
|
140
|
+
*/
|
|
141
|
+
_Fable.log.info(`Beginning MULTIROWMAP Exercises....`);
|
|
142
|
+
|
|
143
|
+
let tmpMultiRowResults = {};
|
|
144
|
+
|
|
145
|
+
// --- Example 1: Fibonacci Verification ---
|
|
146
|
+
// Verify that each value in a Fibonacci sequence equals the sum of the two previous values
|
|
147
|
+
_ExpressionParser.solve(
|
|
148
|
+
'FibCheck = MULTIROWMAP ROWS FROM FibonacciRows VAR Current FROM Fib VAR Prev1 FROM Fib OFFSET -1 DEFAULT 0 VAR Prev2 FROM Fib OFFSET -2 DEFAULT 0 : Current - (Prev1 + Prev2)',
|
|
149
|
+
_AppData, {}, false, tmpMultiRowResults);
|
|
150
|
+
console.log(`\nFibonacci verification (should be 0 from row 2 onward):`);
|
|
151
|
+
console.log(` FibCheck: [${tmpMultiRowResults.FibCheck.join(', ')}]`);
|
|
152
|
+
|
|
153
|
+
// --- Example 2: Stock Price Day-over-Day Change ---
|
|
154
|
+
// Calculate the daily price change for a stock
|
|
155
|
+
_ExpressionParser.solve(
|
|
156
|
+
'DailyChange = MULTIROWMAP ROWS FROM StockPrices VAR Today FROM Close VAR Yesterday FROM Close OFFSET -1 DEFAULT 0 : Today - Yesterday',
|
|
157
|
+
_AppData, {}, false, tmpMultiRowResults);
|
|
158
|
+
console.log(`\nStock daily price change:`);
|
|
159
|
+
console.log(` DailyChange: [${tmpMultiRowResults.DailyChange.join(', ')}]`);
|
|
160
|
+
|
|
161
|
+
// --- Example 3: 3-Period Moving Average ---
|
|
162
|
+
// Compute a 3-day moving average of stock closing prices
|
|
163
|
+
_ExpressionParser.solve(
|
|
164
|
+
'MA3 = MULTIROWMAP ROWS FROM StockPrices VAR c0 FROM Close VAR c1 FROM Close OFFSET -1 DEFAULT 0 VAR c2 FROM Close OFFSET -2 DEFAULT 0 : (c0 + c1 + c2) / 3',
|
|
165
|
+
_AppData, {}, false, tmpMultiRowResults);
|
|
166
|
+
console.log(`\n3-period moving average of stock prices:`);
|
|
167
|
+
console.log(` MA3: [${tmpMultiRowResults.MA3.join(', ')}]`);
|
|
168
|
+
|
|
169
|
+
// --- Example 4: Forward-Looking (Next Row Reference) ---
|
|
170
|
+
// For each temperature reading, compute the difference to the next reading
|
|
171
|
+
_ExpressionParser.solve(
|
|
172
|
+
'TempChange = MULTIROWMAP ROWS FROM TemperatureReadings VAR Current FROM Temp VAR Next FROM Temp OFFSET 1 DEFAULT 0 : Next - Current',
|
|
173
|
+
_AppData, {}, false, tmpMultiRowResults);
|
|
174
|
+
console.log(`\nTemperature change to next reading (forward lookback):`);
|
|
175
|
+
console.log(` TempChange: [${tmpMultiRowResults.TempChange.join(', ')}]`);
|
|
176
|
+
|
|
177
|
+
// --- Example 5: Central Difference (Both Directions) ---
|
|
178
|
+
// Approximate the derivative using central difference: (next - prev) / 2
|
|
179
|
+
_ExpressionParser.solve(
|
|
180
|
+
'TempDerivative = MULTIROWMAP ROWS FROM TemperatureReadings VAR Prev FROM Temp OFFSET -1 DEFAULT 0 VAR Next FROM Temp OFFSET 1 DEFAULT 0 : (Next - Prev) / 2',
|
|
181
|
+
_AppData, {}, false, tmpMultiRowResults);
|
|
182
|
+
console.log(`\nTemperature central difference derivative:`);
|
|
183
|
+
console.log(` TempDerivative: [${tmpMultiRowResults.TempDerivative.join(', ')}]`);
|
|
184
|
+
|
|
185
|
+
// --- Example 6: Second Derivative (Two-Row Lookback) ---
|
|
186
|
+
// Approximate the second derivative: current - 2*prev + twoprev
|
|
187
|
+
_ExpressionParser.solve(
|
|
188
|
+
'TempAcceleration = MULTIROWMAP ROWS FROM TemperatureReadings VAR Current FROM Temp VAR Prev FROM Temp OFFSET -1 DEFAULT 0 VAR TwoBack FROM Temp OFFSET -2 DEFAULT 0 : Current - 2 * Prev + TwoBack',
|
|
189
|
+
_AppData, {}, false, tmpMultiRowResults);
|
|
190
|
+
console.log(`\nTemperature second derivative (acceleration):`);
|
|
191
|
+
console.log(` TempAcceleration: [${tmpMultiRowResults.TempAcceleration.join(', ')}]`);
|
|
192
|
+
|
|
193
|
+
// --- Example 7: SERIESSTART - Skip Initial Rows ---
|
|
194
|
+
// Start the moving average from row 2 so all three periods have valid data
|
|
195
|
+
_ExpressionParser.solve(
|
|
196
|
+
'MA3Valid = MULTIROWMAP ROWS FROM StockPrices SERIESSTART 2 VAR c0 FROM Close VAR c1 FROM Close OFFSET -1 DEFAULT 0 VAR c2 FROM Close OFFSET -2 DEFAULT 0 : (c0 + c1 + c2) / 3',
|
|
197
|
+
_AppData, {}, false, tmpMultiRowResults);
|
|
198
|
+
console.log(`\n3-period MA starting from row 2 (all periods valid):`);
|
|
199
|
+
console.log(` MA3Valid: [${tmpMultiRowResults.MA3Valid.join(', ')}]`);
|
|
200
|
+
|
|
201
|
+
// --- Example 8: SERIESSTEP -1 - Iterate Backwards ---
|
|
202
|
+
// Read stock prices in reverse chronological order
|
|
203
|
+
_ExpressionParser.solve(
|
|
204
|
+
'ReversePrices = MULTIROWMAP ROWS FROM StockPrices SERIESSTART -1 SERIESSTEP -1 VAR Price FROM Close : Price + 0',
|
|
205
|
+
_AppData, {}, false, tmpMultiRowResults);
|
|
206
|
+
console.log(`\nStock prices in reverse order:`);
|
|
207
|
+
console.log(` ReversePrices: [${tmpMultiRowResults.ReversePrices.join(', ')}]`);
|
|
208
|
+
|
|
209
|
+
// --- Example 9: SERIESSTEP 2 - Every Other Row ---
|
|
210
|
+
// Sample every other temperature reading
|
|
211
|
+
_ExpressionParser.solve(
|
|
212
|
+
'EveryOtherTemp = MULTIROWMAP ROWS FROM TemperatureReadings SERIESSTEP 2 VAR t FROM Temp : t + 0',
|
|
213
|
+
_AppData, {}, false, tmpMultiRowResults);
|
|
214
|
+
console.log(`\nEvery other temperature reading:`);
|
|
215
|
+
console.log(` EveryOtherTemp: [${tmpMultiRowResults.EveryOtherTemp.join(', ')}]`);
|
|
216
|
+
|
|
217
|
+
// --- Example 10: Three-Row Lookback with Multiple Properties ---
|
|
218
|
+
// Weighted volume change: compare current volume to 3 days ago, scaled by price ratio
|
|
219
|
+
_ExpressionParser.solve(
|
|
220
|
+
'WeightedVolChange = MULTIROWMAP ROWS FROM StockPrices VAR CurVol FROM Volume VAR CurPrice FROM Close VAR OldVol FROM Volume OFFSET -3 DEFAULT 0 VAR OldPrice FROM Close OFFSET -3 DEFAULT 1 : (CurVol - OldVol) * (CurPrice / OldPrice)',
|
|
221
|
+
_AppData, {}, false, tmpMultiRowResults);
|
|
222
|
+
console.log(`\nWeighted volume change (3-day lookback with price ratio):`);
|
|
223
|
+
console.log(` WeightedVolChange: [${tmpMultiRowResults.WeightedVolChange.join(', ')}]`);
|
|
224
|
+
|
|
118
225
|
console.log('QED');
|
|
226
|
+
|
|
227
|
+
/* * * * * * * * * * * * * * * * *
|
|
228
|
+
*
|
|
229
|
+
* Third-Party Test Suite Runner
|
|
230
|
+
*
|
|
231
|
+
* Run with: node Math-Solver-Harness.js --test-suite <name>
|
|
232
|
+
* Valid names: identities, spreadsheet, precision, acidtest, all
|
|
233
|
+
*
|
|
234
|
+
*/
|
|
235
|
+
let SUITE_MAP =
|
|
236
|
+
{
|
|
237
|
+
'identities': 'TestSuite-Identities.json',
|
|
238
|
+
'spreadsheet': 'TestSuite-Spreadsheet.json',
|
|
239
|
+
'precision': 'TestSuite-Precision.json',
|
|
240
|
+
'acidtest': 'TestSuite-AcidTest.json'
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
let tmpRequestedSuites = [];
|
|
244
|
+
for (let i = 0; i < process.argv.length; i++)
|
|
245
|
+
{
|
|
246
|
+
if (process.argv[i] === '--test-suite' && i + 1 < process.argv.length)
|
|
247
|
+
{
|
|
248
|
+
let tmpSuiteName = process.argv[i + 1].toLowerCase();
|
|
249
|
+
if (tmpSuiteName === 'all')
|
|
250
|
+
{
|
|
251
|
+
tmpRequestedSuites = Object.keys(SUITE_MAP);
|
|
252
|
+
}
|
|
253
|
+
else if (tmpSuiteName in SUITE_MAP)
|
|
254
|
+
{
|
|
255
|
+
tmpRequestedSuites.push(tmpSuiteName);
|
|
256
|
+
}
|
|
257
|
+
else
|
|
258
|
+
{
|
|
259
|
+
console.log(`\x1b[31mUnknown test suite: ${tmpSuiteName}\x1b[0m`);
|
|
260
|
+
console.log(`Valid suites: ${Object.keys(SUITE_MAP).join(', ')}, all`);
|
|
261
|
+
}
|
|
262
|
+
i++;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (tmpRequestedSuites.length > 0)
|
|
267
|
+
{
|
|
268
|
+
let tmpTotalPass = 0;
|
|
269
|
+
let tmpTotalFail = 0;
|
|
270
|
+
let tmpSuiteSummaries = [];
|
|
271
|
+
|
|
272
|
+
for (let s = 0; s < tmpRequestedSuites.length; s++)
|
|
273
|
+
{
|
|
274
|
+
let tmpSuiteKey = tmpRequestedSuites[s];
|
|
275
|
+
let tmpSuiteData = require(`./${SUITE_MAP[tmpSuiteKey]}`);
|
|
276
|
+
let tmpSuitePass = 0;
|
|
277
|
+
let tmpSuiteFail = 0;
|
|
278
|
+
let tmpCurrentCategory = '';
|
|
279
|
+
|
|
280
|
+
console.log(`\n\x1b[1m=== ${tmpSuiteData.Name} ===\x1b[0m`);
|
|
281
|
+
console.log(`${tmpSuiteData.Description}\n`);
|
|
282
|
+
|
|
283
|
+
for (let i = 0; i < tmpSuiteData.Expressions.length; i++)
|
|
284
|
+
{
|
|
285
|
+
let tmpTest = tmpSuiteData.Expressions[i];
|
|
286
|
+
if (tmpTest.Category && tmpTest.Category !== tmpCurrentCategory)
|
|
287
|
+
{
|
|
288
|
+
tmpCurrentCategory = tmpTest.Category;
|
|
289
|
+
console.log(` \x1b[4m${tmpCurrentCategory}\x1b[0m`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
let tmpData = tmpTest.Data || _AppData;
|
|
293
|
+
let tmpResultObject = {};
|
|
294
|
+
let tmpResultValue;
|
|
295
|
+
try
|
|
296
|
+
{
|
|
297
|
+
tmpResultValue = _ExpressionParser.solve(tmpTest.Equation, tmpData, tmpResultObject, false);
|
|
298
|
+
}
|
|
299
|
+
catch (pError)
|
|
300
|
+
{
|
|
301
|
+
tmpResultValue = `ERROR: ${pError.message}`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
let tmpPassed = (String(tmpResultValue) === String(tmpTest.ExpectedResult));
|
|
305
|
+
if (tmpPassed)
|
|
306
|
+
{
|
|
307
|
+
tmpSuitePass++;
|
|
308
|
+
console.log(` \x1b[32m PASS\x1b[0m ${tmpTest.Description}`);
|
|
309
|
+
}
|
|
310
|
+
else
|
|
311
|
+
{
|
|
312
|
+
tmpSuiteFail++;
|
|
313
|
+
console.log(` \x1b[31m FAIL\x1b[0m ${tmpTest.Description}`);
|
|
314
|
+
console.log(` Expected: ${tmpTest.ExpectedResult}`);
|
|
315
|
+
console.log(` Got: ${tmpResultValue}`);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
tmpTotalPass += tmpSuitePass;
|
|
320
|
+
tmpTotalFail += tmpSuiteFail;
|
|
321
|
+
tmpSuiteSummaries.push({ Name: tmpSuiteData.Name, Pass: tmpSuitePass, Fail: tmpSuiteFail, Total: tmpSuitePass + tmpSuiteFail });
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
console.log(`\n\x1b[1m=== Test Suite Summary ===\x1b[0m`);
|
|
325
|
+
for (let i = 0; i < tmpSuiteSummaries.length; i++)
|
|
326
|
+
{
|
|
327
|
+
let tmpSummary = tmpSuiteSummaries[i];
|
|
328
|
+
let tmpColor = tmpSummary.Fail > 0 ? '\x1b[31m' : '\x1b[32m';
|
|
329
|
+
console.log(` ${tmpColor}${tmpSummary.Pass}/${tmpSummary.Total}\x1b[0m ${tmpSummary.Name}`);
|
|
330
|
+
}
|
|
331
|
+
let tmpOverallColor = tmpTotalFail > 0 ? '\x1b[31m' : '\x1b[32m';
|
|
332
|
+
console.log(`\n ${tmpOverallColor}Total: ${tmpTotalPass}/${tmpTotalPass + tmpTotalFail} passed\x1b[0m`);
|
|
333
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Name": "Parser Acid Test",
|
|
3
|
+
"Description": "Stress tests for operator precedence, parenthesis handling, comparison operators, and ternary syntax. Catches common parser bugs.",
|
|
4
|
+
"Expressions":
|
|
5
|
+
[
|
|
6
|
+
{
|
|
7
|
+
"Category": "PEMDAS / Order of Operations",
|
|
8
|
+
"Description": "Multiplication before addition: 2 + 3*4 = 14",
|
|
9
|
+
"Equation": "Result = 2 + 3 * 4",
|
|
10
|
+
"ExpectedResult": "14"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"Category": "PEMDAS / Order of Operations",
|
|
14
|
+
"Description": "Parentheses override: (2+3)*4 = 20",
|
|
15
|
+
"Equation": "Result = (2 + 3) * 4",
|
|
16
|
+
"ExpectedResult": "20"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"Category": "PEMDAS / Order of Operations",
|
|
20
|
+
"Description": "Exponent before multiply: 2^3 * 4 = 32",
|
|
21
|
+
"Equation": "Result = 2^3 * 4",
|
|
22
|
+
"ExpectedResult": "32"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"Category": "PEMDAS / Order of Operations",
|
|
26
|
+
"Description": "Mixed all precedences: 2^3 + 4*5 - 6/3 = 26",
|
|
27
|
+
"Equation": "Result = 2^3 + 4 * 5 - 6 / 3",
|
|
28
|
+
"ExpectedResult": "26"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"Category": "PEMDAS / Order of Operations",
|
|
32
|
+
"Description": "Division before subtraction: 100 - 50 / 5 = 90",
|
|
33
|
+
"Equation": "Result = 100 - 50 / 5",
|
|
34
|
+
"ExpectedResult": "90"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"Category": "Parenthesis Nesting",
|
|
38
|
+
"Description": "Deep nesting: ((((1+2)*3)-4)/5) = 1",
|
|
39
|
+
"Equation": "Result = ((((1 + 2) * 3) - 4) / 5)",
|
|
40
|
+
"ExpectedResult": "1"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"Category": "Parenthesis Nesting",
|
|
44
|
+
"Description": "Redundant parentheses: ((((42)))) = 42",
|
|
45
|
+
"Equation": "Result = ((((42))))",
|
|
46
|
+
"ExpectedResult": "42"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"Category": "Left-to-Right Associativity",
|
|
50
|
+
"Description": "Subtraction is left-to-right: 10 - 3 - 2 = 5",
|
|
51
|
+
"Equation": "Result = 10 - 3 - 2",
|
|
52
|
+
"ExpectedResult": "5"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"Category": "Left-to-Right Associativity",
|
|
56
|
+
"Description": "Division is left-to-right: 20 / 4 / 5 = 1",
|
|
57
|
+
"Equation": "Result = 20 / 4 / 5",
|
|
58
|
+
"ExpectedResult": "1"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"Category": "Negative Numbers",
|
|
62
|
+
"Description": "(-3)^2 = 9 (parenthesized negative base)",
|
|
63
|
+
"Equation": "Result = (-3)^2",
|
|
64
|
+
"ExpectedResult": "9"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"Category": "Negative Numbers",
|
|
68
|
+
"Description": "Addition with negative: 4 + -3 = 1",
|
|
69
|
+
"Equation": "Result = 4 + -3",
|
|
70
|
+
"ExpectedResult": "1"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"Category": "Exponentiation",
|
|
74
|
+
"Description": "2^3^2 = 64 (left-to-right in this parser)",
|
|
75
|
+
"Equation": "Result = 2^3^2",
|
|
76
|
+
"ExpectedResult": "64"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"Category": "Comparison Operators",
|
|
80
|
+
"Description": "Greater than true: 5 > 3 = 1",
|
|
81
|
+
"Equation": "Result = 5 > 3",
|
|
82
|
+
"ExpectedResult": "1"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"Category": "Comparison Operators",
|
|
86
|
+
"Description": "Greater than false: 3 > 5 = 0",
|
|
87
|
+
"Equation": "Result = 3 > 5",
|
|
88
|
+
"ExpectedResult": "0"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"Category": "Comparison Operators",
|
|
92
|
+
"Description": "Equal true: 5 == 5 = 1",
|
|
93
|
+
"Equation": "Result = 5 == 5",
|
|
94
|
+
"ExpectedResult": "1"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"Category": "Comparison Operators",
|
|
98
|
+
"Description": "Not equal false: 5 != 5 = 0",
|
|
99
|
+
"Equation": "Result = 5 != 5",
|
|
100
|
+
"ExpectedResult": "0"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"Category": "Comparison Operators",
|
|
104
|
+
"Description": "GTE boundary: 3 >= 3 = 1",
|
|
105
|
+
"Equation": "Result = 3 >= 3",
|
|
106
|
+
"ExpectedResult": "1"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"Category": "Comparison Operators",
|
|
110
|
+
"Description": "LTE false: 3 <= 2 = 0",
|
|
111
|
+
"Equation": "Result = 3 <= 2",
|
|
112
|
+
"ExpectedResult": "0"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"Category": "Comparison Operators",
|
|
116
|
+
"Description": "Negative comparison: (-3) < 0 = 1",
|
|
117
|
+
"Equation": "Result = (-3) < 0",
|
|
118
|
+
"ExpectedResult": "1"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"Category": "Comparison Precedence",
|
|
122
|
+
"Description": "Arithmetic before comparison: 2+3 > 1+2 = 1",
|
|
123
|
+
"Equation": "Result = 2 + 3 > 1 + 2",
|
|
124
|
+
"ExpectedResult": "1"
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"Category": "Ternary Operator",
|
|
128
|
+
"Description": "True branch: 5>3 ? 10+20 :: 30+40 = 30",
|
|
129
|
+
"Equation": "Result = 5 > 3 ? 10 + 20 :: 30 + 40",
|
|
130
|
+
"ExpectedResult": "30"
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
"Category": "Ternary Operator",
|
|
134
|
+
"Description": "False branch: 3>5 ? 10+20 :: 30+40 = 70",
|
|
135
|
+
"Equation": "Result = 3 > 5 ? 10 + 20 :: 30 + 40",
|
|
136
|
+
"ExpectedResult": "70"
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"Category": "Ternary Operator",
|
|
140
|
+
"Description": "Nested ternary: 1>0 ? (2>3 ? 100 :: 200) :: 300 = 200",
|
|
141
|
+
"Equation": "Result = 1 > 0 ? (2 > 3 ? 100 :: 200) :: 300",
|
|
142
|
+
"ExpectedResult": "200"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"Category": "Ternary Operator",
|
|
146
|
+
"Description": "Ternary with variables (absolute value pattern)",
|
|
147
|
+
"Equation": "Result = X > 0 ? X :: 0 - X",
|
|
148
|
+
"ExpectedResult": "5",
|
|
149
|
+
"Data": { "X": -5 }
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
"Category": "Ternary Operator",
|
|
153
|
+
"Description": "Ternary with variables (positive input)",
|
|
154
|
+
"Equation": "Result = X > 0 ? X :: 0 - X",
|
|
155
|
+
"ExpectedResult": "5",
|
|
156
|
+
"Data": { "X": 5 }
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
"Category": "Ternary Operator",
|
|
160
|
+
"Description": "Equality comparison in ternary",
|
|
161
|
+
"Equation": "Result = 0 == 0 ? 1 :: 0",
|
|
162
|
+
"ExpectedResult": "1"
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
"Category": "Functions with Comparisons",
|
|
166
|
+
"Description": "Function result in comparison via variable",
|
|
167
|
+
"Equation": "Result = SUM(Vals) > 5",
|
|
168
|
+
"ExpectedResult": "1",
|
|
169
|
+
"Data": { "Vals": [1, 2, 3] }
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"Category": "Complex Expressions",
|
|
173
|
+
"Description": "All arithmetic operators in one expression",
|
|
174
|
+
"Equation": "Result = (10 + 5) * 2 - 8 / 4 + 3^2 % 5",
|
|
175
|
+
"ExpectedResult": "37"
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Name": "Mathematical Identities",
|
|
3
|
+
"Description": "Classic mathematical identities from NIST DLMF and standard references. Tests correctness of trig, logarithmic, and algebraic operations.",
|
|
4
|
+
"Expressions":
|
|
5
|
+
[
|
|
6
|
+
{
|
|
7
|
+
"Category": "Pythagorean Identity",
|
|
8
|
+
"Description": "sin^2(30) + cos^2(30) = 1",
|
|
9
|
+
"Equation": "Result = ROUND(sin(rad(30))^2 + cos(rad(30))^2, 10)",
|
|
10
|
+
"ExpectedResult": "1"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"Category": "Pythagorean Identity",
|
|
14
|
+
"Description": "sin^2(45) + cos^2(45) = 1",
|
|
15
|
+
"Equation": "Result = ROUND(sin(rad(45))^2 + cos(rad(45))^2, 10)",
|
|
16
|
+
"ExpectedResult": "1"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"Category": "Pythagorean Identity",
|
|
20
|
+
"Description": "sin^2(60) + cos^2(60) = 1",
|
|
21
|
+
"Equation": "Result = ROUND(sin(rad(60))^2 + cos(rad(60))^2, 10)",
|
|
22
|
+
"ExpectedResult": "1"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"Category": "Double Angle Formula",
|
|
26
|
+
"Description": "2*sin(30)*cos(30) = sin(60)",
|
|
27
|
+
"Equation": "Result = ROUND(2 * sin(rad(30)) * cos(rad(30)), 10)",
|
|
28
|
+
"ExpectedResult": "0.8660254038"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"Category": "Double Angle Formula",
|
|
32
|
+
"Description": "sin(60) directly for comparison",
|
|
33
|
+
"Equation": "Result = ROUND(sin(rad(60)), 10)",
|
|
34
|
+
"ExpectedResult": "0.8660254038"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"Category": "Tangent Identity",
|
|
38
|
+
"Description": "tan(45) = 1",
|
|
39
|
+
"Equation": "Result = ROUND(tan(rad(45)), 10)",
|
|
40
|
+
"ExpectedResult": "1"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"Category": "Power and Root",
|
|
44
|
+
"Description": "sqrt(x)^2 = x (within rounding)",
|
|
45
|
+
"Equation": "Result = ROUND(sqrt(7)^2, 10)",
|
|
46
|
+
"ExpectedResult": "7"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"Category": "Power and Root",
|
|
50
|
+
"Description": "sqrt(144) = 12 (perfect square)",
|
|
51
|
+
"Equation": "Result = sqrt(144)",
|
|
52
|
+
"ExpectedResult": "12"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"Category": "Power and Root",
|
|
56
|
+
"Description": "x^0 = 1 for any nonzero x",
|
|
57
|
+
"Equation": "Result = 5^0",
|
|
58
|
+
"ExpectedResult": "1"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"Category": "Power and Root",
|
|
62
|
+
"Description": "x^1 = x",
|
|
63
|
+
"Equation": "Result = 7^1",
|
|
64
|
+
"ExpectedResult": "7"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"Category": "Power and Root",
|
|
68
|
+
"Description": "x^2 * x^3 = x^5 (exponent addition)",
|
|
69
|
+
"Equation": "Result = 2^2 * 2^3",
|
|
70
|
+
"ExpectedResult": "32"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"Category": "Algebraic Identity",
|
|
74
|
+
"Description": "(a+b)(a-b) = a^2 - b^2 (difference of squares)",
|
|
75
|
+
"Equation": "Result = (A + B) * (A - B)",
|
|
76
|
+
"ExpectedResult": "40",
|
|
77
|
+
"Data": { "A": 7, "B": 3 }
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"Category": "Algebraic Identity",
|
|
81
|
+
"Description": "a^2 - b^2 directly for comparison",
|
|
82
|
+
"Equation": "Result = A^2 - B^2",
|
|
83
|
+
"ExpectedResult": "40",
|
|
84
|
+
"Data": { "A": 7, "B": 3 }
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"Category": "Algebraic Identity",
|
|
88
|
+
"Description": "(a+b)^2 = a^2 + 2ab + b^2",
|
|
89
|
+
"Equation": "Result = (3 + 4)^2",
|
|
90
|
+
"ExpectedResult": "49"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"Category": "Algebraic Identity",
|
|
94
|
+
"Description": "a^2 + 2ab + b^2 directly for comparison",
|
|
95
|
+
"Equation": "Result = 3^2 + 2 * 3 * 4 + 4^2",
|
|
96
|
+
"ExpectedResult": "49"
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"Category": "Reciprocal Identity",
|
|
100
|
+
"Description": "1/(1/x) = x (within rounding)",
|
|
101
|
+
"Equation": "Result = ROUND(1 / (1 / 7), 10)",
|
|
102
|
+
"ExpectedResult": "7"
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"Category": "Constants",
|
|
106
|
+
"Description": "Pi starts with 3.14159265",
|
|
107
|
+
"Equation": "Result = ROUND(pi(), 8)",
|
|
108
|
+
"ExpectedResult": "3.14159265"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"Category": "Constants",
|
|
112
|
+
"Description": "Euler's number starts with 2.71828183",
|
|
113
|
+
"Equation": "Result = ROUND(euler(), 8)",
|
|
114
|
+
"ExpectedResult": "2.71828183"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"Category": "Logarithmic Identity",
|
|
118
|
+
"Description": "log(1) = 0",
|
|
119
|
+
"Equation": "Result = log(1)",
|
|
120
|
+
"ExpectedResult": "0.000000000"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"Category": "Logarithmic Identity",
|
|
124
|
+
"Description": "exp(0) = 1",
|
|
125
|
+
"Equation": "Result = exp(0)",
|
|
126
|
+
"ExpectedResult": "1"
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"Category": "Additive Identity",
|
|
130
|
+
"Description": "x + 0 = x",
|
|
131
|
+
"Equation": "Result = 42 + 0",
|
|
132
|
+
"ExpectedResult": "42"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"Category": "Multiplicative Identity",
|
|
136
|
+
"Description": "x * 1 = x",
|
|
137
|
+
"Equation": "Result = 42 * 1",
|
|
138
|
+
"ExpectedResult": "42"
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
"Category": "Multiplicative Zero",
|
|
142
|
+
"Description": "x * 0 = 0",
|
|
143
|
+
"Equation": "Result = 42 * 0",
|
|
144
|
+
"ExpectedResult": "0"
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Name": "Numeric Precision Stress Tests",
|
|
3
|
+
"Description": "Tests that catch IEEE 754 floating-point errors. The expression parser uses big.js for arbitrary precision, so these should all pass cleanly.",
|
|
4
|
+
"Expressions":
|
|
5
|
+
[
|
|
6
|
+
{
|
|
7
|
+
"Category": "IEEE 754 Traps",
|
|
8
|
+
"Description": "0.1 + 0.2 = 0.3 (classic IEEE 754 failure)",
|
|
9
|
+
"Equation": "Result = 0.1 + 0.2",
|
|
10
|
+
"ExpectedResult": "0.3"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"Category": "IEEE 754 Traps",
|
|
14
|
+
"Description": "0.3 - 0.1 = 0.2",
|
|
15
|
+
"Equation": "Result = 0.3 - 0.1",
|
|
16
|
+
"ExpectedResult": "0.2"
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"Category": "IEEE 754 Traps",
|
|
20
|
+
"Description": "0.7 + 0.1 = 0.8",
|
|
21
|
+
"Equation": "Result = 0.7 + 0.1",
|
|
22
|
+
"ExpectedResult": "0.8"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"Category": "IEEE 754 Traps",
|
|
26
|
+
"Description": "-0.1 + -0.2 = -0.3",
|
|
27
|
+
"Equation": "Result = -0.1 + -0.2",
|
|
28
|
+
"ExpectedResult": "-0.3"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"Category": "IEEE 754 Traps",
|
|
32
|
+
"Description": "1.0 - 0.9 - 0.1 = 0 (catastrophic cancellation)",
|
|
33
|
+
"Equation": "Result = 1.0 - 0.9 - 0.1",
|
|
34
|
+
"ExpectedResult": "0"
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"Category": "Large Numbers",
|
|
38
|
+
"Description": "Large integer multiplication",
|
|
39
|
+
"Equation": "Result = 99999999999999 * 99999999999999",
|
|
40
|
+
"ExpectedResult": "9.999999999999800000000000001e+27"
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"Category": "Large Numbers",
|
|
44
|
+
"Description": "Large integer addition",
|
|
45
|
+
"Equation": "Result = 123456789012345 + 987654321098765",
|
|
46
|
+
"ExpectedResult": "1111111110111110"
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"Category": "Large Numbers",
|
|
50
|
+
"Description": "2^53 exact (JavaScript MAX_SAFE_INTEGER + 1)",
|
|
51
|
+
"Equation": "Result = 2^53",
|
|
52
|
+
"ExpectedResult": "9007199254740992"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"Category": "Small Number Precision",
|
|
56
|
+
"Description": "Subtraction preserving small difference",
|
|
57
|
+
"Equation": "Result = 1000000.001 - 1000000",
|
|
58
|
+
"ExpectedResult": "0.001"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"Category": "Small Number Precision",
|
|
62
|
+
"Description": "Very small number multiplication",
|
|
63
|
+
"Equation": "Result = 0.000001 * 0.000001",
|
|
64
|
+
"ExpectedResult": "1e-12"
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"Category": "Division Precision",
|
|
68
|
+
"Description": "1/3 * 3 approaches 1 (repeating decimal)",
|
|
69
|
+
"Equation": "Result = ROUND(1 / 3 * 3, 10)",
|
|
70
|
+
"ExpectedResult": "1"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"Category": "Division Precision",
|
|
74
|
+
"Description": "Large number / same number = 1 (within rounding)",
|
|
75
|
+
"Equation": "Result = ROUND(1000000 / 7 * 7, 10)",
|
|
76
|
+
"ExpectedResult": "1000000"
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"Category": "Division Precision",
|
|
80
|
+
"Description": "1 / 1000000 = 0.000001",
|
|
81
|
+
"Equation": "Result = 1 / 1000000",
|
|
82
|
+
"ExpectedResult": "0.000001"
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
"Category": "Chained Operations",
|
|
86
|
+
"Description": "Add then subtract should cancel exactly",
|
|
87
|
+
"Equation": "Result = ((((1 + 0.0001) - 0.0001) * 10000) / 10000)",
|
|
88
|
+
"ExpectedResult": "1"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"Category": "Modulus",
|
|
92
|
+
"Description": "10 % 3 = 1",
|
|
93
|
+
"Equation": "Result = 10 % 3",
|
|
94
|
+
"ExpectedResult": "1"
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"Category": "Modulus",
|
|
98
|
+
"Description": "7.5 % 2.5 = 0 (exact division)",
|
|
99
|
+
"Equation": "Result = 7.5 % 2.5",
|
|
100
|
+
"ExpectedResult": "0"
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"Category": "Power Precision",
|
|
104
|
+
"Description": "3^7 = 2187",
|
|
105
|
+
"Equation": "Result = 3^7",
|
|
106
|
+
"ExpectedResult": "2187"
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"Category": "Power Precision",
|
|
110
|
+
"Description": "10^10 = 10000000000",
|
|
111
|
+
"Equation": "Result = 10^10",
|
|
112
|
+
"ExpectedResult": "10000000000"
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
"Category": "Negative Number Precision",
|
|
116
|
+
"Description": "(-1)^2 = 1",
|
|
117
|
+
"Equation": "Result = (-1)^2",
|
|
118
|
+
"ExpectedResult": "1"
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
"Category": "Negative Number Precision",
|
|
122
|
+
"Description": "(-2) * (-3) = 6",
|
|
123
|
+
"Equation": "Result = (-2) * (-3)",
|
|
124
|
+
"ExpectedResult": "6"
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|