@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
@@ -3,8 +3,8 @@
3
3
  * Math / Aggregate Functions — Native RuntimeValue implementation.
4
4
  */
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.fnTRUNC = exports.fnSIGN = exports.fnRANDBETWEEN = exports.fnRAND = exports.fnPI = exports.fnEXP = exports.fnLOG10 = exports.fnLOG = exports.fnLN = exports.fnSQRTPI = exports.fnSQRT = exports.fnROUNDUP = exports.fnROUNDDOWN = exports.fnROUND = exports.fnPOWER = exports.fnMOD = exports.fnINT = exports.fnFLOOR = exports.fnCEILING = exports.fnABS = exports.fnSUMPRODUCT = exports.fnPRODUCT = exports.fnCOUNTBLANK = exports.fnCOUNTA = exports.fnCOUNT = exports.fnMAX = exports.fnMIN = exports.fnAVERAGE = exports.fnSUM = exports.fnACOTH = exports.fnACOT = exports.fnCOTH = exports.fnCSCH = exports.fnSECH = exports.fnCOT = exports.fnCSC = exports.fnSEC = exports.fnATANH = exports.fnACOSH = exports.fnASINH = exports.fnTANH = exports.fnCOSH = exports.fnSINH = exports.fnATAN2 = exports.fnATAN = exports.fnACOS = exports.fnASIN = exports.fnTAN = exports.fnCOS = exports.fnSIN = void 0;
7
- exports.fnSERIESSUM = exports.fnMUNIT = exports.fnMINVERSE = exports.fnMDETERM = exports.fnMMULT = exports.fnPERMUT = exports.fnCOMBINA = exports.fnCOMBIN = exports.fnFACTDOUBLE = exports.fnFACT = exports.fnMULTINOMIAL = exports.fnSUMXMY2 = exports.fnSUMX2PY2 = exports.fnSUMX2MY2 = exports.fnRADIANS = exports.fnDEGREES = exports.fnARABIC = exports.fnROMAN = exports.fnDECIMAL = exports.fnBASE = exports.fnQUOTIENT = exports.fnMROUND = exports.fnODD = exports.fnEVEN = exports.fnLCM = exports.fnGCD = exports.fnSUMSQ = void 0;
6
+ exports.fnPI = exports.fnEXP = exports.fnLOG10 = exports.fnLOG = exports.fnLN = exports.fnSQRTPI = exports.fnSQRT = exports.fnROUNDUP = exports.fnROUNDDOWN = exports.fnROUND = exports.fnPOWER = exports.fnMOD = exports.fnINT = exports.fnFLOOR_PRECISE = exports.fnFLOOR_MATH = exports.fnFLOOR = exports.fnCEILING_PRECISE = exports.fnCEILING_MATH = exports.fnCEILING = exports.fnABS = exports.fnSUMPRODUCT = exports.fnPRODUCT = exports.fnCOUNTBLANK = exports.fnCOUNTA = exports.fnCOUNT = exports.fnMAX = exports.fnMIN = exports.fnAVERAGE = exports.fnSUM = exports.fnACOTH = exports.fnACOT = exports.fnCOTH = exports.fnCSCH = exports.fnSECH = exports.fnCOT = exports.fnCSC = exports.fnSEC = exports.fnATANH = exports.fnACOSH = exports.fnASINH = exports.fnTANH = exports.fnCOSH = exports.fnSINH = exports.fnATAN2 = exports.fnATAN = exports.fnACOS = exports.fnASIN = exports.fnTAN = exports.fnCOS = exports.fnSIN = void 0;
7
+ exports.fnSERIESSUM = exports.fnMUNIT = exports.fnMINVERSE = exports.fnMDETERM = exports.fnMMULT = exports.fnPERMUT = exports.fnCOMBINA = exports.fnCOMBIN = exports.fnFACTDOUBLE = exports.fnFACT = exports.fnMULTINOMIAL = exports.fnSUMXMY2 = exports.fnSUMX2PY2 = exports.fnSUMX2MY2 = exports.fnRADIANS = exports.fnDEGREES = exports.fnARABIC = exports.fnROMAN = exports.fnDECIMAL = exports.fnBASE = exports.fnQUOTIENT = exports.fnMROUND = exports.fnODD = exports.fnEVEN = exports.fnLCM = exports.fnGCD = exports.fnSUMSQ = exports.fnTRUNC = exports.fnSIGN = exports.fnRANDBETWEEN = exports.fnRAND = void 0;
8
8
  const values_1 = require("../runtime/values");
9
9
  const _shared_1 = require("./_shared");
10
10
  // ============================================================================
@@ -275,15 +275,13 @@ exports.fnACOTH = fnACOTH;
275
275
  // Math / Aggregate Functions
276
276
  // ============================================================================
277
277
  const fnSUM = args => {
278
- const nums = (0, _shared_1.flattenNumbers)(args);
279
- const err = (0, _shared_1.firstError)(nums);
278
+ let sum = 0;
279
+ const err = (0, _shared_1.forEachNumber)(args, n => {
280
+ sum += n;
281
+ });
280
282
  if (err) {
281
283
  return err;
282
284
  }
283
- let sum = 0;
284
- for (const n of nums) {
285
- sum += n.value;
286
- }
287
285
  // Fail fast on overflow to Infinity; otherwise the result leaks into
288
286
  // any formula that aggregates it (AVERAGE, STDEV, etc.) and those
289
287
  // downstream callers would then fan #NUM! out across the graph.
@@ -291,64 +289,88 @@ const fnSUM = args => {
291
289
  };
292
290
  exports.fnSUM = fnSUM;
293
291
  const fnAVERAGE = args => {
294
- const nums = (0, _shared_1.flattenNumbers)(args);
295
- const err = (0, _shared_1.firstError)(nums);
292
+ let sum = 0;
293
+ let count = 0;
294
+ const err = (0, _shared_1.forEachNumber)(args, n => {
295
+ sum += n;
296
+ count++;
297
+ });
296
298
  if (err) {
297
299
  return err;
298
300
  }
299
- if (nums.length === 0) {
301
+ if (count === 0) {
300
302
  return values_1.ERRORS.DIV0;
301
303
  }
302
- let sum = 0;
303
- for (const n of nums) {
304
- sum += n.value;
305
- }
306
- const avg = sum / nums.length;
304
+ const avg = sum / count;
307
305
  return Number.isFinite(avg) ? (0, values_1.rvNumber)(avg) : values_1.ERRORS.NUM;
308
306
  };
309
307
  exports.fnAVERAGE = fnAVERAGE;
310
308
  const fnMIN = args => {
311
- const nums = (0, _shared_1.flattenNumbers)(args);
312
- const err = (0, _shared_1.firstError)(nums);
313
- if (err) {
314
- return err;
315
- }
316
- if (nums.length === 0) {
317
- return (0, values_1.rvNumber)(0);
318
- }
319
309
  let min = Infinity;
320
- for (const n of nums) {
321
- if (n.value < min) {
322
- min = n.value;
310
+ let seen = false;
311
+ const err = (0, _shared_1.forEachNumber)(args, n => {
312
+ seen = true;
313
+ if (n < min) {
314
+ min = n;
323
315
  }
316
+ });
317
+ if (err) {
318
+ return err;
324
319
  }
325
- return (0, values_1.rvNumber)(min);
320
+ return seen ? (0, values_1.rvNumber)(min) : (0, values_1.rvNumber)(0);
326
321
  };
327
322
  exports.fnMIN = fnMIN;
328
323
  const fnMAX = args => {
329
- const nums = (0, _shared_1.flattenNumbers)(args);
330
- const err = (0, _shared_1.firstError)(nums);
331
- if (err) {
332
- return err;
333
- }
334
- if (nums.length === 0) {
335
- return (0, values_1.rvNumber)(0);
336
- }
337
324
  let max = -Infinity;
338
- for (const n of nums) {
339
- if (n.value > max) {
340
- max = n.value;
325
+ let seen = false;
326
+ const err = (0, _shared_1.forEachNumber)(args, n => {
327
+ seen = true;
328
+ if (n > max) {
329
+ max = n;
341
330
  }
331
+ });
332
+ if (err) {
333
+ return err;
342
334
  }
343
- return (0, values_1.rvNumber)(max);
335
+ return seen ? (0, values_1.rvNumber)(max) : (0, values_1.rvNumber)(0);
344
336
  };
345
337
  exports.fnMAX = fnMAX;
346
338
  const fnCOUNT = args => {
339
+ // Excel's rules — intentionally asymmetric:
340
+ // - Number cell / scalar → counted (direct + inside array).
341
+ // - Numeric-string DIRECT scalar (`COUNT("5")`) → counted. Inside
342
+ // an array, numeric strings are NOT counted.
343
+ // - Boolean / non-numeric string / blank / error → NOT counted in
344
+ // any context (diverges from COUNTA which counts booleans).
345
+ // Previously the engine flattened everything through `topLeft` and
346
+ // only accepted `Number` — which meant `COUNT("5")` returned 0.
347
347
  let count = 0;
348
- const all = (0, _shared_1.flattenAll)(args);
349
- for (const v of all) {
350
- if (v.kind === 1 /* RVKind.Number */) {
351
- count++;
348
+ for (const arg of args) {
349
+ if (arg.kind === 5 /* RVKind.Array */) {
350
+ for (const row of arg.rows) {
351
+ for (const cell of row) {
352
+ if (cell.kind === 1 /* RVKind.Number */) {
353
+ count++;
354
+ }
355
+ }
356
+ }
357
+ }
358
+ else {
359
+ const s = (0, values_1.topLeft)(arg);
360
+ if (s.kind === 1 /* RVKind.Number */) {
361
+ count++;
362
+ }
363
+ else if (s.kind === 2 /* RVKind.String */) {
364
+ // Numeric strings (including `"5"`, `"3.14"`, `"1e3"`) are
365
+ // counted when supplied as direct scalars. Non-numeric text
366
+ // is silently skipped. Route through `toNumberRV` so the same
367
+ // parser that `VALUE()` uses decides.
368
+ const nr = (0, values_1.toNumberRV)(s);
369
+ if (nr.kind === 1 /* RVKind.Number */) {
370
+ count++;
371
+ }
372
+ }
373
+ // Blank, Boolean, Error, Reference, Lambda → skipped.
352
374
  }
353
375
  }
354
376
  return (0, values_1.rvNumber)(count);
@@ -358,8 +380,12 @@ const fnCOUNTA = args => {
358
380
  let count = 0;
359
381
  const all = (0, _shared_1.flattenAll)(args);
360
382
  for (const v of all) {
361
- // Count everything that is not blank and not empty string
362
- if (v.kind !== 0 /* RVKind.Blank */ && !(v.kind === 2 /* RVKind.String */ && v.value === "")) {
383
+ // Excel: COUNTA counts every non-blank cell, INCLUDING cells that
384
+ // hold an empty string (e.g. a formula that returned `=""`). Only
385
+ // the fully-blank kind is excluded. Previously we also excluded
386
+ // empty strings — that matched Google Sheets but diverged from
387
+ // Excel's documented behaviour.
388
+ if (v.kind !== 0 /* RVKind.Blank */) {
363
389
  count++;
364
390
  }
365
391
  }
@@ -370,6 +396,11 @@ const fnCOUNTBLANK = args => {
370
396
  let count = 0;
371
397
  const all = (0, _shared_1.flattenAll)(args);
372
398
  for (const v of all) {
399
+ // Excel: COUNTBLANK counts both truly-blank cells and cells
400
+ // containing an empty string result (e.g. `=""`). This is
401
+ // intentionally asymmetric with COUNTA — COUNTA+COUNTBLANK can
402
+ // legitimately exceed the total cell count when `=""` formulas
403
+ // are present, matching Excel's documented behaviour.
373
404
  if (v.kind === 0 /* RVKind.Blank */ || (v.kind === 2 /* RVKind.String */ && v.value === "")) {
374
405
  count++;
375
406
  }
@@ -378,18 +409,18 @@ const fnCOUNTBLANK = args => {
378
409
  };
379
410
  exports.fnCOUNTBLANK = fnCOUNTBLANK;
380
411
  const fnPRODUCT = args => {
381
- const nums = (0, _shared_1.flattenNumbers)(args);
382
- const err = (0, _shared_1.firstError)(nums);
412
+ let product = 1;
413
+ let seen = false;
414
+ const err = (0, _shared_1.forEachNumber)(args, n => {
415
+ product *= n;
416
+ seen = true;
417
+ });
383
418
  if (err) {
384
419
  return err;
385
420
  }
386
- if (nums.length === 0) {
421
+ if (!seen) {
387
422
  return (0, values_1.rvNumber)(0);
388
423
  }
389
- let product = 1;
390
- for (const n of nums) {
391
- product *= n.value;
392
- }
393
424
  // Excel surfaces an overflow as #NUM! rather than letting Infinity
394
425
  // propagate into subsequent arithmetic.
395
426
  return isFinite(product) ? (0, values_1.rvNumber)(product) : values_1.ERRORS.NUM;
@@ -428,13 +459,23 @@ const fnSUMPRODUCT = args => {
428
459
  }
429
460
  }
430
461
  }
462
+ // Hoist per-array broadcast classification and row-shape out of the
463
+ // hot (r, c) loop — the shape is constant across all cells, but the
464
+ // previous code re-inspected `arr.height === 1 && arr.width === 1`
465
+ // per cell (arrays.length × rows × cols times).
466
+ const arrayCount = arrays.length;
467
+ const broadcasts = new Array(arrayCount);
468
+ for (let i = 0; i < arrayCount; i++) {
469
+ const arr = arrays[i];
470
+ broadcasts[i] = arr.height === 1 && arr.width === 1;
471
+ }
431
472
  let sum = 0;
432
473
  for (let r = 0; r < rows; r++) {
433
474
  for (let c = 0; c < cols; c++) {
434
475
  let product = 1;
435
- for (const arr of arrays) {
436
- // Broadcast 1x1 arrays to the target cell position.
437
- const val = arr.height === 1 && arr.width === 1 ? arr.rows[0][0] : arr.rows[r][c];
476
+ for (let i = 0; i < arrayCount; i++) {
477
+ const arr = arrays[i];
478
+ const val = broadcasts[i] ? arr.rows[0][0] : arr.rows[r][c];
438
479
  if (val.kind === 4 /* RVKind.Error */) {
439
480
  return val;
440
481
  }
@@ -486,6 +527,68 @@ const fnCEILING = args => {
486
527
  return (0, values_1.rvNumber)(Math.ceil(num.value / sig) * sig);
487
528
  };
488
529
  exports.fnCEILING = fnCEILING;
530
+ /**
531
+ * CEILING.MATH(number, [significance], [mode]) — rounds away from zero
532
+ * by default, or toward zero when `mode` is non-zero AND `number` is
533
+ * negative. Significance is always interpreted by absolute value.
534
+ *
535
+ * Different from CEILING: negative numbers with positive significance
536
+ * are valid (Excel does NOT require same sign), and there is an extra
537
+ * `mode` switch that flips the rounding direction for negatives.
538
+ */
539
+ const fnCEILING_MATH = args => {
540
+ const num = (0, _shared_1.argToNumber)(args[0]);
541
+ if ((0, values_1.isError)(num)) {
542
+ return num;
543
+ }
544
+ // Blank `significance` → Excel default 1 (for positive num) or -1
545
+ // (for negative num); either way |sig| = 1. Use 1 explicitly since
546
+ // the algorithm below works on `Math.abs(sig)`.
547
+ const sigRV = args.length > 1 && args[1].kind !== 0 /* RVKind.Blank */ ? (0, _shared_1.argToNumber)(args[1]) : (0, values_1.rvNumber)(1);
548
+ if ((0, values_1.isError)(sigRV)) {
549
+ return sigRV;
550
+ }
551
+ const sigAbs = Math.abs(sigRV.value);
552
+ if (sigAbs === 0) {
553
+ return (0, values_1.rvNumber)(0);
554
+ }
555
+ const modeRV = args.length > 2 && args[2].kind !== 0 /* RVKind.Blank */ ? (0, _shared_1.argToNumber)(args[2]) : (0, values_1.rvNumber)(0);
556
+ if ((0, values_1.isError)(modeRV)) {
557
+ return modeRV;
558
+ }
559
+ // Round away from zero by default; toward zero when `mode` is non-zero
560
+ // AND the number is negative. For positive numbers `mode` has no effect.
561
+ if (num.value >= 0) {
562
+ return (0, values_1.rvNumber)(Math.ceil(num.value / sigAbs) * sigAbs);
563
+ }
564
+ if (modeRV.value !== 0) {
565
+ // Round away from zero (toward −∞) for negatives: use `Math.floor`.
566
+ return (0, values_1.rvNumber)(Math.floor(num.value / sigAbs) * sigAbs);
567
+ }
568
+ // Default: round toward zero for negatives.
569
+ return (0, values_1.rvNumber)(Math.ceil(num.value / sigAbs) * sigAbs);
570
+ };
571
+ exports.fnCEILING_MATH = fnCEILING_MATH;
572
+ /**
573
+ * CEILING.PRECISE / ISO.CEILING — always rounds toward +∞ (irrespective
574
+ * of sign), using the absolute value of significance.
575
+ */
576
+ const fnCEILING_PRECISE = args => {
577
+ const num = (0, _shared_1.argToNumber)(args[0]);
578
+ if ((0, values_1.isError)(num)) {
579
+ return num;
580
+ }
581
+ const sigRV = args.length > 1 && args[1].kind !== 0 /* RVKind.Blank */ ? (0, _shared_1.argToNumber)(args[1]) : (0, values_1.rvNumber)(1);
582
+ if ((0, values_1.isError)(sigRV)) {
583
+ return sigRV;
584
+ }
585
+ const sigAbs = Math.abs(sigRV.value);
586
+ if (sigAbs === 0) {
587
+ return (0, values_1.rvNumber)(0);
588
+ }
589
+ return (0, values_1.rvNumber)(Math.ceil(num.value / sigAbs) * sigAbs);
590
+ };
591
+ exports.fnCEILING_PRECISE = fnCEILING_PRECISE;
489
592
  const fnFLOOR = args => {
490
593
  const num = (0, _shared_1.argToNumber)(args[0]);
491
594
  if ((0, values_1.isError)(num)) {
@@ -505,6 +608,60 @@ const fnFLOOR = args => {
505
608
  return (0, values_1.rvNumber)(Math.floor(num.value / sig) * sig);
506
609
  };
507
610
  exports.fnFLOOR = fnFLOOR;
611
+ /**
612
+ * FLOOR.MATH(number, [significance], [mode]) — rounds toward zero by
613
+ * default, or away from zero when `mode` is non-zero AND `number` is
614
+ * negative. Uses `|significance|` so negative significance never
615
+ * produces #NUM!.
616
+ */
617
+ const fnFLOOR_MATH = args => {
618
+ const num = (0, _shared_1.argToNumber)(args[0]);
619
+ if ((0, values_1.isError)(num)) {
620
+ return num;
621
+ }
622
+ const sigRV = args.length > 1 && args[1].kind !== 0 /* RVKind.Blank */ ? (0, _shared_1.argToNumber)(args[1]) : (0, values_1.rvNumber)(1);
623
+ if ((0, values_1.isError)(sigRV)) {
624
+ return sigRV;
625
+ }
626
+ const sigAbs = Math.abs(sigRV.value);
627
+ if (sigAbs === 0) {
628
+ return (0, values_1.rvNumber)(0);
629
+ }
630
+ const modeRV = args.length > 2 && args[2].kind !== 0 /* RVKind.Blank */ ? (0, _shared_1.argToNumber)(args[2]) : (0, values_1.rvNumber)(0);
631
+ if ((0, values_1.isError)(modeRV)) {
632
+ return modeRV;
633
+ }
634
+ // Positive numbers always round toward zero (down). Negative numbers
635
+ // default to rounding away from zero (down = further negative); `mode`
636
+ // non-zero flips to rounding toward zero.
637
+ if (num.value >= 0) {
638
+ return (0, values_1.rvNumber)(Math.floor(num.value / sigAbs) * sigAbs);
639
+ }
640
+ if (modeRV.value !== 0) {
641
+ return (0, values_1.rvNumber)(Math.ceil(num.value / sigAbs) * sigAbs);
642
+ }
643
+ return (0, values_1.rvNumber)(Math.floor(num.value / sigAbs) * sigAbs);
644
+ };
645
+ exports.fnFLOOR_MATH = fnFLOOR_MATH;
646
+ /**
647
+ * FLOOR.PRECISE — always rounds toward −∞ using `|significance|`.
648
+ */
649
+ const fnFLOOR_PRECISE = args => {
650
+ const num = (0, _shared_1.argToNumber)(args[0]);
651
+ if ((0, values_1.isError)(num)) {
652
+ return num;
653
+ }
654
+ const sigRV = args.length > 1 && args[1].kind !== 0 /* RVKind.Blank */ ? (0, _shared_1.argToNumber)(args[1]) : (0, values_1.rvNumber)(1);
655
+ if ((0, values_1.isError)(sigRV)) {
656
+ return sigRV;
657
+ }
658
+ const sigAbs = Math.abs(sigRV.value);
659
+ if (sigAbs === 0) {
660
+ return (0, values_1.rvNumber)(0);
661
+ }
662
+ return (0, values_1.rvNumber)(Math.floor(num.value / sigAbs) * sigAbs);
663
+ };
664
+ exports.fnFLOOR_PRECISE = fnFLOOR_PRECISE;
508
665
  const fnINT = args => {
509
666
  const n = (0, _shared_1.argToNumber)(args[0]);
510
667
  return (0, values_1.isError)(n) ? n : (0, values_1.rvNumber)(Math.floor(n.value));
@@ -639,7 +796,11 @@ const fnLOG = args => {
639
796
  if (n.value <= 0) {
640
797
  return values_1.ERRORS.NUM;
641
798
  }
642
- const baseRV = args.length > 1 ? (0, _shared_1.argToNumber)(args[1]) : (0, values_1.rvNumber)(10);
799
+ // Blank 2nd arg default base 10. Without this guard, `LOG(100, )`
800
+ // coerces blank → 0 via argToNumber and falls into the `<= 0` branch,
801
+ // incorrectly returning #NUM!. Excel treats the omitted / blank slot
802
+ // as "use the default".
803
+ const baseRV = args.length > 1 && args[1].kind !== 0 /* RVKind.Blank */ ? (0, _shared_1.argToNumber)(args[1]) : (0, values_1.rvNumber)(10);
643
804
  if ((0, values_1.isError)(baseRV)) {
644
805
  return baseRV;
645
806
  }
@@ -722,15 +883,13 @@ const fnTRUNC = args => {
722
883
  };
723
884
  exports.fnTRUNC = fnTRUNC;
724
885
  const fnSUMSQ = args => {
725
- const nums = (0, _shared_1.flattenNumbers)(args);
726
- const err = (0, _shared_1.firstError)(nums);
886
+ let sum = 0;
887
+ const err = (0, _shared_1.forEachNumber)(args, n => {
888
+ sum += n * n;
889
+ });
727
890
  if (err) {
728
891
  return err;
729
892
  }
730
- let sum = 0;
731
- for (const n of nums) {
732
- sum += n.value ** 2;
733
- }
734
893
  return isFinite(sum) ? (0, values_1.rvNumber)(sum) : values_1.ERRORS.NUM;
735
894
  };
736
895
  exports.fnSUMSQ = fnSUMSQ;
@@ -888,12 +1047,24 @@ const fnBASE = args => {
888
1047
  if (radix.value < 2 || radix.value > 36) {
889
1048
  return values_1.ERRORS.NUM;
890
1049
  }
1050
+ // Excel's BASE requires `num` in `[0, 2^53)`. Negative inputs produce
1051
+ // a `-` prefix via JS `.toString(radix)` — Excel rejects them. Very
1052
+ // large inputs exceed the precise integer range and would corrupt the
1053
+ // lower digits; Excel reports #NUM! there too.
1054
+ if (num.value < 0 || num.value >= 2 ** 53) {
1055
+ return values_1.ERRORS.NUM;
1056
+ }
891
1057
  const minLenRV = args.length > 2 ? (0, _shared_1.argToNumber)(args[2]) : (0, values_1.rvNumber)(0);
892
1058
  if ((0, values_1.isError)(minLenRV)) {
893
1059
  return minLenRV;
894
1060
  }
1061
+ // `minLen` must be in `[0, 255]`; Excel rejects larger widths.
1062
+ const minLen = Math.trunc(minLenRV.value);
1063
+ if (minLen < 0 || minLen > 255) {
1064
+ return values_1.ERRORS.NUM;
1065
+ }
895
1066
  const result = Math.floor(num.value).toString(Math.floor(radix.value)).toUpperCase();
896
- return (0, values_1.rvString)(minLenRV.value > 0 ? result.padStart(minLenRV.value, "0") : result);
1067
+ return (0, values_1.rvString)(minLen > 0 ? result.padStart(minLen, "0") : result);
897
1068
  };
898
1069
  exports.fnBASE = fnBASE;
899
1070
  const fnDECIMAL = args => {
@@ -961,10 +1132,10 @@ const fnROMAN = args => {
961
1132
  if ((0, values_1.isError)(f)) {
962
1133
  return f;
963
1134
  }
964
- if (f.value === 1 && f.value === 1) {
965
- // Boolean inputs flow through argToNumber as 0/1 already.
966
- }
967
- form = Math.floor(f.value);
1135
+ // Excel accepts TRUE/FALSE and 0..4 for `form`. argToNumber already
1136
+ // coerces booleans to 0/1, so nothing extra is needed here — we
1137
+ // just truncate toward zero and bounds-check.
1138
+ form = Math.trunc(f.value);
968
1139
  if (form < 0 || form > 4) {
969
1140
  return values_1.ERRORS.VALUE;
970
1141
  }
@@ -1070,8 +1241,15 @@ function sumPairedArrays(args, combine) {
1070
1241
  if (a0.kind !== 5 /* RVKind.Array */ || a1.kind !== 5 /* RVKind.Array */) {
1071
1242
  return values_1.ERRORS.VALUE;
1072
1243
  }
1073
- const h = Math.min(a0.height, a1.height);
1074
- const w = Math.min(a0.width, a1.width);
1244
+ // Excel requires matching shapes — `SUMX2MY2`/`SUMX2PY2`/`SUMXMY2`
1245
+ // return `#N/A` when the two arrays have different cell counts.
1246
+ // Previously we silently clamped to the min, producing a numeric
1247
+ // result that quietly dropped the tail of the longer array.
1248
+ if (a0.height !== a1.height || a0.width !== a1.width) {
1249
+ return values_1.ERRORS.NA;
1250
+ }
1251
+ const h = a0.height;
1252
+ const w = a0.width;
1075
1253
  let sum = 0;
1076
1254
  for (let r = 0; r < h; r++) {
1077
1255
  for (let c = 0; c < w; c++) {
@@ -1241,6 +1419,12 @@ const fnPERMUT = args => {
1241
1419
  let result = 1;
1242
1420
  for (let i = 0; i < ki; i++) {
1243
1421
  result *= ni - i;
1422
+ // Excel surfaces an overflow as #NUM! rather than silently persisting
1423
+ // Infinity. Without this guard `PERMUT(200, 50)` returns Infinity
1424
+ // which then poisons every formula that references the cell.
1425
+ if (!Number.isFinite(result)) {
1426
+ return values_1.ERRORS.NUM;
1427
+ }
1244
1428
  }
1245
1429
  return (0, values_1.rvNumber)(result);
1246
1430
  };