@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
@@ -57,32 +57,73 @@ export function fnFILTER(args) {
57
57
  if (!dataArr || !includeArr) {
58
58
  return ERRORS.VALUE;
59
59
  }
60
- // Excel requires `include` to be a 1-column vector matching data's
61
- // height (the common row-filter shape). A mismatched shape previously
62
- // let rows slip through silently because `getCell` out-of-bounds
63
- // returns BLANK, which reads as FALSE.
64
- if (includeArr.width !== 1 || includeArr.height !== dataArr.height) {
60
+ const ifEmpty = args.length > 2 && args[2].kind !== RVKind.Blank ? topLeft(args[2]) : null;
61
+ // Decide filter orientation from the include vector's shape:
62
+ // - N×1 vector matching data.height ROW filter (classic).
63
+ // - 1×N vector matching data.width → COL filter (Excel supports
64
+ // both; previously we hard-required the row shape and silently
65
+ // rejected column filters with #VALUE!).
66
+ // A 1×1 include vector is treated as a row filter over 1 row.
67
+ const isRowFilter = includeArr.width === 1 && includeArr.height === dataArr.height;
68
+ const isColFilter = !isRowFilter && includeArr.height === 1 && includeArr.width === dataArr.width;
69
+ if (!isRowFilter && !isColFilter) {
65
70
  return ERRORS.VALUE;
66
71
  }
67
- const ifEmpty = args.length > 2 ? topLeft(args[2]) : null;
68
- const resultRows = [];
69
- for (let r = 0; r < dataArr.height; r++) {
70
- const inc = getCell(includeArr, r, 0);
71
- if (inc.kind === RVKind.Error) {
72
- return inc;
73
- }
74
- if ((inc.kind === RVKind.Boolean && inc.value) ||
75
- (inc.kind === RVKind.Number && inc.value !== 0)) {
76
- const row = [];
77
- for (let c = 0; c < dataArr.width; c++) {
78
- row.push(getCell(dataArr, r, c));
72
+ const cellIsTruthy = (cell) => {
73
+ if (cell.kind === RVKind.Error) {
74
+ return cell;
75
+ }
76
+ if (cell.kind === RVKind.Boolean) {
77
+ return cell.value;
78
+ }
79
+ if (cell.kind === RVKind.Number) {
80
+ return cell.value !== 0;
81
+ }
82
+ // Blank / String / anything else treated as FALSE.
83
+ return false;
84
+ };
85
+ if (isRowFilter) {
86
+ const resultRows = [];
87
+ for (let r = 0; r < dataArr.height; r++) {
88
+ const truthy = cellIsTruthy(getCell(includeArr, r, 0));
89
+ if (typeof truthy !== "boolean") {
90
+ return truthy;
79
91
  }
80
- resultRows.push(row);
92
+ if (truthy) {
93
+ const row = [];
94
+ for (let c = 0; c < dataArr.width; c++) {
95
+ row.push(getCell(dataArr, r, c));
96
+ }
97
+ resultRows.push(row);
98
+ }
99
+ }
100
+ if (resultRows.length === 0) {
101
+ return ifEmpty !== null ? rvArray([[ifEmpty]]) : ERRORS.CALC;
102
+ }
103
+ return rvArray(resultRows);
104
+ }
105
+ // Column filter — pick a subset of columns across every row.
106
+ const keepCols = [];
107
+ for (let c = 0; c < dataArr.width; c++) {
108
+ const truthy = cellIsTruthy(getCell(includeArr, 0, c));
109
+ if (typeof truthy !== "boolean") {
110
+ return truthy;
111
+ }
112
+ if (truthy) {
113
+ keepCols.push(c);
81
114
  }
82
115
  }
83
- if (resultRows.length === 0) {
116
+ if (keepCols.length === 0) {
84
117
  return ifEmpty !== null ? rvArray([[ifEmpty]]) : ERRORS.CALC;
85
118
  }
119
+ const resultRows = [];
120
+ for (let r = 0; r < dataArr.height; r++) {
121
+ const row = new Array(keepCols.length);
122
+ for (let i = 0; i < keepCols.length; i++) {
123
+ row[i] = getCell(dataArr, r, keepCols[i]);
124
+ }
125
+ resultRows.push(row);
126
+ }
86
127
  return rvArray(resultRows);
87
128
  }
88
129
  export function fnSORT(args) {
@@ -98,15 +139,26 @@ export function fnSORT(args) {
98
139
  }
99
140
  rows.push(row);
100
141
  }
101
- const sortIndexV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(1);
142
+ const sortIndexV = args.length > 1 && args[1].kind !== RVKind.Blank ? toNumberRV(topLeft(args[1])) : rvNumber(1);
102
143
  if (isError(sortIndexV)) {
103
144
  return sortIndexV;
104
145
  }
105
- const sortOrderV = args.length > 2 ? toNumberRV(args[2]) : rvNumber(1);
146
+ // Blank `sort_order` Excel's default 1 (ascending). Without this
147
+ // guard the blank coerces through `toNumberRV` to 0, which multiplies
148
+ // the comparator output and produces a no-op sort — every pair is
149
+ // treated as equal and the output order is engine-defined noise.
150
+ const sortOrderV = args.length > 2 && args[2].kind !== RVKind.Blank ? toNumberRV(topLeft(args[2])) : rvNumber(1);
106
151
  if (isError(sortOrderV)) {
107
152
  return sortOrderV;
108
153
  }
109
- const byColV = args.length > 3 ? toBooleanRV(args[3]) : { kind: RVKind.Boolean, value: false };
154
+ // Excel restricts `sort_order` to exactly 1 or -1; any other integer
155
+ // (0, 2, 3, …) is #VALUE!. Previously zero silently disabled the sort.
156
+ if (sortOrderV.value !== 1 && sortOrderV.value !== -1) {
157
+ return ERRORS.VALUE;
158
+ }
159
+ const byColV = args.length > 3
160
+ ? toBooleanRV(topLeft(args[3]))
161
+ : { kind: RVKind.Boolean, value: false };
110
162
  if (isError(byColV)) {
111
163
  return byColV;
112
164
  }
@@ -140,11 +192,15 @@ export function fnUNIQUE(args) {
140
192
  if (!dataArr) {
141
193
  return ERRORS.VALUE;
142
194
  }
143
- const byColV = args.length > 1 ? toBooleanRV(args[1]) : { kind: RVKind.Boolean, value: false };
195
+ const byColV = args.length > 1
196
+ ? toBooleanRV(topLeft(args[1]))
197
+ : { kind: RVKind.Boolean, value: false };
144
198
  if (isError(byColV)) {
145
199
  return byColV;
146
200
  }
147
- const exactlyOnceV = args.length > 2 ? toBooleanRV(args[2]) : { kind: RVKind.Boolean, value: false };
201
+ const exactlyOnceV = args.length > 2
202
+ ? toBooleanRV(topLeft(args[2]))
203
+ : { kind: RVKind.Boolean, value: false };
148
204
  if (isError(exactlyOnceV)) {
149
205
  return exactlyOnceV;
150
206
  }
@@ -245,10 +301,18 @@ export function fnSORTBY(args) {
245
301
  if (!keyArr) {
246
302
  return ERRORS.VALUE;
247
303
  }
248
- const orderV = i + 1 < args.length ? toNumberRV(args[i + 1]) : rvNumber(1);
304
+ // Blank order default 1 (ascending); anything other than ±1 is
305
+ // #VALUE!. Mirrors the SORT validation — a blank used to produce
306
+ // 0 via `toNumberRV`, silently disabling the sort key.
307
+ const orderV = i + 1 < args.length && args[i + 1].kind !== RVKind.Blank
308
+ ? toNumberRV(topLeft(args[i + 1]))
309
+ : rvNumber(1);
249
310
  if (isError(orderV)) {
250
311
  return orderV;
251
312
  }
313
+ if (orderV.value !== 1 && orderV.value !== -1) {
314
+ return ERRORS.VALUE;
315
+ }
252
316
  sortKeys.push({ arr: keyArr, order: orderV.value });
253
317
  }
254
318
  data.sort((a, b) => {
@@ -265,7 +329,7 @@ export function fnSORTBY(args) {
265
329
  return rvArray(data.map(d => d.row));
266
330
  }
267
331
  export function fnSUBTOTAL(args) {
268
- const funcNumV = toNumberRV(args[0]);
332
+ const funcNumV = toNumberRV(topLeft(args[0]));
269
333
  if (isError(funcNumV)) {
270
334
  return funcNumV;
271
335
  }
@@ -317,7 +381,7 @@ export function fnSUBTOTAL(args) {
317
381
  }
318
382
  }
319
383
  export function fnAGGREGATE(args) {
320
- const funcNumV = toNumberRV(args[0]);
384
+ const funcNumV = toNumberRV(topLeft(args[0]));
321
385
  if (isError(funcNumV)) {
322
386
  return funcNumV;
323
387
  }
@@ -331,7 +395,7 @@ export function fnAGGREGATE(args) {
331
395
  // 5 → ignore hidden rows (keep nested)
332
396
  // 6 → ignore errors (keep nested)
333
397
  // 7 → ignore hidden rows + errors (keep nested)
334
- const optV = args[1] !== undefined ? toNumberRV(args[1]) : undefined;
398
+ const optV = args[1] !== undefined ? toNumberRV(topLeft(args[1])) : undefined;
335
399
  if (optV && isError(optV)) {
336
400
  return optV;
337
401
  }
@@ -396,19 +460,22 @@ export function fnAGGREGATE(args) {
396
460
  }
397
461
  }
398
462
  export function fnSEQUENCE(args) {
399
- const rowsV = toNumberRV(args[0]);
463
+ const rowsV = toNumberRV(topLeft(args[0]));
400
464
  if (isError(rowsV)) {
401
465
  return rowsV;
402
466
  }
403
- const colsV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(1);
467
+ // Blank `cols` / `start` / `step` → Excel defaults 1 / 1 / 1. Without
468
+ // the blank guards, `SEQUENCE(5, )` coerces blank → 0 → `colCount < 1`
469
+ // → #NUM! instead of Excel's single-column sequence.
470
+ const colsV = args.length > 1 && args[1].kind !== RVKind.Blank ? toNumberRV(topLeft(args[1])) : rvNumber(1);
404
471
  if (isError(colsV)) {
405
472
  return colsV;
406
473
  }
407
- const startV = args.length > 2 ? toNumberRV(args[2]) : rvNumber(1);
474
+ const startV = args.length > 2 && args[2].kind !== RVKind.Blank ? toNumberRV(topLeft(args[2])) : rvNumber(1);
408
475
  if (isError(startV)) {
409
476
  return startV;
410
477
  }
411
- const stepV = args.length > 3 ? toNumberRV(args[3]) : rvNumber(1);
478
+ const stepV = args.length > 3 && args[3].kind !== RVKind.Blank ? toNumberRV(topLeft(args[3])) : rvNumber(1);
412
479
  if (isError(stepV)) {
413
480
  return stepV;
414
481
  }
@@ -440,23 +507,29 @@ export function fnSEQUENCE(args) {
440
507
  return rvArray(result);
441
508
  }
442
509
  export function fnRANDARRAY(args) {
443
- const rowsV = args.length > 0 ? toNumberRV(args[0]) : rvNumber(1);
510
+ // Each arg has a distinct Excel default; blank must be distinguished
511
+ // from an explicit 0, which would cause rows/cols validation to
512
+ // reject the call. Blanks → engine defaults (rows=1, cols=1, min=0,
513
+ // max=1, whole=FALSE).
514
+ const rowsV = args.length > 0 && args[0].kind !== RVKind.Blank ? toNumberRV(topLeft(args[0])) : rvNumber(1);
444
515
  if (isError(rowsV)) {
445
516
  return rowsV;
446
517
  }
447
- const colsV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(1);
518
+ const colsV = args.length > 1 && args[1].kind !== RVKind.Blank ? toNumberRV(topLeft(args[1])) : rvNumber(1);
448
519
  if (isError(colsV)) {
449
520
  return colsV;
450
521
  }
451
- const minV = args.length > 2 ? toNumberRV(args[2]) : rvNumber(0);
522
+ const minV = args.length > 2 && args[2].kind !== RVKind.Blank ? toNumberRV(topLeft(args[2])) : rvNumber(0);
452
523
  if (isError(minV)) {
453
524
  return minV;
454
525
  }
455
- const maxV = args.length > 3 ? toNumberRV(args[3]) : rvNumber(1);
526
+ const maxV = args.length > 3 && args[3].kind !== RVKind.Blank ? toNumberRV(topLeft(args[3])) : rvNumber(1);
456
527
  if (isError(maxV)) {
457
528
  return maxV;
458
529
  }
459
- const wholeV = args.length > 4 ? toBooleanRV(args[4]) : { kind: RVKind.Boolean, value: false };
530
+ const wholeV = args.length > 4 && args[4].kind !== RVKind.Blank
531
+ ? toBooleanRV(topLeft(args[4]))
532
+ : { kind: RVKind.Boolean, value: false };
460
533
  if (isError(wholeV)) {
461
534
  return wholeV;
462
535
  }
@@ -503,11 +576,13 @@ export function fnTOCOL(args) {
503
576
  return rvArray([[topLeft(args[0])]]);
504
577
  }
505
578
  const arr = args[0];
506
- const ignoreV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(0);
579
+ const ignoreV = args.length > 1 ? toNumberRV(topLeft(args[1])) : rvNumber(0);
507
580
  if (isError(ignoreV)) {
508
581
  return ignoreV;
509
582
  }
510
- const scanV = args.length > 2 ? toBooleanRV(args[2]) : { kind: RVKind.Boolean, value: false };
583
+ const scanV = args.length > 2
584
+ ? toBooleanRV(topLeft(args[2]))
585
+ : { kind: RVKind.Boolean, value: false };
511
586
  if (isError(scanV)) {
512
587
  return scanV;
513
588
  }
@@ -545,11 +620,13 @@ export function fnTOROW(args) {
545
620
  return rvArray([[topLeft(args[0])]]);
546
621
  }
547
622
  const arr = args[0];
548
- const ignoreV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(0);
623
+ const ignoreV = args.length > 1 ? toNumberRV(topLeft(args[1])) : rvNumber(0);
549
624
  if (isError(ignoreV)) {
550
625
  return ignoreV;
551
626
  }
552
- const scanV = args.length > 2 ? toBooleanRV(args[2]) : { kind: RVKind.Boolean, value: false };
627
+ const scanV = args.length > 2
628
+ ? toBooleanRV(topLeft(args[2]))
629
+ : { kind: RVKind.Boolean, value: false };
553
630
  if (isError(scanV)) {
554
631
  return scanV;
555
632
  }
@@ -589,11 +666,18 @@ export function fnCHOOSEROWS(args) {
589
666
  }
590
667
  const result = [];
591
668
  for (let i = 1; i < args.length; i++) {
592
- const nV = toNumberRV(args[i]);
669
+ const nV = toNumberRV(topLeft(args[i]));
593
670
  if (isError(nV)) {
594
671
  return nV;
595
672
  }
596
- const idx = nV.value > 0 ? nV.value - 1 : d.height + nV.value;
673
+ // Excel truncates toward zero before the bounds check; without this,
674
+ // `CHOOSEROWS(A1:A5, 2.5)` would compute `idx = 1.5` and end up
675
+ // reading `d.rows[1.5]` (= undefined), throwing on the next index.
676
+ const raw = Math.trunc(nV.value);
677
+ if (raw === 0) {
678
+ return ERRORS.VALUE;
679
+ }
680
+ const idx = raw > 0 ? raw - 1 : d.height + raw;
597
681
  if (idx < 0 || idx >= d.height) {
598
682
  return ERRORS.VALUE;
599
683
  }
@@ -612,11 +696,17 @@ export function fnCHOOSECOLS(args) {
612
696
  }
613
697
  const ci = [];
614
698
  for (let i = 1; i < args.length; i++) {
615
- const nV = toNumberRV(args[i]);
699
+ const nV = toNumberRV(topLeft(args[i]));
616
700
  if (isError(nV)) {
617
701
  return nV;
618
702
  }
619
- const idx = nV.value > 0 ? nV.value - 1 : d.width + nV.value;
703
+ // Truncate fractional indices before the bounds check see CHOOSEROWS
704
+ // for the rationale (fractional indices silently crash getCell).
705
+ const raw = Math.trunc(nV.value);
706
+ if (raw === 0) {
707
+ return ERRORS.VALUE;
708
+ }
709
+ const idx = raw > 0 ? raw - 1 : d.width + raw;
620
710
  if (idx < 0 || idx >= d.width) {
621
711
  return ERRORS.VALUE;
622
712
  }
@@ -706,18 +796,24 @@ export function fnTAKE(args) {
706
796
  if (!d) {
707
797
  return ERRORS.VALUE;
708
798
  }
709
- const rowsV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(d.height);
799
+ const rowsV = args.length > 1 ? toNumberRV(topLeft(args[1])) : rvNumber(d.height);
710
800
  if (isError(rowsV)) {
711
801
  return rowsV;
712
802
  }
713
- const colsV = args.length > 2 ? toNumberRV(args[2]) : rvNumber(d.width);
803
+ const colsV = args.length > 2 ? toNumberRV(topLeft(args[2])) : rvNumber(d.width);
714
804
  if (isError(colsV)) {
715
805
  return colsV;
716
806
  }
717
- const rS = rowsV.value >= 0 ? 0 : Math.max(0, d.height + rowsV.value);
718
- const rE = rowsV.value >= 0 ? Math.min(rowsV.value, d.height) : d.height;
719
- const cS = colsV.value >= 0 ? 0 : Math.max(0, d.width + colsV.value);
720
- const cE = colsV.value >= 0 ? Math.min(colsV.value, d.width) : d.width;
807
+ // Truncate toward zero before using these as array indices. Without
808
+ // it, `TAKE(A1:A5, 2.7)` lands `rE = 2.7` in the slice bounds; V8
809
+ // silently walks the integer range `0..2` but a misuse of the value
810
+ // elsewhere (`d.rows[r]` with `r` fractional) would return undefined.
811
+ const rows = Math.trunc(rowsV.value);
812
+ const cols = Math.trunc(colsV.value);
813
+ const rS = rows >= 0 ? 0 : Math.max(0, d.height + rows);
814
+ const rE = rows >= 0 ? Math.min(rows, d.height) : d.height;
815
+ const cS = cols >= 0 ? 0 : Math.max(0, d.width + cols);
816
+ const cE = cols >= 0 ? Math.min(cols, d.width) : d.width;
721
817
  const result = [];
722
818
  for (let r = rS; r < rE; r++) {
723
819
  const row = [];
@@ -733,18 +829,21 @@ export function fnDROP(args) {
733
829
  if (!d) {
734
830
  return ERRORS.VALUE;
735
831
  }
736
- const rowsV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(0);
832
+ const rowsV = args.length > 1 ? toNumberRV(topLeft(args[1])) : rvNumber(0);
737
833
  if (isError(rowsV)) {
738
834
  return rowsV;
739
835
  }
740
- const colsV = args.length > 2 ? toNumberRV(args[2]) : rvNumber(0);
836
+ const colsV = args.length > 2 ? toNumberRV(topLeft(args[2])) : rvNumber(0);
741
837
  if (isError(colsV)) {
742
838
  return colsV;
743
839
  }
744
- const rS = rowsV.value >= 0 ? rowsV.value : 0;
745
- const rE = rowsV.value >= 0 ? d.height : d.height + rowsV.value;
746
- const cS = colsV.value >= 0 ? colsV.value : 0;
747
- const cE = colsV.value >= 0 ? d.width : d.width + colsV.value;
840
+ // Truncate see TAKE for rationale.
841
+ const rows = Math.trunc(rowsV.value);
842
+ const cols = Math.trunc(colsV.value);
843
+ const rS = rows >= 0 ? rows : 0;
844
+ const rE = rows >= 0 ? d.height : d.height + rows;
845
+ const cS = cols >= 0 ? cols : 0;
846
+ const cE = cols >= 0 ? d.width : d.width + cols;
748
847
  const result = [];
749
848
  for (let r = rS; r < rE; r++) {
750
849
  const row = [];
@@ -768,18 +867,22 @@ export function fnWRAPROWS(args) {
768
867
  flat.push(getCell(arr, r, c));
769
868
  }
770
869
  }
771
- const wcV = toNumberRV(args[1]);
870
+ const wcV = toNumberRV(topLeft(args[1]));
772
871
  if (isError(wcV)) {
773
872
  return wcV;
774
873
  }
775
- if (wcV.value < 1) {
874
+ // Truncate toward zero before using as a stride — `i += 2.5` and
875
+ // `row.length < 2.5` work by accidental float coercion in V8, but the
876
+ // intent is an integer wrap width; encode it explicitly.
877
+ const wc = Math.trunc(wcV.value);
878
+ if (wc < 1) {
776
879
  return ERRORS.VALUE;
777
880
  }
778
881
  const pad = args.length > 2 ? topLeft(args[2]) : ERRORS.NA;
779
882
  const result = [];
780
- for (let i = 0; i < flat.length; i += wcV.value) {
781
- const row = flat.slice(i, i + wcV.value);
782
- while (row.length < wcV.value) {
883
+ for (let i = 0; i < flat.length; i += wc) {
884
+ const row = flat.slice(i, i + wc);
885
+ while (row.length < wc) {
783
886
  row.push(pad);
784
887
  }
785
888
  result.push(row);
@@ -797,20 +900,21 @@ export function fnWRAPCOLS(args) {
797
900
  flat.push(getCell(arr, r, c));
798
901
  }
799
902
  }
800
- const wcV = toNumberRV(args[1]);
903
+ const wcV = toNumberRV(topLeft(args[1]));
801
904
  if (isError(wcV)) {
802
905
  return wcV;
803
906
  }
804
- if (wcV.value < 1) {
907
+ const wc = Math.trunc(wcV.value);
908
+ if (wc < 1) {
805
909
  return ERRORS.VALUE;
806
910
  }
807
911
  const pad = args.length > 2 ? topLeft(args[2]) : ERRORS.NA;
808
- const numCols = Math.ceil(flat.length / wcV.value);
912
+ const numCols = Math.ceil(flat.length / wc);
809
913
  const result = [];
810
- for (let r = 0; r < wcV.value; r++) {
914
+ for (let r = 0; r < wc; r++) {
811
915
  const row = [];
812
916
  for (let c = 0; c < numCols; c++) {
813
- const idx = c * wcV.value + r;
917
+ const idx = c * wc + r;
814
918
  row.push(idx < flat.length ? flat[idx] : pad);
815
919
  }
816
920
  result.push(row);
@@ -822,11 +926,11 @@ export function fnEXPAND(args) {
822
926
  if (!d) {
823
927
  return ERRORS.VALUE;
824
928
  }
825
- const rowsV = args.length > 1 ? toNumberRV(args[1]) : rvNumber(d.height);
929
+ const rowsV = args.length > 1 ? toNumberRV(topLeft(args[1])) : rvNumber(d.height);
826
930
  if (isError(rowsV)) {
827
931
  return rowsV;
828
932
  }
829
- const colsV = args.length > 2 ? toNumberRV(args[2]) : rvNumber(d.width);
933
+ const colsV = args.length > 2 ? toNumberRV(topLeft(args[2])) : rvNumber(d.width);
830
934
  if (isError(colsV)) {
831
935
  return colsV;
832
936
  }