@cj-tech-master/excelts 9.3.1 → 9.4.0
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/dist/browser/index.d.ts +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/modules/excel/cell.d.ts +18 -0
- package/dist/browser/modules/excel/cell.js +21 -0
- package/dist/browser/modules/excel/utils/cell-format.js +85 -13
- package/dist/browser/modules/excel/workbook.browser.d.ts +57 -0
- package/dist/browser/modules/excel/workbook.browser.js +49 -0
- package/dist/browser/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/browser/modules/formula/compile/binder.js +48 -6
- package/dist/browser/modules/formula/compile/bound-ast.d.ts +16 -2
- package/dist/browser/modules/formula/compile/bound-ast.js +1 -0
- package/dist/browser/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/browser/modules/formula/functions/_shared.d.ts +19 -0
- package/dist/browser/modules/formula/functions/_shared.js +47 -0
- package/dist/browser/modules/formula/functions/conditional.js +103 -22
- package/dist/browser/modules/formula/functions/date.js +105 -23
- package/dist/browser/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/browser/modules/formula/functions/engineering.d.ts +2 -2
- package/dist/browser/modules/formula/functions/engineering.js +103 -151
- package/dist/browser/modules/formula/functions/financial.js +210 -184
- package/dist/browser/modules/formula/functions/lookup.js +224 -157
- package/dist/browser/modules/formula/functions/math.d.ts +26 -0
- package/dist/browser/modules/formula/functions/math.js +249 -69
- package/dist/browser/modules/formula/functions/statistical.js +221 -171
- package/dist/browser/modules/formula/functions/text.js +112 -52
- package/dist/browser/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/browser/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/browser/modules/formula/materialize/types.d.ts +15 -0
- package/dist/browser/modules/formula/runtime/evaluator.d.ts +8 -0
- package/dist/browser/modules/formula/runtime/evaluator.js +582 -162
- package/dist/browser/modules/formula/runtime/function-registry.d.ts +5 -0
- package/dist/browser/modules/formula/runtime/function-registry.js +59 -13
- package/dist/browser/modules/formula/runtime/values.d.ts +13 -0
- package/dist/browser/modules/formula/runtime/values.js +20 -2
- package/dist/browser/modules/formula/syntax/ast.d.ts +14 -2
- package/dist/browser/modules/formula/syntax/ast.js +1 -0
- package/dist/browser/modules/formula/syntax/parser.js +29 -7
- package/dist/browser/modules/formula/syntax/token-types.d.ts +4 -0
- package/dist/browser/modules/formula/syntax/token-types.js +9 -0
- package/dist/browser/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/modules/excel/cell.js +21 -0
- package/dist/cjs/modules/excel/utils/cell-format.js +85 -13
- package/dist/cjs/modules/excel/workbook.browser.js +49 -0
- package/dist/cjs/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/cjs/modules/formula/compile/binder.js +48 -6
- package/dist/cjs/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/cjs/modules/formula/functions/_shared.js +48 -0
- package/dist/cjs/modules/formula/functions/conditional.js +103 -22
- package/dist/cjs/modules/formula/functions/date.js +104 -22
- package/dist/cjs/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/cjs/modules/formula/functions/engineering.js +109 -157
- package/dist/cjs/modules/formula/functions/financial.js +209 -183
- package/dist/cjs/modules/formula/functions/lookup.js +224 -157
- package/dist/cjs/modules/formula/functions/math.js +254 -70
- package/dist/cjs/modules/formula/functions/statistical.js +222 -172
- package/dist/cjs/modules/formula/functions/text.js +112 -52
- package/dist/cjs/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/cjs/modules/formula/runtime/evaluator.js +581 -161
- package/dist/cjs/modules/formula/runtime/function-registry.js +57 -11
- package/dist/cjs/modules/formula/runtime/values.js +21 -2
- package/dist/cjs/modules/formula/syntax/parser.js +29 -7
- package/dist/cjs/modules/formula/syntax/token-types.js +9 -0
- package/dist/cjs/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/esm/index.js +2 -0
- package/dist/esm/modules/excel/cell.js +21 -0
- package/dist/esm/modules/excel/utils/cell-format.js +85 -13
- package/dist/esm/modules/excel/workbook.browser.js +49 -0
- package/dist/esm/modules/excel/xlsx/defaultnumformats.js +3 -3
- package/dist/esm/modules/formula/compile/binder.js +48 -6
- package/dist/esm/modules/formula/compile/bound-ast.js +1 -0
- package/dist/esm/modules/formula/compile/compiled-formula.js +41 -8
- package/dist/esm/modules/formula/functions/_shared.js +47 -0
- package/dist/esm/modules/formula/functions/conditional.js +103 -22
- package/dist/esm/modules/formula/functions/date.js +105 -23
- package/dist/esm/modules/formula/functions/dynamic-array.js +173 -69
- package/dist/esm/modules/formula/functions/engineering.js +103 -151
- package/dist/esm/modules/formula/functions/financial.js +210 -184
- package/dist/esm/modules/formula/functions/lookup.js +224 -157
- package/dist/esm/modules/formula/functions/math.js +249 -69
- package/dist/esm/modules/formula/functions/statistical.js +221 -171
- package/dist/esm/modules/formula/functions/text.js +112 -52
- package/dist/esm/modules/formula/integration/calculate-formulas-impl.js +20 -1
- package/dist/esm/modules/formula/materialize/build-writeback-plan.js +10 -6
- package/dist/esm/modules/formula/runtime/evaluator.js +582 -162
- package/dist/esm/modules/formula/runtime/function-registry.js +59 -13
- package/dist/esm/modules/formula/runtime/values.js +20 -2
- package/dist/esm/modules/formula/syntax/ast.js +1 -0
- package/dist/esm/modules/formula/syntax/parser.js +29 -7
- package/dist/esm/modules/formula/syntax/token-types.js +9 -0
- package/dist/esm/modules/formula/syntax/tokenizer.js +76 -19
- package/dist/iife/excelts.iife.js +1502 -1379
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +26 -26
- package/dist/types/index.d.ts +1 -0
- package/dist/types/modules/excel/cell.d.ts +18 -0
- package/dist/types/modules/excel/workbook.browser.d.ts +57 -0
- package/dist/types/modules/formula/compile/bound-ast.d.ts +16 -2
- package/dist/types/modules/formula/functions/_shared.d.ts +19 -0
- package/dist/types/modules/formula/functions/engineering.d.ts +2 -2
- package/dist/types/modules/formula/functions/math.d.ts +26 -0
- package/dist/types/modules/formula/materialize/types.d.ts +15 -0
- package/dist/types/modules/formula/runtime/evaluator.d.ts +8 -0
- package/dist/types/modules/formula/runtime/function-registry.d.ts +5 -0
- package/dist/types/modules/formula/runtime/values.d.ts +13 -0
- package/dist/types/modules/formula/syntax/ast.d.ts +14 -2
- package/dist/types/modules/formula/syntax/token-types.d.ts +4 -0
- package/package.json +1 -1
|
@@ -78,16 +78,14 @@ function quickselect(arr, k) {
|
|
|
78
78
|
return arr[k];
|
|
79
79
|
}
|
|
80
80
|
const fnMEDIAN = args => {
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return err;
|
|
81
|
+
const values = toNumberArray(args);
|
|
82
|
+
if (!Array.isArray(values)) {
|
|
83
|
+
return values;
|
|
85
84
|
}
|
|
86
|
-
|
|
85
|
+
const n = values.length;
|
|
86
|
+
if (n === 0) {
|
|
87
87
|
return values_1.ERRORS.NUM;
|
|
88
88
|
}
|
|
89
|
-
const values = nums.map(n => n.value);
|
|
90
|
-
const n = values.length;
|
|
91
89
|
const mid = n >> 1;
|
|
92
90
|
if (n % 2 !== 0) {
|
|
93
91
|
return (0, values_1.rvNumber)(quickselect(values, mid));
|
|
@@ -119,6 +117,12 @@ const fnLARGE = args => {
|
|
|
119
117
|
// with the same shape where each cell holds LARGE at that k.
|
|
120
118
|
if (args[1].kind === 5 /* RVKind.Array */) {
|
|
121
119
|
const kArr = args[1];
|
|
120
|
+
// Sort once when the array contains more than a single cell — k is
|
|
121
|
+
// O(1) afterwards. quickselect per-cell would allocate a fresh
|
|
122
|
+
// `values.slice()` for every k, which quickly becomes quadratic
|
|
123
|
+
// when LARGE(A1:A100, {1;2;3;…}) evaluates over long ranges.
|
|
124
|
+
const totalCells = kArr.height * kArr.width;
|
|
125
|
+
const sortedDesc = totalCells > 1 ? values.slice().sort((a, b) => b - a) : null;
|
|
122
126
|
const outRows = [];
|
|
123
127
|
for (const row of kArr.rows) {
|
|
124
128
|
const outRow = [];
|
|
@@ -137,10 +141,13 @@ const fnLARGE = args => {
|
|
|
137
141
|
outRow.push(values_1.ERRORS.NUM);
|
|
138
142
|
continue;
|
|
139
143
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
if (sortedDesc) {
|
|
145
|
+
outRow.push((0, values_1.rvNumber)(sortedDesc[kInt - 1]));
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Single-cell k — quickselect is cheaper than a full sort.
|
|
149
|
+
outRow.push((0, values_1.rvNumber)(quickselect(values.slice(), values.length - kInt)));
|
|
150
|
+
}
|
|
144
151
|
}
|
|
145
152
|
outRows.push(outRow);
|
|
146
153
|
}
|
|
@@ -166,6 +173,10 @@ const fnSMALL = args => {
|
|
|
166
173
|
const values = nums.map(n => n.value);
|
|
167
174
|
if (args[1].kind === 5 /* RVKind.Array */) {
|
|
168
175
|
const kArr = args[1];
|
|
176
|
+
// Multi-cell k → sort once ascending, index in O(1). See LARGE for
|
|
177
|
+
// the same optimisation and rationale.
|
|
178
|
+
const totalCells = kArr.height * kArr.width;
|
|
179
|
+
const sortedAsc = totalCells > 1 ? values.slice().sort((a, b) => a - b) : null;
|
|
169
180
|
const outRows = [];
|
|
170
181
|
for (const row of kArr.rows) {
|
|
171
182
|
const outRow = [];
|
|
@@ -184,7 +195,12 @@ const fnSMALL = args => {
|
|
|
184
195
|
outRow.push(values_1.ERRORS.NUM);
|
|
185
196
|
continue;
|
|
186
197
|
}
|
|
187
|
-
|
|
198
|
+
if (sortedAsc) {
|
|
199
|
+
outRow.push((0, values_1.rvNumber)(sortedAsc[kInt - 1]));
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
outRow.push((0, values_1.rvNumber)(quickselect(values.slice(), kInt - 1)));
|
|
203
|
+
}
|
|
188
204
|
}
|
|
189
205
|
outRows.push(outRow);
|
|
190
206
|
}
|
|
@@ -238,30 +254,39 @@ exports.fnRANK = fnRANK;
|
|
|
238
254
|
* Returns `null` when there is no data at all (callers decide whether that
|
|
239
255
|
* should be `#DIV/0!` or zero given the sample/population convention).
|
|
240
256
|
*/
|
|
257
|
+
/**
|
|
258
|
+
* Single-pass mean + sum-of-squared-deviations using Welford's online
|
|
259
|
+
* algorithm. Returns `null` when the array is empty.
|
|
260
|
+
*
|
|
261
|
+
* Welford's recurrence keeps the sum of squared deviations numerically
|
|
262
|
+
* stable (avoids the catastrophic cancellation that bites
|
|
263
|
+
* `Σx² - (Σx)²/n` for datasets with a large mean and small variance).
|
|
264
|
+
* Costs one pass rather than two — meaningful on multi-thousand-cell
|
|
265
|
+
* ranges feeding STDEV / VAR / their variants.
|
|
266
|
+
*/
|
|
241
267
|
function computeMeanAndSumSq(nums) {
|
|
242
268
|
const n = nums.length;
|
|
243
269
|
if (n === 0) {
|
|
244
270
|
return null;
|
|
245
271
|
}
|
|
246
|
-
let
|
|
247
|
-
for (const v of nums) {
|
|
248
|
-
sum += v;
|
|
249
|
-
}
|
|
250
|
-
const mean = sum / n;
|
|
272
|
+
let mean = 0;
|
|
251
273
|
let sumSq = 0;
|
|
252
|
-
for (
|
|
253
|
-
|
|
274
|
+
for (let i = 0; i < n; i++) {
|
|
275
|
+
const x = nums[i];
|
|
276
|
+
const delta = x - mean;
|
|
277
|
+
mean += delta / (i + 1);
|
|
278
|
+
// `x - mean` uses the *updated* mean — this product form is the
|
|
279
|
+
// identity that makes Welford's recurrence equal the second-pass
|
|
280
|
+
// `(x - final_mean)²` sum exactly.
|
|
281
|
+
sumSq += delta * (x - mean);
|
|
254
282
|
}
|
|
255
283
|
return { n, mean, sumSq };
|
|
256
284
|
}
|
|
257
285
|
/** Resolve {args} to `number[]` or an error. Shared by STDEV/VAR family. */
|
|
258
286
|
function toNumberArray(args) {
|
|
259
|
-
const
|
|
260
|
-
const err = (0, _shared_1.
|
|
261
|
-
|
|
262
|
-
return err;
|
|
263
|
-
}
|
|
264
|
-
return rawNums.map(n => n.value);
|
|
287
|
+
const out = [];
|
|
288
|
+
const err = (0, _shared_1.forEachNumber)(args, n => out.push(n));
|
|
289
|
+
return err ?? out;
|
|
265
290
|
}
|
|
266
291
|
const fnSTDEV = args => {
|
|
267
292
|
const nums = toNumberArray(args);
|
|
@@ -470,9 +495,16 @@ exports.fnNORMINV = fnNORMINV;
|
|
|
470
495
|
// PERCENTILE, QUARTILE, MODE
|
|
471
496
|
// ============================================================================
|
|
472
497
|
const fnPERCENTILE = args => {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
498
|
+
// Propagate errors from within the range — `flattenNumbers` returns
|
|
499
|
+
// both numbers and errors, and previously we silently filtered errors
|
|
500
|
+
// out by `.kind === Number`, producing a bogus percentile on ranges
|
|
501
|
+
// that contained `#N/A`. Surface the error like AVERAGE / SUM do.
|
|
502
|
+
const raw = (0, _shared_1.flattenNumbers)([args[0]]);
|
|
503
|
+
const err = (0, _shared_1.firstError)(raw);
|
|
504
|
+
if (err) {
|
|
505
|
+
return err;
|
|
506
|
+
}
|
|
507
|
+
const nums = raw.map(n => n.value);
|
|
476
508
|
const k = (0, _shared_1.argToNumber)(args[1]);
|
|
477
509
|
if (k.kind === 4 /* RVKind.Error */) {
|
|
478
510
|
return k;
|
|
@@ -493,9 +525,12 @@ const fnPERCENTILE = args => {
|
|
|
493
525
|
};
|
|
494
526
|
exports.fnPERCENTILE = fnPERCENTILE;
|
|
495
527
|
const fnPERCENTILEEXC = args => {
|
|
496
|
-
const
|
|
497
|
-
|
|
498
|
-
|
|
528
|
+
const raw = (0, _shared_1.flattenNumbers)([args[0]]);
|
|
529
|
+
const err = (0, _shared_1.firstError)(raw);
|
|
530
|
+
if (err) {
|
|
531
|
+
return err;
|
|
532
|
+
}
|
|
533
|
+
const nums = raw.map(n => n.value);
|
|
499
534
|
const k = (0, _shared_1.argToNumber)(args[1]);
|
|
500
535
|
if (k.kind === 4 /* RVKind.Error */) {
|
|
501
536
|
return k;
|
|
@@ -610,20 +645,48 @@ exports.fnMODE_MULT = fnMODE_MULT;
|
|
|
610
645
|
* Returns the shorter prefix-length pair aligned by position.
|
|
611
646
|
*/
|
|
612
647
|
function pairedNumbers(args, aIdx, bIdx) {
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
const
|
|
619
|
-
const
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
const
|
|
625
|
-
const
|
|
626
|
-
|
|
648
|
+
// Walk both ranges in lockstep so the x/y pairing preserves the
|
|
649
|
+
// source position. Excel's CORREL / SLOPE / INTERCEPT skip any pair
|
|
650
|
+
// where either side is non-numeric (text, blank, boolean) — they do
|
|
651
|
+
// NOT drop the non-numeric cells from one side and then pair by
|
|
652
|
+
// surviving position, which would realign unrelated values.
|
|
653
|
+
const aArg = args[aIdx];
|
|
654
|
+
const bArg = args[bIdx];
|
|
655
|
+
const aCells = [];
|
|
656
|
+
const bCells = [];
|
|
657
|
+
collectCells(aArg, aCells);
|
|
658
|
+
collectCells(bArg, bCells);
|
|
659
|
+
const xs = [];
|
|
660
|
+
const ys = [];
|
|
661
|
+
const n = Math.min(aCells.length, bCells.length);
|
|
662
|
+
for (let i = 0; i < n; i++) {
|
|
663
|
+
const a = aCells[i];
|
|
664
|
+
const b = bCells[i];
|
|
665
|
+
if (a.kind === 4 /* RVKind.Error */) {
|
|
666
|
+
return a;
|
|
667
|
+
}
|
|
668
|
+
if (b.kind === 4 /* RVKind.Error */) {
|
|
669
|
+
return b;
|
|
670
|
+
}
|
|
671
|
+
if (a.kind === 1 /* RVKind.Number */ && b.kind === 1 /* RVKind.Number */) {
|
|
672
|
+
xs.push(a.value);
|
|
673
|
+
ys.push(b.value);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
return { xs, ys };
|
|
677
|
+
}
|
|
678
|
+
/** Walk a runtime value and push every scalar cell (in row-major order). */
|
|
679
|
+
function collectCells(arg, out) {
|
|
680
|
+
if (arg.kind === 5 /* RVKind.Array */) {
|
|
681
|
+
for (const row of arg.rows) {
|
|
682
|
+
for (const cell of row) {
|
|
683
|
+
out.push(cell);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
else if (arg.kind !== 6 /* RVKind.Reference */ && arg.kind !== 7 /* RVKind.Lambda */) {
|
|
688
|
+
out.push(arg);
|
|
689
|
+
}
|
|
627
690
|
}
|
|
628
691
|
function pairedSums(xs, ys) {
|
|
629
692
|
const n = xs.length;
|
|
@@ -768,43 +831,45 @@ Object.defineProperty(exports, "fnPERMUT", { enumerable: true, get: function ()
|
|
|
768
831
|
// GEOMEAN, HARMEAN, TRIMMEAN, DEVSQ, AVEDEV
|
|
769
832
|
// ============================================================================
|
|
770
833
|
const fnGEOMEAN = args => {
|
|
771
|
-
|
|
772
|
-
|
|
834
|
+
let logSum = 0;
|
|
835
|
+
let count = 0;
|
|
836
|
+
let outOfRange = false;
|
|
837
|
+
const err = (0, _shared_1.forEachNumber)(args, n => {
|
|
838
|
+
if (n <= 0) {
|
|
839
|
+
outOfRange = true;
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
logSum += Math.log(n);
|
|
843
|
+
count++;
|
|
844
|
+
});
|
|
773
845
|
if (err) {
|
|
774
846
|
return err;
|
|
775
847
|
}
|
|
776
|
-
|
|
777
|
-
if (nums.length === 0) {
|
|
848
|
+
if (outOfRange || count === 0) {
|
|
778
849
|
return values_1.ERRORS.NUM;
|
|
779
850
|
}
|
|
780
|
-
|
|
781
|
-
for (const n of nums) {
|
|
782
|
-
if (n.value <= 0) {
|
|
783
|
-
return values_1.ERRORS.NUM;
|
|
784
|
-
}
|
|
785
|
-
logSum += Math.log(n.value);
|
|
786
|
-
}
|
|
787
|
-
return (0, values_1.rvNumber)(Math.exp(logSum / nums.length));
|
|
851
|
+
return (0, values_1.rvNumber)(Math.exp(logSum / count));
|
|
788
852
|
};
|
|
789
853
|
exports.fnGEOMEAN = fnGEOMEAN;
|
|
790
854
|
const fnHARMEAN = args => {
|
|
791
|
-
|
|
792
|
-
|
|
855
|
+
let recipSum = 0;
|
|
856
|
+
let count = 0;
|
|
857
|
+
let outOfRange = false;
|
|
858
|
+
const err = (0, _shared_1.forEachNumber)(args, n => {
|
|
859
|
+
if (n <= 0) {
|
|
860
|
+
outOfRange = true;
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
recipSum += 1 / n;
|
|
864
|
+
count++;
|
|
865
|
+
});
|
|
793
866
|
if (err) {
|
|
794
867
|
return err;
|
|
795
868
|
}
|
|
796
|
-
|
|
797
|
-
if (nums.length === 0) {
|
|
869
|
+
if (outOfRange || count === 0) {
|
|
798
870
|
return values_1.ERRORS.NUM;
|
|
799
871
|
}
|
|
800
|
-
|
|
801
|
-
for (const n of nums) {
|
|
802
|
-
if (n.value <= 0) {
|
|
803
|
-
return values_1.ERRORS.NUM;
|
|
804
|
-
}
|
|
805
|
-
recipSum += 1 / n.value;
|
|
806
|
-
}
|
|
807
|
-
return (0, values_1.rvNumber)(nums.length / recipSum);
|
|
872
|
+
return (0, values_1.rvNumber)(count / recipSum);
|
|
808
873
|
};
|
|
809
874
|
exports.fnHARMEAN = fnHARMEAN;
|
|
810
875
|
const fnTRIMMEAN = args => {
|
|
@@ -932,22 +997,27 @@ exports.fnCONFIDENCE_T = fnCONFIDENCE_T;
|
|
|
932
997
|
* side is non-numeric).
|
|
933
998
|
*/
|
|
934
999
|
function pairedNumericValues(a, b) {
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
const
|
|
941
|
-
const
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
}
|
|
945
|
-
const n = Math.min(xsAll.length, ysAll.length);
|
|
1000
|
+
// Walk both ranges in lockstep so pair alignment survives non-numeric
|
|
1001
|
+
// cells. Previously `flattenNumbers` dropped text / blanks before the
|
|
1002
|
+
// zip, which silently shifted the rest of the pairs and produced a
|
|
1003
|
+
// spurious covariance. Excel's COVARIANCE.P / .S pair cells by
|
|
1004
|
+
// position and skip only the pairs where either side is non-numeric.
|
|
1005
|
+
const aCells = [];
|
|
1006
|
+
const bCells = [];
|
|
1007
|
+
collectCells(a, aCells);
|
|
1008
|
+
collectCells(b, bCells);
|
|
946
1009
|
const xs = [];
|
|
947
1010
|
const ys = [];
|
|
1011
|
+
const n = Math.min(aCells.length, bCells.length);
|
|
948
1012
|
for (let i = 0; i < n; i++) {
|
|
949
|
-
const x =
|
|
950
|
-
const y =
|
|
1013
|
+
const x = aCells[i];
|
|
1014
|
+
const y = bCells[i];
|
|
1015
|
+
if (x.kind === 4 /* RVKind.Error */) {
|
|
1016
|
+
return x;
|
|
1017
|
+
}
|
|
1018
|
+
if (y.kind === 4 /* RVKind.Error */) {
|
|
1019
|
+
return y;
|
|
1020
|
+
}
|
|
951
1021
|
if (x.kind === 1 /* RVKind.Number */ && y.kind === 1 /* RVKind.Number */) {
|
|
952
1022
|
xs.push(x.value);
|
|
953
1023
|
ys.push(y.value);
|
|
@@ -1094,39 +1164,26 @@ const fnAVERAGEA = args => {
|
|
|
1094
1164
|
return count === 0 ? values_1.ERRORS.DIV0 : (0, values_1.rvNumber)(sum / count);
|
|
1095
1165
|
};
|
|
1096
1166
|
exports.fnAVERAGEA = fnAVERAGEA;
|
|
1097
|
-
const fnMAXA = args =>
|
|
1098
|
-
const all = (0, _shared_1.flattenAll)(args);
|
|
1099
|
-
let max = -Infinity;
|
|
1100
|
-
let found = false;
|
|
1101
|
-
for (const v of all) {
|
|
1102
|
-
if (v.kind === 0 /* RVKind.Blank */) {
|
|
1103
|
-
continue;
|
|
1104
|
-
}
|
|
1105
|
-
if (v.kind === 4 /* RVKind.Error */) {
|
|
1106
|
-
return v;
|
|
1107
|
-
}
|
|
1108
|
-
let n;
|
|
1109
|
-
if (v.kind === 1 /* RVKind.Number */) {
|
|
1110
|
-
n = v.value;
|
|
1111
|
-
}
|
|
1112
|
-
else if (v.kind === 3 /* RVKind.Boolean */) {
|
|
1113
|
-
n = v.value ? 1 : 0;
|
|
1114
|
-
}
|
|
1115
|
-
else {
|
|
1116
|
-
n = 0;
|
|
1117
|
-
}
|
|
1118
|
-
if (n > max) {
|
|
1119
|
-
max = n;
|
|
1120
|
-
}
|
|
1121
|
-
found = true;
|
|
1122
|
-
}
|
|
1123
|
-
return (0, values_1.rvNumber)(found ? max : 0);
|
|
1124
|
-
};
|
|
1167
|
+
const fnMAXA = args => reduceAValue(args, -Infinity, (best, n) => (n > best ? n : best));
|
|
1125
1168
|
exports.fnMAXA = fnMAXA;
|
|
1126
|
-
const fnMINA = args =>
|
|
1127
|
-
|
|
1128
|
-
|
|
1169
|
+
const fnMINA = args => reduceAValue(args, Infinity, (best, n) => (n < best ? n : best));
|
|
1170
|
+
exports.fnMINA = fnMINA;
|
|
1171
|
+
/**
|
|
1172
|
+
* Shared MAXA / MINA reducer. Excel's `*A` variants differ from MAX / MIN
|
|
1173
|
+
* only in how they treat text and booleans inside ranges:
|
|
1174
|
+
* - Number → its value
|
|
1175
|
+
* - Boolean → 1 / 0
|
|
1176
|
+
* - String → 0 (NOT skipped like MAX / MIN)
|
|
1177
|
+
* - Blank → skipped
|
|
1178
|
+
* - Error → propagated
|
|
1179
|
+
*
|
|
1180
|
+
* When no non-blank cells are seen, both return 0 (the untouched
|
|
1181
|
+
* identity fallback matches Excel's historical behaviour).
|
|
1182
|
+
*/
|
|
1183
|
+
function reduceAValue(args, identity, fold) {
|
|
1184
|
+
let best = identity;
|
|
1129
1185
|
let found = false;
|
|
1186
|
+
const all = (0, _shared_1.flattenAll)(args);
|
|
1130
1187
|
for (const v of all) {
|
|
1131
1188
|
if (v.kind === 0 /* RVKind.Blank */) {
|
|
1132
1189
|
continue;
|
|
@@ -1134,24 +1191,12 @@ const fnMINA = args => {
|
|
|
1134
1191
|
if (v.kind === 4 /* RVKind.Error */) {
|
|
1135
1192
|
return v;
|
|
1136
1193
|
}
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
n = v.value;
|
|
1140
|
-
}
|
|
1141
|
-
else if (v.kind === 3 /* RVKind.Boolean */) {
|
|
1142
|
-
n = v.value ? 1 : 0;
|
|
1143
|
-
}
|
|
1144
|
-
else {
|
|
1145
|
-
n = 0;
|
|
1146
|
-
}
|
|
1147
|
-
if (n < min) {
|
|
1148
|
-
min = n;
|
|
1149
|
-
}
|
|
1194
|
+
const n = v.kind === 1 /* RVKind.Number */ ? v.value : v.kind === 3 /* RVKind.Boolean */ ? (v.value ? 1 : 0) : 0; // text counts as 0 for MAXA / MINA.
|
|
1195
|
+
best = fold(best, n);
|
|
1150
1196
|
found = true;
|
|
1151
1197
|
}
|
|
1152
|
-
return (0, values_1.rvNumber)(found ?
|
|
1153
|
-
}
|
|
1154
|
-
exports.fnMINA = fnMINA;
|
|
1198
|
+
return (0, values_1.rvNumber)(found ? best : 0);
|
|
1199
|
+
}
|
|
1155
1200
|
// ============================================================================
|
|
1156
1201
|
// Private helpers for distributions (pure number → number, unchanged)
|
|
1157
1202
|
// ============================================================================
|
|
@@ -2129,7 +2174,10 @@ const fnERF = args => {
|
|
|
2129
2174
|
if (lower.kind === 4 /* RVKind.Error */) {
|
|
2130
2175
|
return lower;
|
|
2131
2176
|
}
|
|
2132
|
-
|
|
2177
|
+
// Blank 2nd arg → behave like an omitted upper bound, i.e. return
|
|
2178
|
+
// `erf(lower)`. Previously a blank coerced to 0 and flipped the sign
|
|
2179
|
+
// of the result via `erf(0) − erf(lower)`.
|
|
2180
|
+
if (args.length > 1 && args[1].kind !== 0 /* RVKind.Blank */) {
|
|
2133
2181
|
const upper = (0, _shared_1.argToNumber)(args[1]);
|
|
2134
2182
|
if (upper.kind === 4 /* RVKind.Error */) {
|
|
2135
2183
|
return upper;
|
|
@@ -2838,34 +2886,36 @@ exports.fnF_INV_RT = fnF_INV_RT;
|
|
|
2838
2886
|
* Formula: n / ((n-1)(n-2)) * Σ((xi-mean)/s)^3, where s is the sample stdev.
|
|
2839
2887
|
*/
|
|
2840
2888
|
const fnSKEW = args => {
|
|
2841
|
-
const
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
return err;
|
|
2889
|
+
const xs = toNumberArray(args);
|
|
2890
|
+
if (!Array.isArray(xs)) {
|
|
2891
|
+
return xs;
|
|
2845
2892
|
}
|
|
2846
|
-
const xs = nums.map(n => n.value);
|
|
2847
2893
|
const n = xs.length;
|
|
2848
2894
|
if (n < 3) {
|
|
2849
2895
|
return values_1.ERRORS.DIV0;
|
|
2850
2896
|
}
|
|
2897
|
+
// Single pass 1: mean.
|
|
2851
2898
|
let sum = 0;
|
|
2852
|
-
for (
|
|
2853
|
-
sum +=
|
|
2899
|
+
for (let i = 0; i < n; i++) {
|
|
2900
|
+
sum += xs[i];
|
|
2854
2901
|
}
|
|
2855
2902
|
const mean = sum / n;
|
|
2903
|
+
// Single pass 2: accumulate Σ(x−μ)² and Σ(x−μ)³ together. Computing
|
|
2904
|
+
// the cubed normalisation after the loop (dividing by stdev³) is
|
|
2905
|
+
// algebraically equivalent to Σ((x−μ)/s)³ and avoids the third pass.
|
|
2856
2906
|
let sumSq = 0;
|
|
2857
|
-
|
|
2858
|
-
|
|
2907
|
+
let sumCube = 0;
|
|
2908
|
+
for (let i = 0; i < n; i++) {
|
|
2909
|
+
const d = xs[i] - mean;
|
|
2910
|
+
const d2 = d * d;
|
|
2911
|
+
sumSq += d2;
|
|
2912
|
+
sumCube += d2 * d;
|
|
2859
2913
|
}
|
|
2860
2914
|
const sampleStd = Math.sqrt(sumSq / (n - 1));
|
|
2861
2915
|
if (sampleStd === 0) {
|
|
2862
2916
|
return values_1.ERRORS.DIV0;
|
|
2863
2917
|
}
|
|
2864
|
-
|
|
2865
|
-
for (const v of xs) {
|
|
2866
|
-
sumCubed += ((v - mean) / sampleStd) ** 3;
|
|
2867
|
-
}
|
|
2868
|
-
return (0, values_1.rvNumber)((n / ((n - 1) * (n - 2))) * sumCubed);
|
|
2918
|
+
return (0, values_1.rvNumber)((n / ((n - 1) * (n - 2))) * (sumCube / (sampleStd * sampleStd * sampleStd)));
|
|
2869
2919
|
};
|
|
2870
2920
|
exports.fnSKEW = fnSKEW;
|
|
2871
2921
|
/**
|
|
@@ -2873,34 +2923,32 @@ exports.fnSKEW = fnSKEW;
|
|
|
2873
2923
|
* Formula: (1/n) * Σ((xi-mean)/σ)^3, where σ is the population stdev.
|
|
2874
2924
|
*/
|
|
2875
2925
|
const fnSKEW_P = args => {
|
|
2876
|
-
const
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
return err;
|
|
2926
|
+
const xs = toNumberArray(args);
|
|
2927
|
+
if (!Array.isArray(xs)) {
|
|
2928
|
+
return xs;
|
|
2880
2929
|
}
|
|
2881
|
-
const xs = nums.map(n => n.value);
|
|
2882
2930
|
const n = xs.length;
|
|
2883
2931
|
if (n < 1) {
|
|
2884
2932
|
return values_1.ERRORS.DIV0;
|
|
2885
2933
|
}
|
|
2886
2934
|
let sum = 0;
|
|
2887
|
-
for (
|
|
2888
|
-
sum +=
|
|
2935
|
+
for (let i = 0; i < n; i++) {
|
|
2936
|
+
sum += xs[i];
|
|
2889
2937
|
}
|
|
2890
2938
|
const mean = sum / n;
|
|
2891
2939
|
let sumSq = 0;
|
|
2892
|
-
|
|
2893
|
-
|
|
2940
|
+
let sumCube = 0;
|
|
2941
|
+
for (let i = 0; i < n; i++) {
|
|
2942
|
+
const d = xs[i] - mean;
|
|
2943
|
+
const d2 = d * d;
|
|
2944
|
+
sumSq += d2;
|
|
2945
|
+
sumCube += d2 * d;
|
|
2894
2946
|
}
|
|
2895
2947
|
const popStd = Math.sqrt(sumSq / n);
|
|
2896
2948
|
if (popStd === 0) {
|
|
2897
2949
|
return values_1.ERRORS.DIV0;
|
|
2898
2950
|
}
|
|
2899
|
-
|
|
2900
|
-
for (const v of xs) {
|
|
2901
|
-
sumCubed += ((v - mean) / popStd) ** 3;
|
|
2902
|
-
}
|
|
2903
|
-
return (0, values_1.rvNumber)(sumCubed / n);
|
|
2951
|
+
return (0, values_1.rvNumber)(sumCube / n / (popStd * popStd * popStd));
|
|
2904
2952
|
};
|
|
2905
2953
|
exports.fnSKEW_P = fnSKEW_P;
|
|
2906
2954
|
/**
|
|
@@ -2908,36 +2956,38 @@ exports.fnSKEW_P = fnSKEW_P;
|
|
|
2908
2956
|
* Formula: n(n+1) / ((n-1)(n-2)(n-3)) * Σ((xi-mean)/s)^4 - 3(n-1)^2 / ((n-2)(n-3)).
|
|
2909
2957
|
*/
|
|
2910
2958
|
const fnKURT = args => {
|
|
2911
|
-
const
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
return err;
|
|
2959
|
+
const xs = toNumberArray(args);
|
|
2960
|
+
if (!Array.isArray(xs)) {
|
|
2961
|
+
return xs;
|
|
2915
2962
|
}
|
|
2916
|
-
const xs = nums.map(n => n.value);
|
|
2917
2963
|
const n = xs.length;
|
|
2918
2964
|
if (n < 4) {
|
|
2919
2965
|
return values_1.ERRORS.DIV0;
|
|
2920
2966
|
}
|
|
2921
2967
|
let sum = 0;
|
|
2922
|
-
for (
|
|
2923
|
-
sum +=
|
|
2968
|
+
for (let i = 0; i < n; i++) {
|
|
2969
|
+
sum += xs[i];
|
|
2924
2970
|
}
|
|
2925
2971
|
const mean = sum / n;
|
|
2972
|
+
// Single pass for Σ(x−μ)² and Σ(x−μ)⁴ — the `(x−μ)/s` normalisation
|
|
2973
|
+
// is factored out after the loop (divide by stdev⁴) so we don't need
|
|
2974
|
+
// to know `s` ahead of time.
|
|
2926
2975
|
let sumSq = 0;
|
|
2927
|
-
|
|
2928
|
-
|
|
2976
|
+
let sumQuad = 0;
|
|
2977
|
+
for (let i = 0; i < n; i++) {
|
|
2978
|
+
const d = xs[i] - mean;
|
|
2979
|
+
const d2 = d * d;
|
|
2980
|
+
sumSq += d2;
|
|
2981
|
+
sumQuad += d2 * d2;
|
|
2929
2982
|
}
|
|
2930
2983
|
const sampleStd = Math.sqrt(sumSq / (n - 1));
|
|
2931
2984
|
if (sampleStd === 0) {
|
|
2932
2985
|
return values_1.ERRORS.DIV0;
|
|
2933
2986
|
}
|
|
2934
|
-
|
|
2935
|
-
for (const v of xs) {
|
|
2936
|
-
sumQuad += ((v - mean) / sampleStd) ** 4;
|
|
2937
|
-
}
|
|
2987
|
+
const s4 = sampleStd * sampleStd;
|
|
2938
2988
|
const term1 = (n * (n + 1)) / ((n - 1) * (n - 2) * (n - 3));
|
|
2939
2989
|
const term2 = (3 * (n - 1) ** 2) / ((n - 2) * (n - 3));
|
|
2940
|
-
return (0, values_1.rvNumber)(term1 * sumQuad - term2);
|
|
2990
|
+
return (0, values_1.rvNumber)(term1 * (sumQuad / (s4 * s4)) - term2);
|
|
2941
2991
|
};
|
|
2942
2992
|
exports.fnKURT = fnKURT;
|
|
2943
2993
|
// ============================================================================
|