@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
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Math / Aggregate Functions — Native RuntimeValue implementation.
|
|
4
4
|
*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
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
|
-
|
|
279
|
-
const err = (0, _shared_1.
|
|
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
|
-
|
|
295
|
-
|
|
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 (
|
|
301
|
+
if (count === 0) {
|
|
300
302
|
return values_1.ERRORS.DIV0;
|
|
301
303
|
}
|
|
302
|
-
|
|
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
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
//
|
|
362
|
-
|
|
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
|
-
|
|
382
|
-
|
|
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 (
|
|
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 (
|
|
436
|
-
|
|
437
|
-
const val =
|
|
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
|
-
|
|
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
|
-
|
|
726
|
-
const err = (0, _shared_1.
|
|
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)(
|
|
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
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
form = Math.
|
|
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
|
-
|
|
1074
|
-
|
|
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
|
};
|