@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.
Files changed (109) hide show
  1. package/dist/browser/index.d.ts +1 -0
  2. package/dist/browser/index.js +2 -0
  3. package/dist/browser/modules/excel/cell.d.ts +18 -0
  4. package/dist/browser/modules/excel/cell.js +21 -0
  5. package/dist/browser/modules/excel/utils/cell-format.js +85 -13
  6. package/dist/browser/modules/excel/workbook.browser.d.ts +57 -0
  7. package/dist/browser/modules/excel/workbook.browser.js +49 -0
  8. package/dist/browser/modules/excel/xlsx/defaultnumformats.js +3 -3
  9. package/dist/browser/modules/formula/compile/binder.js +48 -6
  10. package/dist/browser/modules/formula/compile/bound-ast.d.ts +16 -2
  11. package/dist/browser/modules/formula/compile/bound-ast.js +1 -0
  12. package/dist/browser/modules/formula/compile/compiled-formula.js +41 -8
  13. package/dist/browser/modules/formula/functions/_shared.d.ts +19 -0
  14. package/dist/browser/modules/formula/functions/_shared.js +47 -0
  15. package/dist/browser/modules/formula/functions/conditional.js +103 -22
  16. package/dist/browser/modules/formula/functions/date.js +105 -23
  17. package/dist/browser/modules/formula/functions/dynamic-array.js +173 -69
  18. package/dist/browser/modules/formula/functions/engineering.d.ts +2 -2
  19. package/dist/browser/modules/formula/functions/engineering.js +103 -151
  20. package/dist/browser/modules/formula/functions/financial.js +210 -184
  21. package/dist/browser/modules/formula/functions/lookup.js +224 -157
  22. package/dist/browser/modules/formula/functions/math.d.ts +26 -0
  23. package/dist/browser/modules/formula/functions/math.js +249 -69
  24. package/dist/browser/modules/formula/functions/statistical.js +221 -171
  25. package/dist/browser/modules/formula/functions/text.js +112 -52
  26. package/dist/browser/modules/formula/integration/calculate-formulas-impl.js +20 -1
  27. package/dist/browser/modules/formula/materialize/build-writeback-plan.js +10 -6
  28. package/dist/browser/modules/formula/materialize/types.d.ts +15 -0
  29. package/dist/browser/modules/formula/runtime/evaluator.d.ts +8 -0
  30. package/dist/browser/modules/formula/runtime/evaluator.js +582 -162
  31. package/dist/browser/modules/formula/runtime/function-registry.d.ts +5 -0
  32. package/dist/browser/modules/formula/runtime/function-registry.js +59 -13
  33. package/dist/browser/modules/formula/runtime/values.d.ts +13 -0
  34. package/dist/browser/modules/formula/runtime/values.js +20 -2
  35. package/dist/browser/modules/formula/syntax/ast.d.ts +14 -2
  36. package/dist/browser/modules/formula/syntax/ast.js +1 -0
  37. package/dist/browser/modules/formula/syntax/parser.js +29 -7
  38. package/dist/browser/modules/formula/syntax/token-types.d.ts +4 -0
  39. package/dist/browser/modules/formula/syntax/token-types.js +9 -0
  40. package/dist/browser/modules/formula/syntax/tokenizer.js +76 -19
  41. package/dist/cjs/index.js +7 -2
  42. package/dist/cjs/modules/excel/cell.js +21 -0
  43. package/dist/cjs/modules/excel/utils/cell-format.js +85 -13
  44. package/dist/cjs/modules/excel/workbook.browser.js +49 -0
  45. package/dist/cjs/modules/excel/xlsx/defaultnumformats.js +3 -3
  46. package/dist/cjs/modules/formula/compile/binder.js +48 -6
  47. package/dist/cjs/modules/formula/compile/compiled-formula.js +41 -8
  48. package/dist/cjs/modules/formula/functions/_shared.js +48 -0
  49. package/dist/cjs/modules/formula/functions/conditional.js +103 -22
  50. package/dist/cjs/modules/formula/functions/date.js +104 -22
  51. package/dist/cjs/modules/formula/functions/dynamic-array.js +173 -69
  52. package/dist/cjs/modules/formula/functions/engineering.js +109 -157
  53. package/dist/cjs/modules/formula/functions/financial.js +209 -183
  54. package/dist/cjs/modules/formula/functions/lookup.js +224 -157
  55. package/dist/cjs/modules/formula/functions/math.js +254 -70
  56. package/dist/cjs/modules/formula/functions/statistical.js +222 -172
  57. package/dist/cjs/modules/formula/functions/text.js +112 -52
  58. package/dist/cjs/modules/formula/integration/calculate-formulas-impl.js +20 -1
  59. package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +10 -6
  60. package/dist/cjs/modules/formula/runtime/evaluator.js +581 -161
  61. package/dist/cjs/modules/formula/runtime/function-registry.js +57 -11
  62. package/dist/cjs/modules/formula/runtime/values.js +21 -2
  63. package/dist/cjs/modules/formula/syntax/parser.js +29 -7
  64. package/dist/cjs/modules/formula/syntax/token-types.js +9 -0
  65. package/dist/cjs/modules/formula/syntax/tokenizer.js +76 -19
  66. package/dist/esm/index.js +2 -0
  67. package/dist/esm/modules/excel/cell.js +21 -0
  68. package/dist/esm/modules/excel/utils/cell-format.js +85 -13
  69. package/dist/esm/modules/excel/workbook.browser.js +49 -0
  70. package/dist/esm/modules/excel/xlsx/defaultnumformats.js +3 -3
  71. package/dist/esm/modules/formula/compile/binder.js +48 -6
  72. package/dist/esm/modules/formula/compile/bound-ast.js +1 -0
  73. package/dist/esm/modules/formula/compile/compiled-formula.js +41 -8
  74. package/dist/esm/modules/formula/functions/_shared.js +47 -0
  75. package/dist/esm/modules/formula/functions/conditional.js +103 -22
  76. package/dist/esm/modules/formula/functions/date.js +105 -23
  77. package/dist/esm/modules/formula/functions/dynamic-array.js +173 -69
  78. package/dist/esm/modules/formula/functions/engineering.js +103 -151
  79. package/dist/esm/modules/formula/functions/financial.js +210 -184
  80. package/dist/esm/modules/formula/functions/lookup.js +224 -157
  81. package/dist/esm/modules/formula/functions/math.js +249 -69
  82. package/dist/esm/modules/formula/functions/statistical.js +221 -171
  83. package/dist/esm/modules/formula/functions/text.js +112 -52
  84. package/dist/esm/modules/formula/integration/calculate-formulas-impl.js +20 -1
  85. package/dist/esm/modules/formula/materialize/build-writeback-plan.js +10 -6
  86. package/dist/esm/modules/formula/runtime/evaluator.js +582 -162
  87. package/dist/esm/modules/formula/runtime/function-registry.js +59 -13
  88. package/dist/esm/modules/formula/runtime/values.js +20 -2
  89. package/dist/esm/modules/formula/syntax/ast.js +1 -0
  90. package/dist/esm/modules/formula/syntax/parser.js +29 -7
  91. package/dist/esm/modules/formula/syntax/token-types.js +9 -0
  92. package/dist/esm/modules/formula/syntax/tokenizer.js +76 -19
  93. package/dist/iife/excelts.iife.js +1502 -1379
  94. package/dist/iife/excelts.iife.js.map +1 -1
  95. package/dist/iife/excelts.iife.min.js +26 -26
  96. package/dist/types/index.d.ts +1 -0
  97. package/dist/types/modules/excel/cell.d.ts +18 -0
  98. package/dist/types/modules/excel/workbook.browser.d.ts +57 -0
  99. package/dist/types/modules/formula/compile/bound-ast.d.ts +16 -2
  100. package/dist/types/modules/formula/functions/_shared.d.ts +19 -0
  101. package/dist/types/modules/formula/functions/engineering.d.ts +2 -2
  102. package/dist/types/modules/formula/functions/math.d.ts +26 -0
  103. package/dist/types/modules/formula/materialize/types.d.ts +15 -0
  104. package/dist/types/modules/formula/runtime/evaluator.d.ts +8 -0
  105. package/dist/types/modules/formula/runtime/function-registry.d.ts +5 -0
  106. package/dist/types/modules/formula/runtime/values.d.ts +13 -0
  107. package/dist/types/modules/formula/syntax/ast.d.ts +14 -2
  108. package/dist/types/modules/formula/syntax/token-types.d.ts +4 -0
  109. 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 nums = (0, _shared_1.flattenNumbers)(args);
82
- const err = (0, _shared_1.firstError)(nums);
83
- if (err) {
84
- return err;
81
+ const values = toNumberArray(args);
82
+ if (!Array.isArray(values)) {
83
+ return values;
85
84
  }
86
- if (nums.length === 0) {
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
- // k-th largest == (n − k)-th smallest. Use a copy — quickselect
141
- // mutates the array it works on, and we need a fresh view for
142
- // every cell in the output.
143
- outRow.push((0, values_1.rvNumber)(quickselect(values.slice(), values.length - kInt)));
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
- outRow.push((0, values_1.rvNumber)(quickselect(values.slice(), kInt - 1)));
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 sum = 0;
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 (const v of nums) {
253
- sumSq += (v - mean) ** 2;
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 rawNums = (0, _shared_1.flattenNumbers)(args);
260
- const err = (0, _shared_1.firstError)(rawNums);
261
- if (err) {
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
- const nums = (0, _shared_1.flattenNumbers)([args[0]])
474
- .filter((v) => v.kind === 1 /* RVKind.Number */)
475
- .map(n => n.value);
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 nums = (0, _shared_1.flattenNumbers)([args[0]])
497
- .filter((v) => v.kind === 1 /* RVKind.Number */)
498
- .map(n => n.value);
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
- const flatA = (0, _shared_1.flattenNumbers)([args[aIdx]]);
614
- const errA = (0, _shared_1.firstError)(flatA);
615
- if (errA) {
616
- return errA;
617
- }
618
- const flatB = (0, _shared_1.flattenNumbers)([args[bIdx]]);
619
- const errB = (0, _shared_1.firstError)(flatB);
620
- if (errB) {
621
- return errB;
622
- }
623
- const xs = flatA.filter((v) => v.kind === 1 /* RVKind.Number */).map(n => n.value);
624
- const ys = flatB.filter((v) => v.kind === 1 /* RVKind.Number */).map(n => n.value);
625
- const n = Math.min(xs.length, ys.length);
626
- return { xs: xs.slice(0, n), ys: ys.slice(0, n) };
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
- const rawNums = (0, _shared_1.flattenNumbers)(args);
772
- const err = (0, _shared_1.firstError)(rawNums);
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
- const nums = rawNums;
777
- if (nums.length === 0) {
848
+ if (outOfRange || count === 0) {
778
849
  return values_1.ERRORS.NUM;
779
850
  }
780
- let logSum = 0;
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
- const rawNums = (0, _shared_1.flattenNumbers)(args);
792
- const err = (0, _shared_1.firstError)(rawNums);
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
- const nums = rawNums;
797
- if (nums.length === 0) {
869
+ if (outOfRange || count === 0) {
798
870
  return values_1.ERRORS.NUM;
799
871
  }
800
- let recipSum = 0;
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
- const xsAll = (0, _shared_1.flattenNumbers)([a]);
936
- const xsErr = (0, _shared_1.firstError)(xsAll);
937
- if (xsErr) {
938
- return xsErr;
939
- }
940
- const ysAll = (0, _shared_1.flattenNumbers)([b]);
941
- const ysErr = (0, _shared_1.firstError)(ysAll);
942
- if (ysErr) {
943
- return ysErr;
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 = xsAll[i];
950
- const y = ysAll[i];
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
- const all = (0, _shared_1.flattenAll)(args);
1128
- let min = Infinity;
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
- let n;
1138
- if (v.kind === 1 /* RVKind.Number */) {
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 ? min : 0);
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
- if (args.length > 1) {
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 nums = (0, _shared_1.flattenNumbers)(args);
2842
- const err = (0, _shared_1.firstError)(nums);
2843
- if (err) {
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 (const v of xs) {
2853
- sum += v;
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
- for (const v of xs) {
2858
- sumSq += (v - mean) ** 2;
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
- let sumCubed = 0;
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 nums = (0, _shared_1.flattenNumbers)(args);
2877
- const err = (0, _shared_1.firstError)(nums);
2878
- if (err) {
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 (const v of xs) {
2888
- sum += v;
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
- for (const v of xs) {
2893
- sumSq += (v - mean) ** 2;
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
- let sumCubed = 0;
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 nums = (0, _shared_1.flattenNumbers)(args);
2912
- const err = (0, _shared_1.firstError)(nums);
2913
- if (err) {
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 (const v of xs) {
2923
- sum += v;
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
- for (const v of xs) {
2928
- sumSq += (v - mean) ** 2;
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
- let sumQuad = 0;
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
  // ============================================================================