@cj-tech-master/excelts 9.2.1 → 9.3.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/README.md +25 -2
- package/README_zh.md +29 -6
- package/dist/browser/index.browser.d.ts +1 -1
- package/dist/browser/index.browser.js +4 -0
- package/dist/browser/index.d.ts +1 -1
- package/dist/browser/index.js +4 -0
- package/dist/browser/modules/excel/cell.d.ts +17 -3
- package/dist/browser/modules/excel/cell.js +170 -22
- package/dist/browser/modules/excel/defined-names.d.ts +96 -1
- package/dist/browser/modules/excel/defined-names.js +411 -21
- package/dist/browser/modules/excel/image.d.ts +11 -0
- package/dist/browser/modules/excel/image.js +24 -1
- package/dist/browser/modules/excel/stream/workbook-reader.browser.d.ts +9 -3
- package/dist/browser/modules/excel/stream/workbook-reader.browser.js +14 -0
- package/dist/browser/modules/excel/stream/workbook-reader.d.ts +2 -1
- package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +39 -5
- package/dist/browser/modules/excel/stream/workbook-writer.browser.js +48 -1
- package/dist/browser/modules/excel/stream/workbook-writer.d.ts +3 -2
- package/dist/browser/modules/excel/stream/worksheet-reader.js +17 -1
- package/dist/browser/modules/excel/stream/worksheet-writer.d.ts +39 -6
- package/dist/browser/modules/excel/stream/worksheet-writer.js +45 -5
- package/dist/browser/modules/excel/table.js +15 -2
- package/dist/browser/modules/excel/types.d.ts +133 -2
- package/dist/browser/modules/excel/utils/col-cache.d.ts +1 -0
- package/dist/browser/modules/excel/utils/col-cache.js +15 -0
- package/dist/browser/modules/excel/utils/drawing-utils.d.ts +3 -3
- package/dist/browser/modules/excel/utils/drawing-utils.js +4 -0
- package/dist/browser/modules/excel/utils/external-link-formula.d.ts +76 -0
- package/dist/browser/modules/excel/utils/external-link-formula.js +208 -0
- package/dist/browser/modules/excel/utils/iterate-stream.d.ts +9 -3
- package/dist/browser/modules/excel/utils/iterate-stream.js +3 -1
- package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +19 -0
- package/dist/browser/modules/excel/utils/ooxml-paths.js +37 -2
- package/dist/browser/modules/excel/utils/shared-strings.d.ts +8 -3
- package/dist/browser/modules/excel/utils/shared-strings.js +21 -2
- package/dist/browser/modules/excel/utils/workbook-protection.d.ts +30 -0
- package/dist/browser/modules/excel/utils/workbook-protection.js +30 -0
- package/dist/browser/modules/excel/workbook.browser.d.ts +257 -6
- package/dist/browser/modules/excel/workbook.browser.js +318 -34
- package/dist/browser/modules/excel/workbook.d.ts +1 -1
- package/dist/browser/modules/excel/worksheet.d.ts +3 -1
- package/dist/browser/modules/excel/worksheet.js +21 -2
- package/dist/browser/modules/excel/xlsx/rel-type.d.ts +15 -0
- package/dist/browser/modules/excel/xlsx/rel-type.js +16 -1
- package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +6 -5
- package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
- package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.d.ts +84 -0
- package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.js +330 -0
- package/dist/browser/modules/excel/xlsx/xform/book/external-reference-xform.d.ts +17 -0
- package/dist/browser/modules/excel/xlsx/xform/book/external-reference-xform.js +24 -0
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.d.ts +3 -0
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-protection-xform.d.ts +20 -0
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-protection-xform.js +66 -0
- package/dist/browser/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
- package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +19 -1
- package/dist/browser/modules/excel/xlsx/xform/core/metadata-xform.d.ts +56 -0
- package/dist/browser/modules/excel/xlsx/xform/core/metadata-xform.js +158 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.d.ts +26 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +105 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
- package/dist/browser/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
- package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.d.ts +1 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
- package/dist/browser/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
- package/dist/browser/modules/excel/xlsx/xform/sheet/ignored-errors-xform.d.ts +21 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +80 -0
- package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
- package/dist/browser/modules/excel/xlsx/xform/style/border-xform.js +4 -1
- package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +172 -13
- package/dist/browser/modules/excel/xlsx/xlsx.browser.js +410 -20
- package/dist/browser/modules/excel/xlsx/xlsx.d.ts +7 -4
- package/dist/browser/modules/excel/xlsx/xlsx.js +4 -5
- package/dist/browser/modules/formula/compile/address-utils.d.ts +62 -0
- package/dist/browser/modules/formula/compile/address-utils.js +83 -0
- package/dist/browser/modules/formula/compile/binder.d.ts +42 -0
- package/dist/browser/modules/formula/compile/binder.js +487 -0
- package/dist/browser/modules/formula/compile/bound-ast.d.ts +230 -0
- package/dist/browser/modules/formula/compile/bound-ast.js +80 -0
- package/dist/browser/modules/formula/compile/compiled-formula.d.ts +137 -0
- package/dist/browser/modules/formula/compile/compiled-formula.js +383 -0
- package/dist/browser/modules/formula/compile/dependency-analysis.d.ts +93 -0
- package/dist/browser/modules/formula/compile/dependency-analysis.js +432 -0
- package/dist/browser/modules/formula/compile/structured-ref-utils.d.ts +93 -0
- package/dist/browser/modules/formula/compile/structured-ref-utils.js +136 -0
- package/dist/browser/modules/formula/default-syntax-probe.d.ts +79 -0
- package/dist/browser/modules/formula/default-syntax-probe.js +83 -0
- package/dist/browser/modules/formula/functions/_date-context.d.ts +4 -0
- package/dist/browser/modules/formula/functions/_date-context.js +29 -0
- package/dist/browser/modules/formula/functions/_shared.d.ts +121 -0
- package/dist/browser/modules/formula/functions/_shared.js +381 -0
- package/dist/browser/modules/formula/functions/conditional.d.ts +27 -0
- package/dist/browser/modules/formula/functions/conditional.js +343 -0
- package/dist/browser/modules/formula/functions/database.d.ts +37 -0
- package/dist/browser/modules/formula/functions/database.js +274 -0
- package/dist/browser/modules/formula/functions/date.d.ts +61 -0
- package/dist/browser/modules/formula/functions/date.js +855 -0
- package/dist/browser/modules/formula/functions/dynamic-array.d.ts +23 -0
- package/dist/browser/modules/formula/functions/dynamic-array.js +860 -0
- package/dist/browser/modules/formula/functions/engineering.d.ts +57 -0
- package/dist/browser/modules/formula/functions/engineering.js +1128 -0
- package/dist/browser/modules/formula/functions/financial.d.ts +202 -0
- package/dist/browser/modules/formula/functions/financial.js +2296 -0
- package/dist/browser/modules/formula/functions/lookup.d.ts +18 -0
- package/dist/browser/modules/formula/functions/lookup.js +886 -0
- package/dist/browser/modules/formula/functions/math.d.ts +114 -0
- package/dist/browser/modules/formula/functions/math.js +1406 -0
- package/dist/browser/modules/formula/functions/statistical.d.ts +193 -0
- package/dist/browser/modules/formula/functions/statistical.js +3390 -0
- package/dist/browser/modules/formula/functions/text.d.ts +86 -0
- package/dist/browser/modules/formula/functions/text.js +1845 -0
- package/dist/browser/modules/formula/host-registry.d.ts +53 -0
- package/dist/browser/modules/formula/host-registry.js +69 -0
- package/dist/browser/modules/formula/index.d.ts +39 -0
- package/dist/browser/modules/formula/index.js +49 -0
- package/dist/browser/modules/formula/install.d.ts +62 -0
- package/dist/browser/modules/formula/install.js +88 -0
- package/dist/browser/modules/formula/integration/apply-writeback-plan.d.ts +26 -0
- package/dist/browser/modules/formula/integration/apply-writeback-plan.js +210 -0
- package/dist/browser/modules/formula/integration/calculate-formulas-impl.d.ts +30 -0
- package/dist/browser/modules/formula/integration/calculate-formulas-impl.js +616 -0
- package/dist/browser/modules/formula/integration/calculate-formulas.d.ts +67 -0
- package/dist/browser/modules/formula/integration/calculate-formulas.js +68 -0
- package/dist/browser/modules/formula/integration/formula-instance.d.ts +64 -0
- package/dist/browser/modules/formula/integration/formula-instance.js +79 -0
- package/dist/browser/modules/formula/integration/workbook-adapter.d.ts +26 -0
- package/dist/browser/modules/formula/integration/workbook-adapter.js +324 -0
- package/dist/browser/modules/formula/integration/workbook-snapshot.d.ts +267 -0
- package/dist/browser/modules/formula/integration/workbook-snapshot.js +77 -0
- package/dist/browser/modules/formula/materialize/build-writeback-plan.d.ts +34 -0
- package/dist/browser/modules/formula/materialize/build-writeback-plan.js +473 -0
- package/dist/browser/modules/formula/materialize/spill-engine.d.ts +9 -0
- package/dist/browser/modules/formula/materialize/spill-engine.js +38 -0
- package/dist/browser/modules/formula/materialize/types.d.ts +179 -0
- package/dist/browser/modules/formula/materialize/types.js +29 -0
- package/dist/browser/modules/formula/materialize/writeback-plan.d.ts +167 -0
- package/dist/browser/modules/formula/materialize/writeback-plan.js +27 -0
- package/dist/browser/modules/formula/runtime/evaluator.d.ts +151 -0
- package/dist/browser/modules/formula/runtime/evaluator.js +2291 -0
- package/dist/browser/modules/formula/runtime/function-registry.d.ts +47 -0
- package/dist/browser/modules/formula/runtime/function-registry.js +840 -0
- package/dist/browser/modules/formula/runtime/values.d.ts +211 -0
- package/dist/browser/modules/formula/runtime/values.js +385 -0
- package/dist/browser/modules/formula/syntax/ast.d.ts +129 -0
- package/dist/browser/modules/formula/syntax/ast.js +28 -0
- package/dist/browser/modules/formula/syntax/parser.d.ts +18 -0
- package/dist/browser/modules/formula/syntax/parser.js +439 -0
- package/dist/browser/modules/formula/syntax/token-types.d.ts +153 -0
- package/dist/browser/modules/formula/syntax/token-types.js +59 -0
- package/dist/browser/modules/formula/syntax/tokenizer.d.ts +10 -0
- package/dist/browser/modules/formula/syntax/tokenizer.js +1074 -0
- package/dist/browser/modules/pdf/excel-bridge.js +9 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/modules/excel/cell.js +170 -22
- package/dist/cjs/modules/excel/defined-names.js +411 -21
- package/dist/cjs/modules/excel/image.js +24 -1
- package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +14 -0
- package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +48 -1
- package/dist/cjs/modules/excel/stream/worksheet-reader.js +17 -1
- package/dist/cjs/modules/excel/stream/worksheet-writer.js +45 -5
- package/dist/cjs/modules/excel/table.js +15 -2
- package/dist/cjs/modules/excel/utils/col-cache.js +15 -0
- package/dist/cjs/modules/excel/utils/drawing-utils.js +4 -0
- package/dist/cjs/modules/excel/utils/external-link-formula.js +212 -0
- package/dist/cjs/modules/excel/utils/iterate-stream.js +3 -1
- package/dist/cjs/modules/excel/utils/ooxml-paths.js +42 -2
- package/dist/cjs/modules/excel/utils/shared-strings.js +21 -2
- package/dist/cjs/modules/excel/utils/workbook-protection.js +33 -0
- package/dist/cjs/modules/excel/workbook.browser.js +318 -34
- package/dist/cjs/modules/excel/worksheet.js +20 -1
- package/dist/cjs/modules/excel/xlsx/rel-type.js +16 -1
- package/dist/cjs/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
- package/dist/cjs/modules/excel/xlsx/xform/book/external-link-xform.js +333 -0
- package/dist/cjs/modules/excel/xlsx/xform/book/external-reference-xform.js +27 -0
- package/dist/cjs/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
- package/dist/cjs/modules/excel/xlsx/xform/book/workbook-protection-xform.js +69 -0
- package/dist/cjs/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
- package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +18 -0
- package/dist/cjs/modules/excel/xlsx/xform/core/metadata-xform.js +161 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +108 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
- package/dist/cjs/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
- package/dist/cjs/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
- package/dist/cjs/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
- package/dist/cjs/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +83 -0
- package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
- package/dist/cjs/modules/excel/xlsx/xform/style/border-xform.js +4 -1
- package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +408 -18
- package/dist/cjs/modules/excel/xlsx/xlsx.js +4 -5
- package/dist/cjs/modules/formula/compile/address-utils.js +89 -0
- package/dist/cjs/modules/formula/compile/binder.js +489 -0
- package/dist/cjs/modules/formula/compile/bound-ast.js +68 -0
- package/dist/cjs/modules/formula/compile/compiled-formula.js +387 -0
- package/dist/cjs/modules/formula/compile/dependency-analysis.js +437 -0
- package/dist/cjs/modules/formula/compile/structured-ref-utils.js +141 -0
- package/dist/cjs/modules/formula/default-syntax-probe.js +87 -0
- package/dist/cjs/modules/formula/functions/_date-context.js +33 -0
- package/dist/cjs/modules/formula/functions/_shared.js +396 -0
- package/dist/cjs/modules/formula/functions/conditional.js +354 -0
- package/dist/cjs/modules/formula/functions/database.js +288 -0
- package/dist/cjs/modules/formula/functions/date.js +883 -0
- package/dist/cjs/modules/formula/functions/dynamic-array.js +881 -0
- package/dist/cjs/modules/formula/functions/engineering.js +1183 -0
- package/dist/cjs/modules/formula/functions/financial.js +2348 -0
- package/dist/cjs/modules/formula/functions/lookup.js +902 -0
- package/dist/cjs/modules/formula/functions/math.js +1487 -0
- package/dist/cjs/modules/formula/functions/statistical.js +3488 -0
- package/dist/cjs/modules/formula/functions/text.js +1889 -0
- package/dist/cjs/modules/formula/host-registry.js +75 -0
- package/dist/cjs/modules/formula/index.js +58 -0
- package/dist/cjs/modules/formula/install.js +93 -0
- package/dist/cjs/modules/formula/integration/apply-writeback-plan.js +213 -0
- package/dist/cjs/modules/formula/integration/calculate-formulas-impl.js +619 -0
- package/dist/cjs/modules/formula/integration/calculate-formulas.js +71 -0
- package/dist/cjs/modules/formula/integration/formula-instance.js +82 -0
- package/dist/cjs/modules/formula/integration/workbook-adapter.js +327 -0
- package/dist/cjs/modules/formula/integration/workbook-snapshot.js +84 -0
- package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +475 -0
- package/dist/cjs/modules/formula/materialize/spill-engine.js +42 -0
- package/dist/cjs/modules/formula/materialize/types.js +32 -0
- package/dist/cjs/modules/formula/materialize/writeback-plan.js +28 -0
- package/dist/cjs/modules/formula/runtime/evaluator.js +2298 -0
- package/dist/cjs/modules/formula/runtime/function-registry.js +846 -0
- package/dist/cjs/modules/formula/runtime/values.js +385 -0
- package/dist/cjs/modules/formula/syntax/ast.js +8 -0
- package/dist/cjs/modules/formula/syntax/parser.js +440 -0
- package/dist/cjs/modules/formula/syntax/token-types.js +32 -0
- package/dist/cjs/modules/formula/syntax/tokenizer.js +1076 -0
- package/dist/cjs/modules/pdf/excel-bridge.js +9 -0
- package/dist/esm/index.browser.js +4 -0
- package/dist/esm/index.js +4 -0
- package/dist/esm/modules/excel/cell.js +170 -22
- package/dist/esm/modules/excel/defined-names.js +411 -21
- package/dist/esm/modules/excel/image.js +24 -1
- package/dist/esm/modules/excel/stream/workbook-reader.browser.js +14 -0
- package/dist/esm/modules/excel/stream/workbook-writer.browser.js +48 -1
- package/dist/esm/modules/excel/stream/worksheet-reader.js +17 -1
- package/dist/esm/modules/excel/stream/worksheet-writer.js +45 -5
- package/dist/esm/modules/excel/table.js +15 -2
- package/dist/esm/modules/excel/utils/col-cache.js +15 -0
- package/dist/esm/modules/excel/utils/drawing-utils.js +4 -0
- package/dist/esm/modules/excel/utils/external-link-formula.js +208 -0
- package/dist/esm/modules/excel/utils/iterate-stream.js +3 -1
- package/dist/esm/modules/excel/utils/ooxml-paths.js +37 -2
- package/dist/esm/modules/excel/utils/shared-strings.js +21 -2
- package/dist/esm/modules/excel/utils/workbook-protection.js +30 -0
- package/dist/esm/modules/excel/workbook.browser.js +318 -34
- package/dist/esm/modules/excel/worksheet.js +21 -2
- package/dist/esm/modules/excel/xlsx/rel-type.js +16 -1
- package/dist/esm/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
- package/dist/esm/modules/excel/xlsx/xform/book/external-link-xform.js +330 -0
- package/dist/esm/modules/excel/xlsx/xform/book/external-reference-xform.js +24 -0
- package/dist/esm/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
- package/dist/esm/modules/excel/xlsx/xform/book/workbook-protection-xform.js +66 -0
- package/dist/esm/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
- package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +19 -1
- package/dist/esm/modules/excel/xlsx/xform/core/metadata-xform.js +158 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +105 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
- package/dist/esm/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
- package/dist/esm/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
- package/dist/esm/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
- package/dist/esm/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +80 -0
- package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
- package/dist/esm/modules/excel/xlsx/xform/style/border-xform.js +4 -1
- package/dist/esm/modules/excel/xlsx/xlsx.browser.js +410 -20
- package/dist/esm/modules/excel/xlsx/xlsx.js +4 -5
- package/dist/esm/modules/formula/compile/address-utils.js +83 -0
- package/dist/esm/modules/formula/compile/binder.js +487 -0
- package/dist/esm/modules/formula/compile/bound-ast.js +80 -0
- package/dist/esm/modules/formula/compile/compiled-formula.js +383 -0
- package/dist/esm/modules/formula/compile/dependency-analysis.js +432 -0
- package/dist/esm/modules/formula/compile/structured-ref-utils.js +136 -0
- package/dist/esm/modules/formula/default-syntax-probe.js +83 -0
- package/dist/esm/modules/formula/functions/_date-context.js +29 -0
- package/dist/esm/modules/formula/functions/_shared.js +381 -0
- package/dist/esm/modules/formula/functions/conditional.js +343 -0
- package/dist/esm/modules/formula/functions/database.js +274 -0
- package/dist/esm/modules/formula/functions/date.js +855 -0
- package/dist/esm/modules/formula/functions/dynamic-array.js +860 -0
- package/dist/esm/modules/formula/functions/engineering.js +1128 -0
- package/dist/esm/modules/formula/functions/financial.js +2296 -0
- package/dist/esm/modules/formula/functions/lookup.js +886 -0
- package/dist/esm/modules/formula/functions/math.js +1406 -0
- package/dist/esm/modules/formula/functions/statistical.js +3390 -0
- package/dist/esm/modules/formula/functions/text.js +1845 -0
- package/dist/esm/modules/formula/host-registry.js +69 -0
- package/dist/esm/modules/formula/index.js +49 -0
- package/dist/esm/modules/formula/install.js +88 -0
- package/dist/esm/modules/formula/integration/apply-writeback-plan.js +210 -0
- package/dist/esm/modules/formula/integration/calculate-formulas-impl.js +616 -0
- package/dist/esm/modules/formula/integration/calculate-formulas.js +68 -0
- package/dist/esm/modules/formula/integration/formula-instance.js +79 -0
- package/dist/esm/modules/formula/integration/workbook-adapter.js +324 -0
- package/dist/esm/modules/formula/integration/workbook-snapshot.js +77 -0
- package/dist/esm/modules/formula/materialize/build-writeback-plan.js +473 -0
- package/dist/esm/modules/formula/materialize/spill-engine.js +38 -0
- package/dist/esm/modules/formula/materialize/types.js +29 -0
- package/dist/esm/modules/formula/materialize/writeback-plan.js +27 -0
- package/dist/esm/modules/formula/runtime/evaluator.js +2291 -0
- package/dist/esm/modules/formula/runtime/function-registry.js +840 -0
- package/dist/esm/modules/formula/runtime/values.js +385 -0
- package/dist/esm/modules/formula/syntax/ast.js +28 -0
- package/dist/esm/modules/formula/syntax/parser.js +439 -0
- package/dist/esm/modules/formula/syntax/token-types.js +59 -0
- package/dist/esm/modules/formula/syntax/tokenizer.js +1074 -0
- package/dist/esm/modules/pdf/excel-bridge.js +9 -0
- package/dist/iife/excelts.iife.js +2302 -373
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +34 -34
- package/dist/types/index.browser.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/modules/excel/cell.d.ts +17 -3
- package/dist/types/modules/excel/defined-names.d.ts +96 -1
- package/dist/types/modules/excel/image.d.ts +11 -0
- package/dist/types/modules/excel/stream/workbook-reader.browser.d.ts +9 -3
- package/dist/types/modules/excel/stream/workbook-reader.d.ts +2 -1
- package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +39 -5
- package/dist/types/modules/excel/stream/workbook-writer.d.ts +3 -2
- package/dist/types/modules/excel/stream/worksheet-writer.d.ts +39 -6
- package/dist/types/modules/excel/types.d.ts +133 -2
- package/dist/types/modules/excel/utils/col-cache.d.ts +1 -0
- package/dist/types/modules/excel/utils/drawing-utils.d.ts +3 -3
- package/dist/types/modules/excel/utils/external-link-formula.d.ts +76 -0
- package/dist/types/modules/excel/utils/iterate-stream.d.ts +9 -3
- package/dist/types/modules/excel/utils/ooxml-paths.d.ts +19 -0
- package/dist/types/modules/excel/utils/shared-strings.d.ts +8 -3
- package/dist/types/modules/excel/utils/workbook-protection.d.ts +30 -0
- package/dist/types/modules/excel/workbook.browser.d.ts +257 -6
- package/dist/types/modules/excel/workbook.d.ts +1 -1
- package/dist/types/modules/excel/worksheet.d.ts +3 -1
- package/dist/types/modules/excel/xlsx/rel-type.d.ts +15 -0
- package/dist/types/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +6 -5
- package/dist/types/modules/excel/xlsx/xform/book/external-link-xform.d.ts +84 -0
- package/dist/types/modules/excel/xlsx/xform/book/external-reference-xform.d.ts +17 -0
- package/dist/types/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.d.ts +3 -0
- package/dist/types/modules/excel/xlsx/xform/book/workbook-protection-xform.d.ts +20 -0
- package/dist/types/modules/excel/xlsx/xform/core/metadata-xform.d.ts +56 -0
- package/dist/types/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.d.ts +26 -0
- package/dist/types/modules/excel/xlsx/xform/sheet/cell-xform.d.ts +1 -1
- package/dist/types/modules/excel/xlsx/xform/sheet/ignored-errors-xform.d.ts +21 -0
- package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +172 -13
- package/dist/types/modules/excel/xlsx/xlsx.d.ts +7 -4
- package/dist/types/modules/formula/compile/address-utils.d.ts +62 -0
- package/dist/types/modules/formula/compile/binder.d.ts +42 -0
- package/dist/types/modules/formula/compile/bound-ast.d.ts +230 -0
- package/dist/types/modules/formula/compile/compiled-formula.d.ts +137 -0
- package/dist/types/modules/formula/compile/dependency-analysis.d.ts +93 -0
- package/dist/types/modules/formula/compile/structured-ref-utils.d.ts +93 -0
- package/dist/types/modules/formula/default-syntax-probe.d.ts +79 -0
- package/dist/types/modules/formula/functions/_date-context.d.ts +4 -0
- package/dist/types/modules/formula/functions/_shared.d.ts +121 -0
- package/dist/types/modules/formula/functions/conditional.d.ts +27 -0
- package/dist/types/modules/formula/functions/database.d.ts +37 -0
- package/dist/types/modules/formula/functions/date.d.ts +61 -0
- package/dist/types/modules/formula/functions/dynamic-array.d.ts +23 -0
- package/dist/types/modules/formula/functions/engineering.d.ts +57 -0
- package/dist/types/modules/formula/functions/financial.d.ts +202 -0
- package/dist/types/modules/formula/functions/lookup.d.ts +18 -0
- package/dist/types/modules/formula/functions/math.d.ts +114 -0
- package/dist/types/modules/formula/functions/statistical.d.ts +193 -0
- package/dist/types/modules/formula/functions/text.d.ts +86 -0
- package/dist/types/modules/formula/host-registry.d.ts +53 -0
- package/dist/types/modules/formula/index.d.ts +39 -0
- package/dist/types/modules/formula/install.d.ts +62 -0
- package/dist/types/modules/formula/integration/apply-writeback-plan.d.ts +26 -0
- package/dist/types/modules/formula/integration/calculate-formulas-impl.d.ts +30 -0
- package/dist/types/modules/formula/integration/calculate-formulas.d.ts +67 -0
- package/dist/types/modules/formula/integration/formula-instance.d.ts +64 -0
- package/dist/types/modules/formula/integration/workbook-adapter.d.ts +26 -0
- package/dist/types/modules/formula/integration/workbook-snapshot.d.ts +267 -0
- package/dist/types/modules/formula/materialize/build-writeback-plan.d.ts +34 -0
- package/dist/types/modules/formula/materialize/spill-engine.d.ts +9 -0
- package/dist/types/modules/formula/materialize/types.d.ts +179 -0
- package/dist/types/modules/formula/materialize/writeback-plan.d.ts +167 -0
- package/dist/types/modules/formula/runtime/evaluator.d.ts +151 -0
- package/dist/types/modules/formula/runtime/function-registry.d.ts +47 -0
- package/dist/types/modules/formula/runtime/values.d.ts +211 -0
- package/dist/types/modules/formula/syntax/ast.d.ts +129 -0
- package/dist/types/modules/formula/syntax/parser.d.ts +18 -0
- package/dist/types/modules/formula/syntax/token-types.d.ts +153 -0
- package/dist/types/modules/formula/syntax/tokenizer.d.ts +10 -0
- package/package.json +28 -28
|
@@ -0,0 +1,3390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Statistical Functions
|
|
3
|
+
*
|
|
4
|
+
* Native RuntimeValue implementations.
|
|
5
|
+
*/
|
|
6
|
+
import { RVKind, ERRORS, rvNumber, rvArray, toNumberRV, toBooleanRV, topLeft, isError } from "../runtime/values.js";
|
|
7
|
+
import { argToNumber, flattenAll, flattenNumbers, firstError } from "./_shared.js";
|
|
8
|
+
/**
|
|
9
|
+
* Extract a boolean from a single arg. Returns the boolean or ErrorValue.
|
|
10
|
+
*/
|
|
11
|
+
function boolArg(args, idx) {
|
|
12
|
+
const rv = toBooleanRV(topLeft(args[idx]));
|
|
13
|
+
if (rv.kind === RVKind.Error) {
|
|
14
|
+
return { ok: false, error: rv };
|
|
15
|
+
}
|
|
16
|
+
return { ok: true, value: rv.value };
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check if an arg is an array.
|
|
20
|
+
*/
|
|
21
|
+
function isArrayArg(arg) {
|
|
22
|
+
return arg.kind === RVKind.Array;
|
|
23
|
+
}
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// MEDIAN, LARGE, SMALL, RANK
|
|
26
|
+
// ============================================================================
|
|
27
|
+
/**
|
|
28
|
+
* Quickselect: returns the k-th smallest element (0-indexed) of `arr`
|
|
29
|
+
* in-place, in expected O(n) time.
|
|
30
|
+
*
|
|
31
|
+
* Uses Hoare partitioning with a "median of three" pivot choice for
|
|
32
|
+
* resilience against already-sorted and adversarial inputs. The input
|
|
33
|
+
* array is reorganised around the pivot — callers that need the original
|
|
34
|
+
* order must pass a copy.
|
|
35
|
+
*/
|
|
36
|
+
function quickselect(arr, k) {
|
|
37
|
+
let lo = 0;
|
|
38
|
+
let hi = arr.length - 1;
|
|
39
|
+
while (lo < hi) {
|
|
40
|
+
// median-of-three pivot
|
|
41
|
+
const mid = (lo + hi) >> 1;
|
|
42
|
+
const a = arr[lo];
|
|
43
|
+
const b = arr[mid];
|
|
44
|
+
const c = arr[hi];
|
|
45
|
+
const pivot = a < b ? (b < c ? b : a < c ? c : a) : a < c ? a : b < c ? c : b;
|
|
46
|
+
let i = lo;
|
|
47
|
+
let j = hi;
|
|
48
|
+
while (i <= j) {
|
|
49
|
+
while (arr[i] < pivot) {
|
|
50
|
+
i++;
|
|
51
|
+
}
|
|
52
|
+
while (arr[j] > pivot) {
|
|
53
|
+
j--;
|
|
54
|
+
}
|
|
55
|
+
if (i <= j) {
|
|
56
|
+
const t = arr[i];
|
|
57
|
+
arr[i] = arr[j];
|
|
58
|
+
arr[j] = t;
|
|
59
|
+
i++;
|
|
60
|
+
j--;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (k <= j) {
|
|
64
|
+
hi = j;
|
|
65
|
+
}
|
|
66
|
+
else if (k >= i) {
|
|
67
|
+
lo = i;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
// pivot settled at k
|
|
71
|
+
return arr[k];
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return arr[k];
|
|
75
|
+
}
|
|
76
|
+
export const fnMEDIAN = args => {
|
|
77
|
+
const nums = flattenNumbers(args);
|
|
78
|
+
const err = firstError(nums);
|
|
79
|
+
if (err) {
|
|
80
|
+
return err;
|
|
81
|
+
}
|
|
82
|
+
if (nums.length === 0) {
|
|
83
|
+
return ERRORS.NUM;
|
|
84
|
+
}
|
|
85
|
+
const values = nums.map(n => n.value);
|
|
86
|
+
const n = values.length;
|
|
87
|
+
const mid = n >> 1;
|
|
88
|
+
if (n % 2 !== 0) {
|
|
89
|
+
return rvNumber(quickselect(values, mid));
|
|
90
|
+
}
|
|
91
|
+
// Even count: average of (n/2 − 1)-th and (n/2)-th smallest. Use
|
|
92
|
+
// quickselect twice — but the second search can be limited to the upper
|
|
93
|
+
// half produced by the first call, since quickselect leaves that region
|
|
94
|
+
// sorted w.r.t. the pivot.
|
|
95
|
+
const hi = quickselect(values, mid);
|
|
96
|
+
// After selecting index `mid`, every element at position < mid is ≤ hi.
|
|
97
|
+
// The lower of the two middle values is the max of that prefix.
|
|
98
|
+
let lo = Number.NEGATIVE_INFINITY;
|
|
99
|
+
for (let i = 0; i < mid; i++) {
|
|
100
|
+
if (values[i] > lo) {
|
|
101
|
+
lo = values[i];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return rvNumber((lo + hi) / 2);
|
|
105
|
+
};
|
|
106
|
+
export const fnLARGE = args => {
|
|
107
|
+
const nums = flattenNumbers([args[0]]);
|
|
108
|
+
const err = firstError(nums);
|
|
109
|
+
if (err) {
|
|
110
|
+
return err;
|
|
111
|
+
}
|
|
112
|
+
const values = nums.map(n => n.value);
|
|
113
|
+
// k can be an array (Excel broadcasts); when it is, return an array
|
|
114
|
+
// with the same shape where each cell holds LARGE at that k.
|
|
115
|
+
if (args[1].kind === RVKind.Array) {
|
|
116
|
+
const kArr = args[1];
|
|
117
|
+
const outRows = [];
|
|
118
|
+
for (const row of kArr.rows) {
|
|
119
|
+
const outRow = [];
|
|
120
|
+
for (const cell of row) {
|
|
121
|
+
if (cell.kind === RVKind.Error) {
|
|
122
|
+
outRow.push(cell);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const kn = toNumberRV(cell);
|
|
126
|
+
if (kn.kind === RVKind.Error) {
|
|
127
|
+
outRow.push(kn);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
const kInt = Math.floor(kn.value);
|
|
131
|
+
if (kInt < 1 || kInt > values.length) {
|
|
132
|
+
outRow.push(ERRORS.NUM);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
// k-th largest == (n − k)-th smallest. Use a copy — quickselect
|
|
136
|
+
// mutates the array it works on, and we need a fresh view for
|
|
137
|
+
// every cell in the output.
|
|
138
|
+
outRow.push(rvNumber(quickselect(values.slice(), values.length - kInt)));
|
|
139
|
+
}
|
|
140
|
+
outRows.push(outRow);
|
|
141
|
+
}
|
|
142
|
+
return rvArray(outRows);
|
|
143
|
+
}
|
|
144
|
+
const k = argToNumber(args[1]);
|
|
145
|
+
if (k.kind === RVKind.Error) {
|
|
146
|
+
return k;
|
|
147
|
+
}
|
|
148
|
+
const kInt = Math.floor(k.value);
|
|
149
|
+
if (kInt < 1 || kInt > values.length) {
|
|
150
|
+
return ERRORS.NUM;
|
|
151
|
+
}
|
|
152
|
+
return rvNumber(quickselect(values, values.length - kInt));
|
|
153
|
+
};
|
|
154
|
+
export const fnSMALL = args => {
|
|
155
|
+
const nums = flattenNumbers([args[0]]);
|
|
156
|
+
const err = firstError(nums);
|
|
157
|
+
if (err) {
|
|
158
|
+
return err;
|
|
159
|
+
}
|
|
160
|
+
const values = nums.map(n => n.value);
|
|
161
|
+
if (args[1].kind === RVKind.Array) {
|
|
162
|
+
const kArr = args[1];
|
|
163
|
+
const outRows = [];
|
|
164
|
+
for (const row of kArr.rows) {
|
|
165
|
+
const outRow = [];
|
|
166
|
+
for (const cell of row) {
|
|
167
|
+
if (cell.kind === RVKind.Error) {
|
|
168
|
+
outRow.push(cell);
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
const kn = toNumberRV(cell);
|
|
172
|
+
if (kn.kind === RVKind.Error) {
|
|
173
|
+
outRow.push(kn);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const kInt = Math.floor(kn.value);
|
|
177
|
+
if (kInt < 1 || kInt > values.length) {
|
|
178
|
+
outRow.push(ERRORS.NUM);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
outRow.push(rvNumber(quickselect(values.slice(), kInt - 1)));
|
|
182
|
+
}
|
|
183
|
+
outRows.push(outRow);
|
|
184
|
+
}
|
|
185
|
+
return rvArray(outRows);
|
|
186
|
+
}
|
|
187
|
+
const k = argToNumber(args[1]);
|
|
188
|
+
if (k.kind === RVKind.Error) {
|
|
189
|
+
return k;
|
|
190
|
+
}
|
|
191
|
+
const kInt = Math.floor(k.value);
|
|
192
|
+
if (kInt < 1 || kInt > values.length) {
|
|
193
|
+
return ERRORS.NUM;
|
|
194
|
+
}
|
|
195
|
+
return rvNumber(quickselect(values, kInt - 1));
|
|
196
|
+
};
|
|
197
|
+
export const fnRANK = args => {
|
|
198
|
+
const num = argToNumber(args[0]);
|
|
199
|
+
if (num.kind === RVKind.Error) {
|
|
200
|
+
return num;
|
|
201
|
+
}
|
|
202
|
+
const nums = flattenNumbers([args[1]]);
|
|
203
|
+
const err = firstError(nums);
|
|
204
|
+
if (err) {
|
|
205
|
+
return err;
|
|
206
|
+
}
|
|
207
|
+
const orderRV = args.length > 2 ? argToNumber(args[2]) : rvNumber(0);
|
|
208
|
+
if (orderRV.kind === RVKind.Error) {
|
|
209
|
+
return orderRV;
|
|
210
|
+
}
|
|
211
|
+
const order = orderRV.value;
|
|
212
|
+
const sorted = order === 0
|
|
213
|
+
? nums
|
|
214
|
+
.map(n => n.value)
|
|
215
|
+
.slice()
|
|
216
|
+
.sort((a, b) => b - a)
|
|
217
|
+
: nums
|
|
218
|
+
.map(n => n.value)
|
|
219
|
+
.slice()
|
|
220
|
+
.sort((a, b) => a - b);
|
|
221
|
+
const idx = sorted.indexOf(num.value);
|
|
222
|
+
return idx === -1 ? ERRORS.NA : rvNumber(idx + 1);
|
|
223
|
+
};
|
|
224
|
+
// ============================================================================
|
|
225
|
+
// STDEV, STDEVP, VAR, VARP
|
|
226
|
+
// ============================================================================
|
|
227
|
+
/**
|
|
228
|
+
* Compute mean and sum of squared deviations from mean. Used by the
|
|
229
|
+
* STDEV/STDEVP/VAR/VARP family to share a single pass through the data.
|
|
230
|
+
* Returns `null` when there is no data at all (callers decide whether that
|
|
231
|
+
* should be `#DIV/0!` or zero given the sample/population convention).
|
|
232
|
+
*/
|
|
233
|
+
function computeMeanAndSumSq(nums) {
|
|
234
|
+
const n = nums.length;
|
|
235
|
+
if (n === 0) {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
let sum = 0;
|
|
239
|
+
for (const v of nums) {
|
|
240
|
+
sum += v;
|
|
241
|
+
}
|
|
242
|
+
const mean = sum / n;
|
|
243
|
+
let sumSq = 0;
|
|
244
|
+
for (const v of nums) {
|
|
245
|
+
sumSq += (v - mean) ** 2;
|
|
246
|
+
}
|
|
247
|
+
return { n, mean, sumSq };
|
|
248
|
+
}
|
|
249
|
+
/** Resolve {args} to `number[]` or an error. Shared by STDEV/VAR family. */
|
|
250
|
+
function toNumberArray(args) {
|
|
251
|
+
const rawNums = flattenNumbers(args);
|
|
252
|
+
const err = firstError(rawNums);
|
|
253
|
+
if (err) {
|
|
254
|
+
return err;
|
|
255
|
+
}
|
|
256
|
+
return rawNums.map(n => n.value);
|
|
257
|
+
}
|
|
258
|
+
export const fnSTDEV = args => {
|
|
259
|
+
const nums = toNumberArray(args);
|
|
260
|
+
if (!Array.isArray(nums)) {
|
|
261
|
+
return nums;
|
|
262
|
+
}
|
|
263
|
+
const stats = computeMeanAndSumSq(nums);
|
|
264
|
+
if (!stats || stats.n < 2) {
|
|
265
|
+
return ERRORS.DIV0;
|
|
266
|
+
}
|
|
267
|
+
return rvNumber(Math.sqrt(stats.sumSq / (stats.n - 1)));
|
|
268
|
+
};
|
|
269
|
+
export const fnSTDEVP = args => {
|
|
270
|
+
const nums = toNumberArray(args);
|
|
271
|
+
if (!Array.isArray(nums)) {
|
|
272
|
+
return nums;
|
|
273
|
+
}
|
|
274
|
+
const stats = computeMeanAndSumSq(nums);
|
|
275
|
+
if (!stats) {
|
|
276
|
+
return ERRORS.DIV0;
|
|
277
|
+
}
|
|
278
|
+
return rvNumber(Math.sqrt(stats.sumSq / stats.n));
|
|
279
|
+
};
|
|
280
|
+
export const fnVAR = args => {
|
|
281
|
+
const nums = toNumberArray(args);
|
|
282
|
+
if (!Array.isArray(nums)) {
|
|
283
|
+
return nums;
|
|
284
|
+
}
|
|
285
|
+
const stats = computeMeanAndSumSq(nums);
|
|
286
|
+
if (!stats || stats.n < 2) {
|
|
287
|
+
return ERRORS.DIV0;
|
|
288
|
+
}
|
|
289
|
+
return rvNumber(stats.sumSq / (stats.n - 1));
|
|
290
|
+
};
|
|
291
|
+
export const fnVARP = args => {
|
|
292
|
+
const nums = toNumberArray(args);
|
|
293
|
+
if (!Array.isArray(nums)) {
|
|
294
|
+
return nums;
|
|
295
|
+
}
|
|
296
|
+
const stats = computeMeanAndSumSq(nums);
|
|
297
|
+
if (!stats) {
|
|
298
|
+
return ERRORS.DIV0;
|
|
299
|
+
}
|
|
300
|
+
return rvNumber(stats.sumSq / stats.n);
|
|
301
|
+
};
|
|
302
|
+
// ============================================================================
|
|
303
|
+
// Advanced Statistical Functions — private math helpers
|
|
304
|
+
// ============================================================================
|
|
305
|
+
// Peter Acklam's rational approximation for the standard normal inverse CDF.
|
|
306
|
+
// Accuracy: |error| < 1.15e-9 across the full range (0, 1).
|
|
307
|
+
function normSInv(p) {
|
|
308
|
+
if (p <= 0 || p >= 1) {
|
|
309
|
+
return NaN;
|
|
310
|
+
}
|
|
311
|
+
if (p < 0.5) {
|
|
312
|
+
return -normSInv(1 - p);
|
|
313
|
+
}
|
|
314
|
+
// Coefficients for rational approximation
|
|
315
|
+
const a1 = -3.969683028665376e1;
|
|
316
|
+
const a2 = 2.209460984245205e2;
|
|
317
|
+
const a3 = -2.759285104469687e2;
|
|
318
|
+
const a4 = 1.38357751867269e2;
|
|
319
|
+
const a5 = -3.066479806614716e1;
|
|
320
|
+
const a6 = 2.506628277459239;
|
|
321
|
+
const b1 = -5.447609879822406e1;
|
|
322
|
+
const b2 = 1.615858368580409e2;
|
|
323
|
+
const b3 = -1.556989798598866e2;
|
|
324
|
+
const b4 = 6.680131188771972e1;
|
|
325
|
+
const b5 = -1.328068155288572e1;
|
|
326
|
+
const c1 = -7.784894002430293e-3;
|
|
327
|
+
const c2 = -3.223964580411365e-1;
|
|
328
|
+
const c3 = -2.400758277161838;
|
|
329
|
+
const c4 = -2.549732539343734;
|
|
330
|
+
const c5 = 4.374664141464968;
|
|
331
|
+
const c6 = 2.938163982698783;
|
|
332
|
+
const d1 = 7.784695709041462e-3;
|
|
333
|
+
const d2 = 3.224671290700398e-1;
|
|
334
|
+
const d3 = 2.445134137142996;
|
|
335
|
+
const d4 = 3.754408661907416;
|
|
336
|
+
const pLow = 0.02425;
|
|
337
|
+
const pHigh = 1 - pLow;
|
|
338
|
+
if (p < pLow) {
|
|
339
|
+
// Rational approximation for lower region
|
|
340
|
+
const q = Math.sqrt(-2 * Math.log(p));
|
|
341
|
+
return ((((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) /
|
|
342
|
+
((((d1 * q + d2) * q + d3) * q + d4) * q + 1));
|
|
343
|
+
}
|
|
344
|
+
if (p <= pHigh) {
|
|
345
|
+
// Rational approximation for central region
|
|
346
|
+
const q = p - 0.5;
|
|
347
|
+
const r = q * q;
|
|
348
|
+
return (((((((a1 * r + a2) * r + a3) * r + a4) * r + a5) * r + a6) * q) /
|
|
349
|
+
(((((b1 * r + b2) * r + b3) * r + b4) * r + b5) * r + 1));
|
|
350
|
+
}
|
|
351
|
+
// Upper region — use symmetry
|
|
352
|
+
const q = Math.sqrt(-2 * Math.log(1 - p));
|
|
353
|
+
return -((((((c1 * q + c2) * q + c3) * q + c4) * q + c5) * q + c6) /
|
|
354
|
+
((((d1 * q + d2) * q + d3) * q + d4) * q + 1));
|
|
355
|
+
}
|
|
356
|
+
// Standard normal CDF approximation (Abramowitz & Stegun 7.1.26). The
|
|
357
|
+
// approximation has max error ~7.5e-8 — good enough for GAUSS/NORM.S.DIST
|
|
358
|
+
// but it does NOT evaluate to exactly 0.5 at x = 0 (the erf kernel leaves
|
|
359
|
+
// a residual ≈ 5e-10). Excel-facing callers expect symmetry around 0, so
|
|
360
|
+
// we short-circuit that single point.
|
|
361
|
+
function normSDist(x) {
|
|
362
|
+
if (x === 0) {
|
|
363
|
+
return 0.5;
|
|
364
|
+
}
|
|
365
|
+
const a1 = 0.254829592;
|
|
366
|
+
const a2 = -0.284496736;
|
|
367
|
+
const a3 = 1.421413741;
|
|
368
|
+
const a4 = -1.453152027;
|
|
369
|
+
const a5 = 1.061405429;
|
|
370
|
+
const p = 0.3275911;
|
|
371
|
+
const sign = x < 0 ? -1 : 1;
|
|
372
|
+
x = Math.abs(x) / Math.SQRT2;
|
|
373
|
+
const t = 1.0 / (1.0 + p * x);
|
|
374
|
+
const y = 1.0 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-x * x);
|
|
375
|
+
return 0.5 * (1.0 + sign * y);
|
|
376
|
+
}
|
|
377
|
+
// Standard normal PDF
|
|
378
|
+
function normSPdf(x) {
|
|
379
|
+
return Math.exp(-0.5 * x * x) / Math.sqrt(2 * Math.PI);
|
|
380
|
+
}
|
|
381
|
+
// ============================================================================
|
|
382
|
+
// Normal Distribution Functions
|
|
383
|
+
// ============================================================================
|
|
384
|
+
export const fnNORMSDIST = args => {
|
|
385
|
+
const z = argToNumber(args[0]);
|
|
386
|
+
if (z.kind === RVKind.Error) {
|
|
387
|
+
return z;
|
|
388
|
+
}
|
|
389
|
+
// Legacy NORM.S.DIST compatibility: single arg = CDF
|
|
390
|
+
if (args.length > 1) {
|
|
391
|
+
const cum = boolArg(args, 1);
|
|
392
|
+
if (!cum.ok) {
|
|
393
|
+
return cum.error;
|
|
394
|
+
}
|
|
395
|
+
return rvNumber(cum.value ? normSDist(z.value) : normSPdf(z.value));
|
|
396
|
+
}
|
|
397
|
+
return rvNumber(normSDist(z.value));
|
|
398
|
+
};
|
|
399
|
+
export const fnNORMDIST = args => {
|
|
400
|
+
const x = argToNumber(args[0]);
|
|
401
|
+
if (x.kind === RVKind.Error) {
|
|
402
|
+
return x;
|
|
403
|
+
}
|
|
404
|
+
const mean = argToNumber(args[1]);
|
|
405
|
+
if (mean.kind === RVKind.Error) {
|
|
406
|
+
return mean;
|
|
407
|
+
}
|
|
408
|
+
const stddev = argToNumber(args[2]);
|
|
409
|
+
if (stddev.kind === RVKind.Error) {
|
|
410
|
+
return stddev;
|
|
411
|
+
}
|
|
412
|
+
if (stddev.value <= 0) {
|
|
413
|
+
return ERRORS.NUM;
|
|
414
|
+
}
|
|
415
|
+
const cum = boolArg(args, 3);
|
|
416
|
+
if (!cum.ok) {
|
|
417
|
+
return cum.error;
|
|
418
|
+
}
|
|
419
|
+
const zVal = (x.value - mean.value) / stddev.value;
|
|
420
|
+
if (cum.value) {
|
|
421
|
+
return rvNumber(normSDist(zVal));
|
|
422
|
+
}
|
|
423
|
+
return rvNumber(normSPdf(zVal) / stddev.value);
|
|
424
|
+
};
|
|
425
|
+
export const fnNORMSINV = args => {
|
|
426
|
+
const p = argToNumber(args[0]);
|
|
427
|
+
if (p.kind === RVKind.Error) {
|
|
428
|
+
return p;
|
|
429
|
+
}
|
|
430
|
+
if (p.value <= 0 || p.value >= 1) {
|
|
431
|
+
return ERRORS.NUM;
|
|
432
|
+
}
|
|
433
|
+
return rvNumber(normSInv(p.value));
|
|
434
|
+
};
|
|
435
|
+
export const fnNORMINV = args => {
|
|
436
|
+
const p = argToNumber(args[0]);
|
|
437
|
+
if (p.kind === RVKind.Error) {
|
|
438
|
+
return p;
|
|
439
|
+
}
|
|
440
|
+
const mean = argToNumber(args[1]);
|
|
441
|
+
if (mean.kind === RVKind.Error) {
|
|
442
|
+
return mean;
|
|
443
|
+
}
|
|
444
|
+
const stddev = argToNumber(args[2]);
|
|
445
|
+
if (stddev.kind === RVKind.Error) {
|
|
446
|
+
return stddev;
|
|
447
|
+
}
|
|
448
|
+
if (p.value <= 0 || p.value >= 1 || stddev.value <= 0) {
|
|
449
|
+
return ERRORS.NUM;
|
|
450
|
+
}
|
|
451
|
+
return rvNumber(mean.value + stddev.value * normSInv(p.value));
|
|
452
|
+
};
|
|
453
|
+
// ============================================================================
|
|
454
|
+
// PERCENTILE, QUARTILE, MODE
|
|
455
|
+
// ============================================================================
|
|
456
|
+
export const fnPERCENTILE = args => {
|
|
457
|
+
const nums = flattenNumbers([args[0]])
|
|
458
|
+
.filter((v) => v.kind === RVKind.Number)
|
|
459
|
+
.map(n => n.value);
|
|
460
|
+
const k = argToNumber(args[1]);
|
|
461
|
+
if (k.kind === RVKind.Error) {
|
|
462
|
+
return k;
|
|
463
|
+
}
|
|
464
|
+
if (k.value < 0 || k.value > 1 || nums.length === 0) {
|
|
465
|
+
return ERRORS.NUM;
|
|
466
|
+
}
|
|
467
|
+
nums.sort((a, b) => a - b);
|
|
468
|
+
const n = nums.length;
|
|
469
|
+
if (n === 1) {
|
|
470
|
+
return rvNumber(nums[0]);
|
|
471
|
+
}
|
|
472
|
+
const rank = k.value * (n - 1);
|
|
473
|
+
const lower = Math.floor(rank);
|
|
474
|
+
const upper = Math.ceil(rank);
|
|
475
|
+
const frac = rank - lower;
|
|
476
|
+
return rvNumber(nums[lower] + frac * (nums[upper] - nums[lower]));
|
|
477
|
+
};
|
|
478
|
+
export const fnPERCENTILEEXC = args => {
|
|
479
|
+
const nums = flattenNumbers([args[0]])
|
|
480
|
+
.filter((v) => v.kind === RVKind.Number)
|
|
481
|
+
.map(n => n.value);
|
|
482
|
+
const k = argToNumber(args[1]);
|
|
483
|
+
if (k.kind === RVKind.Error) {
|
|
484
|
+
return k;
|
|
485
|
+
}
|
|
486
|
+
const n = nums.length;
|
|
487
|
+
if (k.value <= 0 || k.value >= 1 || n === 0) {
|
|
488
|
+
return ERRORS.NUM;
|
|
489
|
+
}
|
|
490
|
+
if (k.value < 1 / (n + 1) || k.value > n / (n + 1)) {
|
|
491
|
+
return ERRORS.NUM;
|
|
492
|
+
}
|
|
493
|
+
nums.sort((a, b) => a - b);
|
|
494
|
+
const rank = k.value * (n + 1) - 1;
|
|
495
|
+
const lower = Math.floor(rank);
|
|
496
|
+
const upper = Math.ceil(rank);
|
|
497
|
+
const frac = rank - lower;
|
|
498
|
+
return rvNumber(nums[Math.max(0, lower)] + frac * (nums[Math.min(n - 1, upper)] - nums[Math.max(0, lower)]));
|
|
499
|
+
};
|
|
500
|
+
export const fnQUARTILE = args => {
|
|
501
|
+
const quart = argToNumber(args[1]);
|
|
502
|
+
if (quart.kind === RVKind.Error) {
|
|
503
|
+
return quart;
|
|
504
|
+
}
|
|
505
|
+
if (quart.value < 0 || quart.value > 4) {
|
|
506
|
+
return ERRORS.NUM;
|
|
507
|
+
}
|
|
508
|
+
return fnPERCENTILE([args[0], rvNumber(quart.value / 4)]);
|
|
509
|
+
};
|
|
510
|
+
export const fnQUARTILEEXC = args => {
|
|
511
|
+
const quart = argToNumber(args[1]);
|
|
512
|
+
if (quart.kind === RVKind.Error) {
|
|
513
|
+
return quart;
|
|
514
|
+
}
|
|
515
|
+
if (quart.value < 1 || quart.value > 3) {
|
|
516
|
+
return ERRORS.NUM;
|
|
517
|
+
}
|
|
518
|
+
return fnPERCENTILEEXC([args[0], rvNumber(quart.value / 4)]);
|
|
519
|
+
};
|
|
520
|
+
export const fnMODE = args => {
|
|
521
|
+
const all = flattenNumbers(args);
|
|
522
|
+
const err = firstError(all);
|
|
523
|
+
if (err) {
|
|
524
|
+
return err;
|
|
525
|
+
}
|
|
526
|
+
const nums = all.map(n => n.value);
|
|
527
|
+
if (nums.length === 0) {
|
|
528
|
+
return ERRORS.NA;
|
|
529
|
+
}
|
|
530
|
+
const counts = new Map();
|
|
531
|
+
let maxCount = 0;
|
|
532
|
+
let mode = nums[0];
|
|
533
|
+
for (const n of nums) {
|
|
534
|
+
const c = (counts.get(n) ?? 0) + 1;
|
|
535
|
+
counts.set(n, c);
|
|
536
|
+
if (c > maxCount) {
|
|
537
|
+
maxCount = c;
|
|
538
|
+
mode = n;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return maxCount > 1 ? rvNumber(mode) : ERRORS.NA;
|
|
542
|
+
};
|
|
543
|
+
/**
|
|
544
|
+
* MODE.MULT — returns a vertical array of every mode (dynamic array).
|
|
545
|
+
* When the dataset is multimodal Excel spills all of them; for a single
|
|
546
|
+
* mode it behaves like MODE.SNGL.
|
|
547
|
+
*/
|
|
548
|
+
export const fnMODE_MULT = args => {
|
|
549
|
+
const all = flattenNumbers(args);
|
|
550
|
+
const err = firstError(all);
|
|
551
|
+
if (err) {
|
|
552
|
+
return err;
|
|
553
|
+
}
|
|
554
|
+
const nums = all.map(n => n.value);
|
|
555
|
+
if (nums.length === 0) {
|
|
556
|
+
return ERRORS.NA;
|
|
557
|
+
}
|
|
558
|
+
const counts = new Map();
|
|
559
|
+
for (const n of nums) {
|
|
560
|
+
counts.set(n, (counts.get(n) ?? 0) + 1);
|
|
561
|
+
}
|
|
562
|
+
let maxCount = 0;
|
|
563
|
+
for (const c of counts.values()) {
|
|
564
|
+
if (c > maxCount) {
|
|
565
|
+
maxCount = c;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
if (maxCount < 2) {
|
|
569
|
+
return ERRORS.NA;
|
|
570
|
+
}
|
|
571
|
+
// Preserve first-occurrence order as Excel does (not sorted).
|
|
572
|
+
const seen = new Set();
|
|
573
|
+
const modes = [];
|
|
574
|
+
for (const n of nums) {
|
|
575
|
+
if (!seen.has(n) && counts.get(n) === maxCount) {
|
|
576
|
+
seen.add(n);
|
|
577
|
+
modes.push(n);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
return rvArray(modes.map(m => [rvNumber(m)]));
|
|
581
|
+
};
|
|
582
|
+
// ============================================================================
|
|
583
|
+
// Paired-array functions: CORREL, SLOPE, INTERCEPT, RSQ, FORECAST
|
|
584
|
+
// ============================================================================
|
|
585
|
+
/**
|
|
586
|
+
* Extract matching pairs of numbers from two array arguments, filtering to
|
|
587
|
+
* numeric cells only (matching Excel's CORREL/SLOPE/INTERCEPT conventions).
|
|
588
|
+
* Returns the shorter prefix-length pair aligned by position.
|
|
589
|
+
*/
|
|
590
|
+
function pairedNumbers(args, aIdx, bIdx) {
|
|
591
|
+
const flatA = flattenNumbers([args[aIdx]]);
|
|
592
|
+
const errA = firstError(flatA);
|
|
593
|
+
if (errA) {
|
|
594
|
+
return errA;
|
|
595
|
+
}
|
|
596
|
+
const flatB = flattenNumbers([args[bIdx]]);
|
|
597
|
+
const errB = firstError(flatB);
|
|
598
|
+
if (errB) {
|
|
599
|
+
return errB;
|
|
600
|
+
}
|
|
601
|
+
const xs = flatA.filter((v) => v.kind === RVKind.Number).map(n => n.value);
|
|
602
|
+
const ys = flatB.filter((v) => v.kind === RVKind.Number).map(n => n.value);
|
|
603
|
+
const n = Math.min(xs.length, ys.length);
|
|
604
|
+
return { xs: xs.slice(0, n), ys: ys.slice(0, n) };
|
|
605
|
+
}
|
|
606
|
+
function pairedSums(xs, ys) {
|
|
607
|
+
const n = xs.length;
|
|
608
|
+
if (n === 0) {
|
|
609
|
+
return null;
|
|
610
|
+
}
|
|
611
|
+
let sumX = 0;
|
|
612
|
+
let sumY = 0;
|
|
613
|
+
for (let i = 0; i < n; i++) {
|
|
614
|
+
sumX += xs[i];
|
|
615
|
+
sumY += ys[i];
|
|
616
|
+
}
|
|
617
|
+
const meanX = sumX / n;
|
|
618
|
+
const meanY = sumY / n;
|
|
619
|
+
let sxy = 0;
|
|
620
|
+
let sxx = 0;
|
|
621
|
+
let syy = 0;
|
|
622
|
+
for (let i = 0; i < n; i++) {
|
|
623
|
+
const dx = xs[i] - meanX;
|
|
624
|
+
const dy = ys[i] - meanY;
|
|
625
|
+
sxy += dx * dy;
|
|
626
|
+
sxx += dx * dx;
|
|
627
|
+
syy += dy * dy;
|
|
628
|
+
}
|
|
629
|
+
return { n, meanX, meanY, sxy, sxx, syy };
|
|
630
|
+
}
|
|
631
|
+
export const fnCORREL = args => {
|
|
632
|
+
const paired = pairedNumbers(args, 0, 1);
|
|
633
|
+
if ("code" in paired) {
|
|
634
|
+
return paired;
|
|
635
|
+
}
|
|
636
|
+
const { xs, ys } = paired;
|
|
637
|
+
const s = pairedSums(xs, ys);
|
|
638
|
+
if (!s || s.n < 2) {
|
|
639
|
+
return ERRORS.DIV0;
|
|
640
|
+
}
|
|
641
|
+
const denom = Math.sqrt(s.sxx * s.syy);
|
|
642
|
+
return denom === 0 ? ERRORS.DIV0 : rvNumber(s.sxy / denom);
|
|
643
|
+
};
|
|
644
|
+
export const fnSLOPE = args => {
|
|
645
|
+
// SLOPE(known_y, known_x) — note the argument order (y first, x second).
|
|
646
|
+
const paired = pairedNumbers(args, 0, 1);
|
|
647
|
+
if ("code" in paired) {
|
|
648
|
+
return paired;
|
|
649
|
+
}
|
|
650
|
+
const { xs: ys, ys: xs } = paired;
|
|
651
|
+
const s = pairedSums(xs, ys);
|
|
652
|
+
if (!s || s.n < 2) {
|
|
653
|
+
return ERRORS.DIV0;
|
|
654
|
+
}
|
|
655
|
+
return s.sxx === 0 ? ERRORS.DIV0 : rvNumber(s.sxy / s.sxx);
|
|
656
|
+
};
|
|
657
|
+
export const fnINTERCEPT = args => {
|
|
658
|
+
const paired = pairedNumbers(args, 0, 1);
|
|
659
|
+
if ("code" in paired) {
|
|
660
|
+
return paired;
|
|
661
|
+
}
|
|
662
|
+
const { xs: ys, ys: xs } = paired;
|
|
663
|
+
const s = pairedSums(xs, ys);
|
|
664
|
+
if (!s || s.n < 2) {
|
|
665
|
+
return ERRORS.DIV0;
|
|
666
|
+
}
|
|
667
|
+
if (s.sxx === 0) {
|
|
668
|
+
return ERRORS.DIV0;
|
|
669
|
+
}
|
|
670
|
+
const slope = s.sxy / s.sxx;
|
|
671
|
+
return rvNumber(s.meanY - slope * s.meanX);
|
|
672
|
+
};
|
|
673
|
+
export const fnRSQ = args => {
|
|
674
|
+
const r = fnCORREL(args);
|
|
675
|
+
if (isError(r)) {
|
|
676
|
+
return r;
|
|
677
|
+
}
|
|
678
|
+
return rvNumber(r.value ** 2);
|
|
679
|
+
};
|
|
680
|
+
/**
|
|
681
|
+
* STEYX(known_y, known_x) — standard error of the predicted y-value for
|
|
682
|
+
* each x in a regression. Matches Excel's definition:
|
|
683
|
+
* SE = sqrt((1/(n-2)) × (S_yy − S_xy² / S_xx))
|
|
684
|
+
* where S_xx, S_yy, S_xy are centred sums of squares / cross-product.
|
|
685
|
+
*/
|
|
686
|
+
export const fnSTEYX = args => {
|
|
687
|
+
// STEYX(known_y, known_x) — y-first ordering, like SLOPE/INTERCEPT.
|
|
688
|
+
const paired = pairedNumbers(args, 0, 1);
|
|
689
|
+
if ("code" in paired) {
|
|
690
|
+
return paired;
|
|
691
|
+
}
|
|
692
|
+
const { xs: ys, ys: xs } = paired;
|
|
693
|
+
const s = pairedSums(xs, ys);
|
|
694
|
+
if (!s || s.n < 3) {
|
|
695
|
+
return ERRORS.DIV0;
|
|
696
|
+
}
|
|
697
|
+
if (s.sxx === 0) {
|
|
698
|
+
return ERRORS.DIV0;
|
|
699
|
+
}
|
|
700
|
+
const numer = s.syy - (s.sxy * s.sxy) / s.sxx;
|
|
701
|
+
if (numer < 0) {
|
|
702
|
+
// Floating-point noise when the regression is essentially perfect.
|
|
703
|
+
return rvNumber(0);
|
|
704
|
+
}
|
|
705
|
+
return rvNumber(Math.sqrt(numer / (s.n - 2)));
|
|
706
|
+
};
|
|
707
|
+
export const fnFORECAST = args => {
|
|
708
|
+
const x = argToNumber(args[0]);
|
|
709
|
+
if (x.kind === RVKind.Error) {
|
|
710
|
+
return x;
|
|
711
|
+
}
|
|
712
|
+
// FORECAST(x, known_y, known_x) — same y-first argument order as SLOPE.
|
|
713
|
+
const paired = pairedNumbers(args, 1, 2);
|
|
714
|
+
if ("code" in paired) {
|
|
715
|
+
return paired;
|
|
716
|
+
}
|
|
717
|
+
const { xs: ys, ys: xs } = paired;
|
|
718
|
+
const s = pairedSums(xs, ys);
|
|
719
|
+
if (!s || s.n < 2) {
|
|
720
|
+
return ERRORS.DIV0;
|
|
721
|
+
}
|
|
722
|
+
if (s.sxx === 0) {
|
|
723
|
+
return ERRORS.DIV0;
|
|
724
|
+
}
|
|
725
|
+
const slope = s.sxy / s.sxx;
|
|
726
|
+
const intercept = s.meanY - slope * s.meanX;
|
|
727
|
+
return rvNumber(intercept + slope * x.value);
|
|
728
|
+
};
|
|
729
|
+
// ============================================================================
|
|
730
|
+
// FACT, FACTDOUBLE, COMBIN, COMBINA, PERMUT
|
|
731
|
+
// Re-exported from math.ts — canonical definitions live there.
|
|
732
|
+
// ============================================================================
|
|
733
|
+
export { fnFACT, fnFACTDOUBLE, fnCOMBIN, fnCOMBINA, fnPERMUT } from "./math.js";
|
|
734
|
+
// ============================================================================
|
|
735
|
+
// GEOMEAN, HARMEAN, TRIMMEAN, DEVSQ, AVEDEV
|
|
736
|
+
// ============================================================================
|
|
737
|
+
export const fnGEOMEAN = args => {
|
|
738
|
+
const rawNums = flattenNumbers(args);
|
|
739
|
+
const err = firstError(rawNums);
|
|
740
|
+
if (err) {
|
|
741
|
+
return err;
|
|
742
|
+
}
|
|
743
|
+
const nums = rawNums;
|
|
744
|
+
if (nums.length === 0) {
|
|
745
|
+
return ERRORS.NUM;
|
|
746
|
+
}
|
|
747
|
+
let logSum = 0;
|
|
748
|
+
for (const n of nums) {
|
|
749
|
+
if (n.value <= 0) {
|
|
750
|
+
return ERRORS.NUM;
|
|
751
|
+
}
|
|
752
|
+
logSum += Math.log(n.value);
|
|
753
|
+
}
|
|
754
|
+
return rvNumber(Math.exp(logSum / nums.length));
|
|
755
|
+
};
|
|
756
|
+
export const fnHARMEAN = args => {
|
|
757
|
+
const rawNums = flattenNumbers(args);
|
|
758
|
+
const err = firstError(rawNums);
|
|
759
|
+
if (err) {
|
|
760
|
+
return err;
|
|
761
|
+
}
|
|
762
|
+
const nums = rawNums;
|
|
763
|
+
if (nums.length === 0) {
|
|
764
|
+
return ERRORS.NUM;
|
|
765
|
+
}
|
|
766
|
+
let recipSum = 0;
|
|
767
|
+
for (const n of nums) {
|
|
768
|
+
if (n.value <= 0) {
|
|
769
|
+
return ERRORS.NUM;
|
|
770
|
+
}
|
|
771
|
+
recipSum += 1 / n.value;
|
|
772
|
+
}
|
|
773
|
+
return rvNumber(nums.length / recipSum);
|
|
774
|
+
};
|
|
775
|
+
export const fnTRIMMEAN = args => {
|
|
776
|
+
const all = flattenNumbers([args[0]]);
|
|
777
|
+
const err = firstError(all);
|
|
778
|
+
if (err) {
|
|
779
|
+
return err;
|
|
780
|
+
}
|
|
781
|
+
const nums = all.map(n => n.value);
|
|
782
|
+
const pct = argToNumber(args[1]);
|
|
783
|
+
if (pct.kind === RVKind.Error) {
|
|
784
|
+
return pct;
|
|
785
|
+
}
|
|
786
|
+
if (pct.value < 0 || pct.value >= 1) {
|
|
787
|
+
return ERRORS.NUM;
|
|
788
|
+
}
|
|
789
|
+
nums.sort((a, b) => a - b);
|
|
790
|
+
const trimCount = Math.floor((nums.length * pct.value) / 2);
|
|
791
|
+
const trimmed = nums.slice(trimCount, nums.length - trimCount);
|
|
792
|
+
if (trimmed.length === 0) {
|
|
793
|
+
return ERRORS.DIV0;
|
|
794
|
+
}
|
|
795
|
+
return rvNumber(trimmed.reduce((a, b) => a + b, 0) / trimmed.length);
|
|
796
|
+
};
|
|
797
|
+
export const fnDEVSQ = args => {
|
|
798
|
+
const rawNums = flattenNumbers(args);
|
|
799
|
+
const err = firstError(rawNums);
|
|
800
|
+
if (err) {
|
|
801
|
+
return err;
|
|
802
|
+
}
|
|
803
|
+
const nums = rawNums;
|
|
804
|
+
if (nums.length === 0) {
|
|
805
|
+
return rvNumber(0);
|
|
806
|
+
}
|
|
807
|
+
let sum = 0;
|
|
808
|
+
for (const n of nums) {
|
|
809
|
+
sum += n.value;
|
|
810
|
+
}
|
|
811
|
+
const mean = sum / nums.length;
|
|
812
|
+
let result = 0;
|
|
813
|
+
for (const n of nums) {
|
|
814
|
+
result += (n.value - mean) ** 2;
|
|
815
|
+
}
|
|
816
|
+
return rvNumber(result);
|
|
817
|
+
};
|
|
818
|
+
export const fnAVEDEV = args => {
|
|
819
|
+
const rawNums = flattenNumbers(args);
|
|
820
|
+
const err = firstError(rawNums);
|
|
821
|
+
if (err) {
|
|
822
|
+
return err;
|
|
823
|
+
}
|
|
824
|
+
const nums = rawNums;
|
|
825
|
+
if (nums.length === 0) {
|
|
826
|
+
return ERRORS.NUM;
|
|
827
|
+
}
|
|
828
|
+
let sum = 0;
|
|
829
|
+
for (const n of nums) {
|
|
830
|
+
sum += n.value;
|
|
831
|
+
}
|
|
832
|
+
const mean = sum / nums.length;
|
|
833
|
+
let result = 0;
|
|
834
|
+
for (const n of nums) {
|
|
835
|
+
result += Math.abs(n.value - mean);
|
|
836
|
+
}
|
|
837
|
+
return rvNumber(result / nums.length);
|
|
838
|
+
};
|
|
839
|
+
// ============================================================================
|
|
840
|
+
// CONFIDENCE, FISHER, AVERAGEA, MAXA, MINA
|
|
841
|
+
// ============================================================================
|
|
842
|
+
export const fnCONFIDENCENORM = args => {
|
|
843
|
+
const alpha = argToNumber(args[0]);
|
|
844
|
+
if (alpha.kind === RVKind.Error) {
|
|
845
|
+
return alpha;
|
|
846
|
+
}
|
|
847
|
+
const stddev = argToNumber(args[1]);
|
|
848
|
+
if (stddev.kind === RVKind.Error) {
|
|
849
|
+
return stddev;
|
|
850
|
+
}
|
|
851
|
+
const size = argToNumber(args[2]);
|
|
852
|
+
if (size.kind === RVKind.Error) {
|
|
853
|
+
return size;
|
|
854
|
+
}
|
|
855
|
+
if (alpha.value <= 0 || alpha.value >= 1 || stddev.value <= 0 || size.value < 1) {
|
|
856
|
+
return ERRORS.NUM;
|
|
857
|
+
}
|
|
858
|
+
return rvNumber((normSInv(1 - alpha.value / 2) * stddev.value) / Math.sqrt(size.value));
|
|
859
|
+
};
|
|
860
|
+
/**
|
|
861
|
+
* CONFIDENCE.T — confidence interval half-width for the mean using the
|
|
862
|
+
* Student's t distribution (small sample / unknown population variance).
|
|
863
|
+
*/
|
|
864
|
+
export const fnCONFIDENCE_T = args => {
|
|
865
|
+
const alpha = argToNumber(args[0]);
|
|
866
|
+
if (alpha.kind === RVKind.Error) {
|
|
867
|
+
return alpha;
|
|
868
|
+
}
|
|
869
|
+
const stddev = argToNumber(args[1]);
|
|
870
|
+
if (stddev.kind === RVKind.Error) {
|
|
871
|
+
return stddev;
|
|
872
|
+
}
|
|
873
|
+
const size = argToNumber(args[2]);
|
|
874
|
+
if (size.kind === RVKind.Error) {
|
|
875
|
+
return size;
|
|
876
|
+
}
|
|
877
|
+
if (alpha.value <= 0 || alpha.value >= 1 || stddev.value <= 0 || size.value < 2) {
|
|
878
|
+
return ERRORS.NUM;
|
|
879
|
+
}
|
|
880
|
+
// Reuse the engine's existing T.INV.2T to pull the two-tailed critical
|
|
881
|
+
// value; avoids duplicating the Newton search. Excel uses df = n − 1.
|
|
882
|
+
const tCrit = fnT_INV_2T([rvNumber(alpha.value), rvNumber(size.value - 1)]);
|
|
883
|
+
if (isError(tCrit)) {
|
|
884
|
+
return tCrit;
|
|
885
|
+
}
|
|
886
|
+
const t = tCrit.value;
|
|
887
|
+
return rvNumber((t * stddev.value) / Math.sqrt(size.value));
|
|
888
|
+
};
|
|
889
|
+
/**
|
|
890
|
+
* Shared helper: walk two numeric arrays element-wise, filtering to
|
|
891
|
+
* matching-position numeric pairs only (Excel skips rows where either
|
|
892
|
+
* side is non-numeric).
|
|
893
|
+
*/
|
|
894
|
+
function pairedNumericValues(a, b) {
|
|
895
|
+
const xsAll = flattenNumbers([a]);
|
|
896
|
+
const xsErr = firstError(xsAll);
|
|
897
|
+
if (xsErr) {
|
|
898
|
+
return xsErr;
|
|
899
|
+
}
|
|
900
|
+
const ysAll = flattenNumbers([b]);
|
|
901
|
+
const ysErr = firstError(ysAll);
|
|
902
|
+
if (ysErr) {
|
|
903
|
+
return ysErr;
|
|
904
|
+
}
|
|
905
|
+
const n = Math.min(xsAll.length, ysAll.length);
|
|
906
|
+
const xs = [];
|
|
907
|
+
const ys = [];
|
|
908
|
+
for (let i = 0; i < n; i++) {
|
|
909
|
+
const x = xsAll[i];
|
|
910
|
+
const y = ysAll[i];
|
|
911
|
+
if (x.kind === RVKind.Number && y.kind === RVKind.Number) {
|
|
912
|
+
xs.push(x.value);
|
|
913
|
+
ys.push(y.value);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
return { xs, ys };
|
|
917
|
+
}
|
|
918
|
+
/** COVARIANCE.P — population covariance. */
|
|
919
|
+
export const fnCOVARIANCE_P = args => {
|
|
920
|
+
const pairs = pairedNumericValues(args[0], args[1]);
|
|
921
|
+
if (pairs.kind === RVKind.Error) {
|
|
922
|
+
return pairs;
|
|
923
|
+
}
|
|
924
|
+
const { xs, ys } = pairs;
|
|
925
|
+
const n = xs.length;
|
|
926
|
+
if (n === 0) {
|
|
927
|
+
return ERRORS.DIV0;
|
|
928
|
+
}
|
|
929
|
+
let mx = 0;
|
|
930
|
+
let my = 0;
|
|
931
|
+
for (let i = 0; i < n; i++) {
|
|
932
|
+
mx += xs[i];
|
|
933
|
+
my += ys[i];
|
|
934
|
+
}
|
|
935
|
+
mx /= n;
|
|
936
|
+
my /= n;
|
|
937
|
+
let s = 0;
|
|
938
|
+
for (let i = 0; i < n; i++) {
|
|
939
|
+
s += (xs[i] - mx) * (ys[i] - my);
|
|
940
|
+
}
|
|
941
|
+
return rvNumber(s / n);
|
|
942
|
+
};
|
|
943
|
+
/** COVARIANCE.S — sample covariance (divide by n-1). */
|
|
944
|
+
export const fnCOVARIANCE_S = args => {
|
|
945
|
+
const pairs = pairedNumericValues(args[0], args[1]);
|
|
946
|
+
if (pairs.kind === RVKind.Error) {
|
|
947
|
+
return pairs;
|
|
948
|
+
}
|
|
949
|
+
const { xs, ys } = pairs;
|
|
950
|
+
const n = xs.length;
|
|
951
|
+
if (n < 2) {
|
|
952
|
+
return ERRORS.DIV0;
|
|
953
|
+
}
|
|
954
|
+
let mx = 0;
|
|
955
|
+
let my = 0;
|
|
956
|
+
for (let i = 0; i < n; i++) {
|
|
957
|
+
mx += xs[i];
|
|
958
|
+
my += ys[i];
|
|
959
|
+
}
|
|
960
|
+
mx /= n;
|
|
961
|
+
my /= n;
|
|
962
|
+
let s = 0;
|
|
963
|
+
for (let i = 0; i < n; i++) {
|
|
964
|
+
s += (xs[i] - mx) * (ys[i] - my);
|
|
965
|
+
}
|
|
966
|
+
return rvNumber(s / (n - 1));
|
|
967
|
+
};
|
|
968
|
+
/**
|
|
969
|
+
* RANK.AVG — average-tie rank. Identical to RANK.EQ except that tied
|
|
970
|
+
* positions return the average of the ranks they would otherwise span.
|
|
971
|
+
*/
|
|
972
|
+
export const fnRANK_AVG = args => {
|
|
973
|
+
const numberRV = argToNumber(args[0]);
|
|
974
|
+
if (numberRV.kind === RVKind.Error) {
|
|
975
|
+
return numberRV;
|
|
976
|
+
}
|
|
977
|
+
const rawArr = flattenNumbers([args[1]]);
|
|
978
|
+
const arrErr = firstError(rawArr);
|
|
979
|
+
if (arrErr) {
|
|
980
|
+
return arrErr;
|
|
981
|
+
}
|
|
982
|
+
const nums = rawArr.map(n => n.value);
|
|
983
|
+
const orderRV = args.length > 2 ? argToNumber(args[2]) : rvNumber(0);
|
|
984
|
+
if (orderRV.kind === RVKind.Error) {
|
|
985
|
+
return orderRV;
|
|
986
|
+
}
|
|
987
|
+
const ascending = orderRV.value !== 0;
|
|
988
|
+
const sorted = nums.slice().sort((a, b) => (ascending ? a - b : b - a));
|
|
989
|
+
// Find the range of indices that equal `number`; RANK.AVG returns the
|
|
990
|
+
// average rank over that range.
|
|
991
|
+
const target = numberRV.value;
|
|
992
|
+
let first = -1;
|
|
993
|
+
let last = -1;
|
|
994
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
995
|
+
if (sorted[i] === target) {
|
|
996
|
+
if (first === -1) {
|
|
997
|
+
first = i;
|
|
998
|
+
}
|
|
999
|
+
last = i;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
if (first === -1) {
|
|
1003
|
+
return ERRORS.NA;
|
|
1004
|
+
}
|
|
1005
|
+
// Ranks are 1-based; average of first+1 .. last+1.
|
|
1006
|
+
return rvNumber((first + last + 2) / 2);
|
|
1007
|
+
};
|
|
1008
|
+
export const fnFISHER = args => {
|
|
1009
|
+
const x = argToNumber(args[0]);
|
|
1010
|
+
if (x.kind === RVKind.Error) {
|
|
1011
|
+
return x;
|
|
1012
|
+
}
|
|
1013
|
+
if (x.value <= -1 || x.value >= 1) {
|
|
1014
|
+
return ERRORS.NUM;
|
|
1015
|
+
}
|
|
1016
|
+
return rvNumber(0.5 * Math.log((1 + x.value) / (1 - x.value)));
|
|
1017
|
+
};
|
|
1018
|
+
export const fnFISHERINV = args => {
|
|
1019
|
+
const y = argToNumber(args[0]);
|
|
1020
|
+
if (y.kind === RVKind.Error) {
|
|
1021
|
+
return y;
|
|
1022
|
+
}
|
|
1023
|
+
const e2y = Math.exp(2 * y.value);
|
|
1024
|
+
return rvNumber((e2y - 1) / (e2y + 1));
|
|
1025
|
+
};
|
|
1026
|
+
export const fnAVERAGEA = args => {
|
|
1027
|
+
const all = flattenAll(args);
|
|
1028
|
+
if (all.length === 0) {
|
|
1029
|
+
return ERRORS.DIV0;
|
|
1030
|
+
}
|
|
1031
|
+
let sum = 0;
|
|
1032
|
+
let count = 0;
|
|
1033
|
+
for (const v of all) {
|
|
1034
|
+
if (v.kind === RVKind.Blank) {
|
|
1035
|
+
continue;
|
|
1036
|
+
}
|
|
1037
|
+
if (v.kind === RVKind.Error) {
|
|
1038
|
+
return v;
|
|
1039
|
+
}
|
|
1040
|
+
if (v.kind === RVKind.Number) {
|
|
1041
|
+
sum += v.value;
|
|
1042
|
+
}
|
|
1043
|
+
else if (v.kind === RVKind.Boolean) {
|
|
1044
|
+
sum += v.value ? 1 : 0;
|
|
1045
|
+
}
|
|
1046
|
+
// Text = 0 for AVERAGEA (no addition needed)
|
|
1047
|
+
count++;
|
|
1048
|
+
}
|
|
1049
|
+
return count === 0 ? ERRORS.DIV0 : rvNumber(sum / count);
|
|
1050
|
+
};
|
|
1051
|
+
export const fnMAXA = args => {
|
|
1052
|
+
const all = flattenAll(args);
|
|
1053
|
+
let max = -Infinity;
|
|
1054
|
+
let found = false;
|
|
1055
|
+
for (const v of all) {
|
|
1056
|
+
if (v.kind === RVKind.Blank) {
|
|
1057
|
+
continue;
|
|
1058
|
+
}
|
|
1059
|
+
if (v.kind === RVKind.Error) {
|
|
1060
|
+
return v;
|
|
1061
|
+
}
|
|
1062
|
+
let n;
|
|
1063
|
+
if (v.kind === RVKind.Number) {
|
|
1064
|
+
n = v.value;
|
|
1065
|
+
}
|
|
1066
|
+
else if (v.kind === RVKind.Boolean) {
|
|
1067
|
+
n = v.value ? 1 : 0;
|
|
1068
|
+
}
|
|
1069
|
+
else {
|
|
1070
|
+
n = 0;
|
|
1071
|
+
}
|
|
1072
|
+
if (n > max) {
|
|
1073
|
+
max = n;
|
|
1074
|
+
}
|
|
1075
|
+
found = true;
|
|
1076
|
+
}
|
|
1077
|
+
return rvNumber(found ? max : 0);
|
|
1078
|
+
};
|
|
1079
|
+
export const fnMINA = args => {
|
|
1080
|
+
const all = flattenAll(args);
|
|
1081
|
+
let min = Infinity;
|
|
1082
|
+
let found = false;
|
|
1083
|
+
for (const v of all) {
|
|
1084
|
+
if (v.kind === RVKind.Blank) {
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
if (v.kind === RVKind.Error) {
|
|
1088
|
+
return v;
|
|
1089
|
+
}
|
|
1090
|
+
let n;
|
|
1091
|
+
if (v.kind === RVKind.Number) {
|
|
1092
|
+
n = v.value;
|
|
1093
|
+
}
|
|
1094
|
+
else if (v.kind === RVKind.Boolean) {
|
|
1095
|
+
n = v.value ? 1 : 0;
|
|
1096
|
+
}
|
|
1097
|
+
else {
|
|
1098
|
+
n = 0;
|
|
1099
|
+
}
|
|
1100
|
+
if (n < min) {
|
|
1101
|
+
min = n;
|
|
1102
|
+
}
|
|
1103
|
+
found = true;
|
|
1104
|
+
}
|
|
1105
|
+
return rvNumber(found ? min : 0);
|
|
1106
|
+
};
|
|
1107
|
+
// ============================================================================
|
|
1108
|
+
// Private helpers for distributions (pure number → number, unchanged)
|
|
1109
|
+
// ============================================================================
|
|
1110
|
+
function gammaFn(z) {
|
|
1111
|
+
if (z < 0.5) {
|
|
1112
|
+
return Math.PI / (Math.sin(Math.PI * z) * gammaFn(1 - z));
|
|
1113
|
+
}
|
|
1114
|
+
z -= 1;
|
|
1115
|
+
const g = 7;
|
|
1116
|
+
const c = [
|
|
1117
|
+
0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313,
|
|
1118
|
+
-176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6,
|
|
1119
|
+
1.5056327351493116e-7
|
|
1120
|
+
];
|
|
1121
|
+
let x = c[0];
|
|
1122
|
+
for (let i = 1; i < g + 2; i++) {
|
|
1123
|
+
x += c[i] / (z + i);
|
|
1124
|
+
}
|
|
1125
|
+
const t = z + g + 0.5;
|
|
1126
|
+
return Math.sqrt(2 * Math.PI) * Math.pow(t, z + 0.5) * Math.exp(-t) * x;
|
|
1127
|
+
}
|
|
1128
|
+
function lnGamma(x) {
|
|
1129
|
+
return Math.log(gammaFn(x));
|
|
1130
|
+
}
|
|
1131
|
+
function betaIncomplete(x, a, b) {
|
|
1132
|
+
if (x <= 0) {
|
|
1133
|
+
return 0;
|
|
1134
|
+
}
|
|
1135
|
+
if (x >= 1) {
|
|
1136
|
+
return 1;
|
|
1137
|
+
}
|
|
1138
|
+
if (x > (a + 1) / (a + b + 2)) {
|
|
1139
|
+
return 1 - betaIncomplete(1 - x, b, a);
|
|
1140
|
+
}
|
|
1141
|
+
const lbeta = lnGamma(a) + lnGamma(b) - lnGamma(a + b);
|
|
1142
|
+
const front = Math.exp(Math.log(x) * a + Math.log(1 - x) * b - lbeta) / a;
|
|
1143
|
+
// For extreme (a, b) the front factor underflows to 0 or overflows to
|
|
1144
|
+
// Infinity (e.g. T.DIST.2T at df >= 300 computes betaIncomplete with
|
|
1145
|
+
// a around 150 and x near 0.004; Math.log(x)*a dips below -650, so
|
|
1146
|
+
// Math.exp returns 0 and subsequent `f *= c * d` multiplications can
|
|
1147
|
+
// produce 0 * Infinity = NaN in the continued-fraction loop). Short-
|
|
1148
|
+
// circuit those pathological inputs: a zero front dominates the
|
|
1149
|
+
// series, so the integrated value is effectively 0.
|
|
1150
|
+
if (!Number.isFinite(front) || front === 0) {
|
|
1151
|
+
return 0;
|
|
1152
|
+
}
|
|
1153
|
+
let f = 1;
|
|
1154
|
+
let c = 1;
|
|
1155
|
+
let d = 1 - ((a + b) * x) / (a + 1);
|
|
1156
|
+
if (Math.abs(d) < 1e-30) {
|
|
1157
|
+
d = 1e-30;
|
|
1158
|
+
}
|
|
1159
|
+
d = 1 / d;
|
|
1160
|
+
f = d;
|
|
1161
|
+
for (let m = 1; m <= 200; m++) {
|
|
1162
|
+
let num = (m * (b - m) * x) / ((a + 2 * m - 1) * (a + 2 * m));
|
|
1163
|
+
d = 1 + num * d;
|
|
1164
|
+
if (Math.abs(d) < 1e-30) {
|
|
1165
|
+
d = 1e-30;
|
|
1166
|
+
}
|
|
1167
|
+
c = 1 + num / c;
|
|
1168
|
+
if (Math.abs(c) < 1e-30) {
|
|
1169
|
+
c = 1e-30;
|
|
1170
|
+
}
|
|
1171
|
+
d = 1 / d;
|
|
1172
|
+
f *= c * d;
|
|
1173
|
+
num = -((a + m) * (a + b + m) * x) / ((a + 2 * m) * (a + 2 * m + 1));
|
|
1174
|
+
d = 1 + num * d;
|
|
1175
|
+
if (Math.abs(d) < 1e-30) {
|
|
1176
|
+
d = 1e-30;
|
|
1177
|
+
}
|
|
1178
|
+
c = 1 + num / c;
|
|
1179
|
+
if (Math.abs(c) < 1e-30) {
|
|
1180
|
+
c = 1e-30;
|
|
1181
|
+
}
|
|
1182
|
+
d = 1 / d;
|
|
1183
|
+
const delta = c * d;
|
|
1184
|
+
f *= delta;
|
|
1185
|
+
if (Math.abs(delta - 1) < 1e-10) {
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
return front * f;
|
|
1190
|
+
}
|
|
1191
|
+
function gammaIncomplete(a, x) {
|
|
1192
|
+
if (x < 0) {
|
|
1193
|
+
return 0;
|
|
1194
|
+
}
|
|
1195
|
+
if (x === 0) {
|
|
1196
|
+
return 0;
|
|
1197
|
+
}
|
|
1198
|
+
if (x < a + 1) {
|
|
1199
|
+
let sum = 1 / a;
|
|
1200
|
+
let term = 1 / a;
|
|
1201
|
+
for (let n = 1; n <= 200; n++) {
|
|
1202
|
+
term *= x / (a + n);
|
|
1203
|
+
sum += term;
|
|
1204
|
+
if (Math.abs(term) < Math.abs(sum) * 1e-14) {
|
|
1205
|
+
break;
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
return sum * Math.exp(-x + a * Math.log(x) - lnGamma(a));
|
|
1209
|
+
}
|
|
1210
|
+
let f = 1;
|
|
1211
|
+
const b0 = x + 1 - a;
|
|
1212
|
+
let ci = 1e30;
|
|
1213
|
+
let d = 1 / b0;
|
|
1214
|
+
f = d;
|
|
1215
|
+
for (let i = 1; i <= 200; i++) {
|
|
1216
|
+
const an = -i * (i - a);
|
|
1217
|
+
const bn = x + 2 * i + 1 - a;
|
|
1218
|
+
d = bn + an * d;
|
|
1219
|
+
if (Math.abs(d) < 1e-30) {
|
|
1220
|
+
d = 1e-30;
|
|
1221
|
+
}
|
|
1222
|
+
ci = bn + an / ci;
|
|
1223
|
+
if (Math.abs(ci) < 1e-30) {
|
|
1224
|
+
ci = 1e-30;
|
|
1225
|
+
}
|
|
1226
|
+
d = 1 / d;
|
|
1227
|
+
const delta = d * ci;
|
|
1228
|
+
f *= delta;
|
|
1229
|
+
if (Math.abs(delta - 1) < 1e-10) {
|
|
1230
|
+
break;
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
return 1 - f * Math.exp(-x + a * Math.log(x) - lnGamma(a));
|
|
1234
|
+
}
|
|
1235
|
+
// ============================================================================
|
|
1236
|
+
// More Statistical Distribution Functions
|
|
1237
|
+
// ============================================================================
|
|
1238
|
+
export const fnPOISSON_DIST = args => {
|
|
1239
|
+
const x = argToNumber(args[0]);
|
|
1240
|
+
if (x.kind === RVKind.Error) {
|
|
1241
|
+
return x;
|
|
1242
|
+
}
|
|
1243
|
+
const mean = argToNumber(args[1]);
|
|
1244
|
+
if (mean.kind === RVKind.Error) {
|
|
1245
|
+
return mean;
|
|
1246
|
+
}
|
|
1247
|
+
const cum = boolArg(args, 2);
|
|
1248
|
+
if (!cum.ok) {
|
|
1249
|
+
return cum.error;
|
|
1250
|
+
}
|
|
1251
|
+
const k = Math.floor(x.value);
|
|
1252
|
+
if (k < 0 || mean.value < 0) {
|
|
1253
|
+
return ERRORS.NUM;
|
|
1254
|
+
}
|
|
1255
|
+
// Degenerate mean = 0: the Poisson distribution concentrates all mass at
|
|
1256
|
+
// k = 0. The textbook PMF formula evaluates `k * log(0) = -Infinity`
|
|
1257
|
+
// multiplied by `k = 0`, yielding `NaN`, so we short-circuit.
|
|
1258
|
+
if (mean.value === 0) {
|
|
1259
|
+
if (!cum.value) {
|
|
1260
|
+
return rvNumber(k === 0 ? 1 : 0);
|
|
1261
|
+
}
|
|
1262
|
+
return rvNumber(1); // CDF is 1 at every k >= 0
|
|
1263
|
+
}
|
|
1264
|
+
if (!cum.value) {
|
|
1265
|
+
return rvNumber(Math.exp(-mean.value + k * Math.log(mean.value) - lnGamma(k + 1)));
|
|
1266
|
+
}
|
|
1267
|
+
return rvNumber(1 - gammaIncomplete(k + 1, mean.value));
|
|
1268
|
+
};
|
|
1269
|
+
export const fnBINOM_DIST = args => {
|
|
1270
|
+
const numS = argToNumber(args[0]);
|
|
1271
|
+
if (numS.kind === RVKind.Error) {
|
|
1272
|
+
return numS;
|
|
1273
|
+
}
|
|
1274
|
+
const trials = argToNumber(args[1]);
|
|
1275
|
+
if (trials.kind === RVKind.Error) {
|
|
1276
|
+
return trials;
|
|
1277
|
+
}
|
|
1278
|
+
const probS = argToNumber(args[2]);
|
|
1279
|
+
if (probS.kind === RVKind.Error) {
|
|
1280
|
+
return probS;
|
|
1281
|
+
}
|
|
1282
|
+
const cum = boolArg(args, 3);
|
|
1283
|
+
if (!cum.ok) {
|
|
1284
|
+
return cum.error;
|
|
1285
|
+
}
|
|
1286
|
+
const k = Math.floor(numS.value);
|
|
1287
|
+
const n = Math.floor(trials.value);
|
|
1288
|
+
if (k < 0 || n < 0 || k > n || probS.value < 0 || probS.value > 1) {
|
|
1289
|
+
return ERRORS.NUM;
|
|
1290
|
+
}
|
|
1291
|
+
const pmf = (ki) => {
|
|
1292
|
+
// Degenerate p = 0 or p = 1 edges: the textbook formula multiplies
|
|
1293
|
+
// the log of 0 by 0 (for the absent term), producing NaN. Handle
|
|
1294
|
+
// these analytically — all mass is at k = 0 when p = 0, or at k = n
|
|
1295
|
+
// when p = 1.
|
|
1296
|
+
if (probS.value === 0) {
|
|
1297
|
+
return ki === 0 ? 1 : 0;
|
|
1298
|
+
}
|
|
1299
|
+
if (probS.value === 1) {
|
|
1300
|
+
return ki === n ? 1 : 0;
|
|
1301
|
+
}
|
|
1302
|
+
const lnC = lnGamma(n + 1) - lnGamma(ki + 1) - lnGamma(n - ki + 1);
|
|
1303
|
+
return Math.exp(lnC + ki * Math.log(probS.value) + (n - ki) * Math.log(1 - probS.value));
|
|
1304
|
+
};
|
|
1305
|
+
if (!cum.value) {
|
|
1306
|
+
return rvNumber(pmf(k));
|
|
1307
|
+
}
|
|
1308
|
+
let sum = 0;
|
|
1309
|
+
for (let i = 0; i <= k; i++) {
|
|
1310
|
+
sum += pmf(i);
|
|
1311
|
+
}
|
|
1312
|
+
return rvNumber(sum);
|
|
1313
|
+
};
|
|
1314
|
+
/**
|
|
1315
|
+
* BINOM.DIST.RANGE(trials, probability, number_s, [number_s2]) —
|
|
1316
|
+
* probability of a binomial trial outcome between `number_s` and
|
|
1317
|
+
* `number_s2` (inclusive). When `number_s2` is omitted, returns the
|
|
1318
|
+
* probability of exactly `number_s` successes.
|
|
1319
|
+
*/
|
|
1320
|
+
export const fnBINOM_DIST_RANGE = args => {
|
|
1321
|
+
const trialsV = argToNumber(args[0]);
|
|
1322
|
+
if (trialsV.kind === RVKind.Error) {
|
|
1323
|
+
return trialsV;
|
|
1324
|
+
}
|
|
1325
|
+
const probV = argToNumber(args[1]);
|
|
1326
|
+
if (probV.kind === RVKind.Error) {
|
|
1327
|
+
return probV;
|
|
1328
|
+
}
|
|
1329
|
+
const s1V = argToNumber(args[2]);
|
|
1330
|
+
if (s1V.kind === RVKind.Error) {
|
|
1331
|
+
return s1V;
|
|
1332
|
+
}
|
|
1333
|
+
const s2V = args.length > 3 ? argToNumber(args[3]) : s1V;
|
|
1334
|
+
if (s2V.kind === RVKind.Error) {
|
|
1335
|
+
return s2V;
|
|
1336
|
+
}
|
|
1337
|
+
const n = Math.floor(trialsV.value);
|
|
1338
|
+
const s1 = Math.floor(s1V.value);
|
|
1339
|
+
const s2 = Math.floor(s2V.value);
|
|
1340
|
+
const p = probV.value;
|
|
1341
|
+
if (n < 0 || p < 0 || p > 1) {
|
|
1342
|
+
return ERRORS.NUM;
|
|
1343
|
+
}
|
|
1344
|
+
if (s1 < 0 || s1 > n) {
|
|
1345
|
+
return ERRORS.NUM;
|
|
1346
|
+
}
|
|
1347
|
+
if (s2 < s1 || s2 > n) {
|
|
1348
|
+
return ERRORS.NUM;
|
|
1349
|
+
}
|
|
1350
|
+
const pmf = (ki) => {
|
|
1351
|
+
if (p === 0) {
|
|
1352
|
+
return ki === 0 ? 1 : 0;
|
|
1353
|
+
}
|
|
1354
|
+
if (p === 1) {
|
|
1355
|
+
return ki === n ? 1 : 0;
|
|
1356
|
+
}
|
|
1357
|
+
const lnC = lnGamma(n + 1) - lnGamma(ki + 1) - lnGamma(n - ki + 1);
|
|
1358
|
+
return Math.exp(lnC + ki * Math.log(p) + (n - ki) * Math.log(1 - p));
|
|
1359
|
+
};
|
|
1360
|
+
let sum = 0;
|
|
1361
|
+
for (let i = s1; i <= s2; i++) {
|
|
1362
|
+
sum += pmf(i);
|
|
1363
|
+
}
|
|
1364
|
+
return rvNumber(sum);
|
|
1365
|
+
};
|
|
1366
|
+
export const fnBINOM_INV = args => {
|
|
1367
|
+
const trials = argToNumber(args[0]);
|
|
1368
|
+
if (trials.kind === RVKind.Error) {
|
|
1369
|
+
return trials;
|
|
1370
|
+
}
|
|
1371
|
+
const probS = argToNumber(args[1]);
|
|
1372
|
+
if (probS.kind === RVKind.Error) {
|
|
1373
|
+
return probS;
|
|
1374
|
+
}
|
|
1375
|
+
const alpha = argToNumber(args[2]);
|
|
1376
|
+
if (alpha.kind === RVKind.Error) {
|
|
1377
|
+
return alpha;
|
|
1378
|
+
}
|
|
1379
|
+
const n = Math.floor(trials.value);
|
|
1380
|
+
if (n < 0 || probS.value < 0 || probS.value > 1 || alpha.value < 0 || alpha.value > 1) {
|
|
1381
|
+
return ERRORS.NUM;
|
|
1382
|
+
}
|
|
1383
|
+
let cdf = 0;
|
|
1384
|
+
for (let k = 0; k <= n; k++) {
|
|
1385
|
+
const lnC = lnGamma(n + 1) - lnGamma(k + 1) - lnGamma(n - k + 1);
|
|
1386
|
+
cdf += Math.exp(lnC + k * Math.log(probS.value) + (n - k) * Math.log(1 - probS.value));
|
|
1387
|
+
if (cdf >= alpha.value) {
|
|
1388
|
+
return rvNumber(k);
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
return rvNumber(n);
|
|
1392
|
+
};
|
|
1393
|
+
export const fnHYPGEOM_DIST = args => {
|
|
1394
|
+
const sampleS = argToNumber(args[0]);
|
|
1395
|
+
if (sampleS.kind === RVKind.Error) {
|
|
1396
|
+
return sampleS;
|
|
1397
|
+
}
|
|
1398
|
+
const numberSample = argToNumber(args[1]);
|
|
1399
|
+
if (numberSample.kind === RVKind.Error) {
|
|
1400
|
+
return numberSample;
|
|
1401
|
+
}
|
|
1402
|
+
const popS = argToNumber(args[2]);
|
|
1403
|
+
if (popS.kind === RVKind.Error) {
|
|
1404
|
+
return popS;
|
|
1405
|
+
}
|
|
1406
|
+
const numberPop = argToNumber(args[3]);
|
|
1407
|
+
if (numberPop.kind === RVKind.Error) {
|
|
1408
|
+
return numberPop;
|
|
1409
|
+
}
|
|
1410
|
+
const cum = boolArg(args, 4);
|
|
1411
|
+
if (!cum.ok) {
|
|
1412
|
+
return cum.error;
|
|
1413
|
+
}
|
|
1414
|
+
const ss = Math.floor(sampleS.value);
|
|
1415
|
+
const ns = Math.floor(numberSample.value);
|
|
1416
|
+
const ps = Math.floor(popS.value);
|
|
1417
|
+
const np = Math.floor(numberPop.value);
|
|
1418
|
+
const pmf = (k) => Math.exp(lnGamma(ps + 1) -
|
|
1419
|
+
lnGamma(k + 1) -
|
|
1420
|
+
lnGamma(ps - k + 1) +
|
|
1421
|
+
lnGamma(np - ps + 1) -
|
|
1422
|
+
lnGamma(ns - k + 1) -
|
|
1423
|
+
lnGamma(np - ps - ns + k + 1) -
|
|
1424
|
+
lnGamma(np + 1) +
|
|
1425
|
+
lnGamma(ns + 1) +
|
|
1426
|
+
lnGamma(np - ns + 1));
|
|
1427
|
+
if (!cum.value) {
|
|
1428
|
+
return rvNumber(pmf(ss));
|
|
1429
|
+
}
|
|
1430
|
+
let sum = 0;
|
|
1431
|
+
for (let k = 0; k <= ss; k++) {
|
|
1432
|
+
sum += pmf(k);
|
|
1433
|
+
}
|
|
1434
|
+
return rvNumber(sum);
|
|
1435
|
+
};
|
|
1436
|
+
export const fnNEGBINOM_DIST = args => {
|
|
1437
|
+
const numF = argToNumber(args[0]);
|
|
1438
|
+
if (numF.kind === RVKind.Error) {
|
|
1439
|
+
return numF;
|
|
1440
|
+
}
|
|
1441
|
+
const numS = argToNumber(args[1]);
|
|
1442
|
+
if (numS.kind === RVKind.Error) {
|
|
1443
|
+
return numS;
|
|
1444
|
+
}
|
|
1445
|
+
const probS = argToNumber(args[2]);
|
|
1446
|
+
if (probS.kind === RVKind.Error) {
|
|
1447
|
+
return probS;
|
|
1448
|
+
}
|
|
1449
|
+
const cum = boolArg(args, 3);
|
|
1450
|
+
if (!cum.ok) {
|
|
1451
|
+
return cum.error;
|
|
1452
|
+
}
|
|
1453
|
+
const f = Math.floor(numF.value);
|
|
1454
|
+
const s = Math.floor(numS.value);
|
|
1455
|
+
if (f < 0 || s < 1 || probS.value < 0 || probS.value > 1) {
|
|
1456
|
+
return ERRORS.NUM;
|
|
1457
|
+
}
|
|
1458
|
+
const pmf = (k) => {
|
|
1459
|
+
const lnC = lnGamma(k + s) - lnGamma(s) - lnGamma(k + 1);
|
|
1460
|
+
return Math.exp(lnC + s * Math.log(probS.value) + k * Math.log(1 - probS.value));
|
|
1461
|
+
};
|
|
1462
|
+
if (!cum.value) {
|
|
1463
|
+
return rvNumber(pmf(f));
|
|
1464
|
+
}
|
|
1465
|
+
let sum = 0;
|
|
1466
|
+
for (let k = 0; k <= f; k++) {
|
|
1467
|
+
sum += pmf(k);
|
|
1468
|
+
}
|
|
1469
|
+
return rvNumber(sum);
|
|
1470
|
+
};
|
|
1471
|
+
export const fnCHISQ_DIST = args => {
|
|
1472
|
+
const x = argToNumber(args[0]);
|
|
1473
|
+
if (x.kind === RVKind.Error) {
|
|
1474
|
+
return x;
|
|
1475
|
+
}
|
|
1476
|
+
const df = argToNumber(args[1]);
|
|
1477
|
+
if (df.kind === RVKind.Error) {
|
|
1478
|
+
return df;
|
|
1479
|
+
}
|
|
1480
|
+
const cum = boolArg(args, 2);
|
|
1481
|
+
if (!cum.ok) {
|
|
1482
|
+
return cum.error;
|
|
1483
|
+
}
|
|
1484
|
+
if (x.value < 0 || df.value < 1) {
|
|
1485
|
+
return ERRORS.NUM;
|
|
1486
|
+
}
|
|
1487
|
+
// Excel's CHISQ.DIST accepts non-integer degrees of freedom; the
|
|
1488
|
+
// incomplete-gamma formula is defined for any positive `df/2`. We used
|
|
1489
|
+
// to Math.floor() df, which silently returned the chi-square for the
|
|
1490
|
+
// nearest smaller integer df and produced visibly wrong densities for
|
|
1491
|
+
// fractional inputs.
|
|
1492
|
+
if (cum.value) {
|
|
1493
|
+
return rvNumber(gammaIncomplete(df.value / 2, x.value / 2));
|
|
1494
|
+
}
|
|
1495
|
+
const halfK = df.value / 2;
|
|
1496
|
+
return rvNumber(Math.exp((halfK - 1) * Math.log(x.value / 2) - x.value / 2 - lnGamma(halfK)) / 2);
|
|
1497
|
+
};
|
|
1498
|
+
export const fnCHISQ_INV = args => {
|
|
1499
|
+
const p = argToNumber(args[0]);
|
|
1500
|
+
if (p.kind === RVKind.Error) {
|
|
1501
|
+
return p;
|
|
1502
|
+
}
|
|
1503
|
+
const df = argToNumber(args[1]);
|
|
1504
|
+
if (df.kind === RVKind.Error) {
|
|
1505
|
+
return df;
|
|
1506
|
+
}
|
|
1507
|
+
if (p.value < 0 || p.value >= 1 || df.value < 1) {
|
|
1508
|
+
return ERRORS.NUM;
|
|
1509
|
+
}
|
|
1510
|
+
// P = 0 → x = 0 exactly. Without the short-circuit, Newton from x = df
|
|
1511
|
+
// would compute `(0 - 0) / pdf` = 0 on the first iteration (fine), but
|
|
1512
|
+
// rounding drift can push x negative, triggering the clamp to 0.001 and
|
|
1513
|
+
// returning a non-zero result.
|
|
1514
|
+
if (p.value === 0) {
|
|
1515
|
+
return rvNumber(0);
|
|
1516
|
+
}
|
|
1517
|
+
let x = df.value;
|
|
1518
|
+
for (let iter = 0; iter < 100; iter++) {
|
|
1519
|
+
const cdf = gammaIncomplete(df.value / 2, x / 2);
|
|
1520
|
+
const halfK = df.value / 2;
|
|
1521
|
+
const pdf = Math.exp((halfK - 1) * Math.log(x / 2) - x / 2 - lnGamma(halfK)) / 2;
|
|
1522
|
+
if (Math.abs(pdf) < 1e-15) {
|
|
1523
|
+
break;
|
|
1524
|
+
}
|
|
1525
|
+
const delta = (cdf - p.value) / pdf;
|
|
1526
|
+
x -= delta;
|
|
1527
|
+
if (x <= 0) {
|
|
1528
|
+
x = 0.001;
|
|
1529
|
+
}
|
|
1530
|
+
if (Math.abs(delta) < 1e-10) {
|
|
1531
|
+
break;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
return rvNumber(x);
|
|
1535
|
+
};
|
|
1536
|
+
export const fnCHISQ_DIST_RT = args => {
|
|
1537
|
+
const x = argToNumber(args[0]);
|
|
1538
|
+
if (x.kind === RVKind.Error) {
|
|
1539
|
+
return x;
|
|
1540
|
+
}
|
|
1541
|
+
const df = argToNumber(args[1]);
|
|
1542
|
+
if (df.kind === RVKind.Error) {
|
|
1543
|
+
return df;
|
|
1544
|
+
}
|
|
1545
|
+
if (x.value < 0 || df.value < 1) {
|
|
1546
|
+
return ERRORS.NUM;
|
|
1547
|
+
}
|
|
1548
|
+
return rvNumber(1 - gammaIncomplete(df.value / 2, x.value / 2));
|
|
1549
|
+
};
|
|
1550
|
+
/**
|
|
1551
|
+
* CHISQ.INV.RT(probability, df) — right-tailed inverse of chi-square.
|
|
1552
|
+
* Equivalent to CHISQ.INV(1 - probability, df). Probabilities of 0 or 1
|
|
1553
|
+
* return +∞ or 0 respectively; values outside (0, 1] are #NUM!.
|
|
1554
|
+
*/
|
|
1555
|
+
export const fnCHISQ_INV_RT = args => {
|
|
1556
|
+
const p = argToNumber(args[0]);
|
|
1557
|
+
if (p.kind === RVKind.Error) {
|
|
1558
|
+
return p;
|
|
1559
|
+
}
|
|
1560
|
+
const df = argToNumber(args[1]);
|
|
1561
|
+
if (df.kind === RVKind.Error) {
|
|
1562
|
+
return df;
|
|
1563
|
+
}
|
|
1564
|
+
if (p.value <= 0 || p.value > 1 || df.value < 1) {
|
|
1565
|
+
return ERRORS.NUM;
|
|
1566
|
+
}
|
|
1567
|
+
// Re-use CHISQ.INV with the complementary probability.
|
|
1568
|
+
return fnCHISQ_INV([rvNumber(1 - p.value), df]);
|
|
1569
|
+
};
|
|
1570
|
+
export const fnF_DIST = args => {
|
|
1571
|
+
const x = argToNumber(args[0]);
|
|
1572
|
+
if (x.kind === RVKind.Error) {
|
|
1573
|
+
return x;
|
|
1574
|
+
}
|
|
1575
|
+
const df1 = argToNumber(args[1]);
|
|
1576
|
+
if (df1.kind === RVKind.Error) {
|
|
1577
|
+
return df1;
|
|
1578
|
+
}
|
|
1579
|
+
const df2 = argToNumber(args[2]);
|
|
1580
|
+
if (df2.kind === RVKind.Error) {
|
|
1581
|
+
return df2;
|
|
1582
|
+
}
|
|
1583
|
+
const cum = boolArg(args, 3);
|
|
1584
|
+
if (!cum.ok) {
|
|
1585
|
+
return cum.error;
|
|
1586
|
+
}
|
|
1587
|
+
if (x.value < 0 || df1.value < 1 || df2.value < 1) {
|
|
1588
|
+
return ERRORS.NUM;
|
|
1589
|
+
}
|
|
1590
|
+
// Accept fractional df. See CHISQ.DIST comment — the betaIncomplete/lnGamma
|
|
1591
|
+
// formulas below are defined for any positive df, so there is no reason
|
|
1592
|
+
// to truncate.
|
|
1593
|
+
const d1 = df1.value;
|
|
1594
|
+
const d2 = df2.value;
|
|
1595
|
+
if (cum.value) {
|
|
1596
|
+
return rvNumber(betaIncomplete((d1 * x.value) / (d1 * x.value + d2), d1 / 2, d2 / 2));
|
|
1597
|
+
}
|
|
1598
|
+
const num = (Math.pow(d1 * x.value, d1 / 2) * Math.pow(d2, d2 / 2)) /
|
|
1599
|
+
Math.pow(d1 * x.value + d2, (d1 + d2) / 2);
|
|
1600
|
+
const denom = x.value * Math.exp(lnGamma(d1 / 2) + lnGamma(d2 / 2) - lnGamma((d1 + d2) / 2));
|
|
1601
|
+
return rvNumber(denom === 0 ? 0 : num / denom);
|
|
1602
|
+
};
|
|
1603
|
+
export const fnF_INV = args => {
|
|
1604
|
+
const p = argToNumber(args[0]);
|
|
1605
|
+
if (p.kind === RVKind.Error) {
|
|
1606
|
+
return p;
|
|
1607
|
+
}
|
|
1608
|
+
const df1 = argToNumber(args[1]);
|
|
1609
|
+
if (df1.kind === RVKind.Error) {
|
|
1610
|
+
return df1;
|
|
1611
|
+
}
|
|
1612
|
+
const df2 = argToNumber(args[2]);
|
|
1613
|
+
if (df2.kind === RVKind.Error) {
|
|
1614
|
+
return df2;
|
|
1615
|
+
}
|
|
1616
|
+
if (p.value < 0 || p.value >= 1 || df1.value < 1 || df2.value < 1) {
|
|
1617
|
+
return ERRORS.NUM;
|
|
1618
|
+
}
|
|
1619
|
+
const d1 = df1.value;
|
|
1620
|
+
const d2 = df2.value;
|
|
1621
|
+
let lo = 0;
|
|
1622
|
+
let hi = 1000;
|
|
1623
|
+
for (let i = 0; i < 100; i++) {
|
|
1624
|
+
const mid = (lo + hi) / 2;
|
|
1625
|
+
const cdf = betaIncomplete((d1 * mid) / (d1 * mid + d2), d1 / 2, d2 / 2);
|
|
1626
|
+
if (cdf < p.value) {
|
|
1627
|
+
lo = mid;
|
|
1628
|
+
}
|
|
1629
|
+
else {
|
|
1630
|
+
hi = mid;
|
|
1631
|
+
}
|
|
1632
|
+
if (hi - lo < 1e-10) {
|
|
1633
|
+
break;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
return rvNumber((lo + hi) / 2);
|
|
1637
|
+
};
|
|
1638
|
+
export const fnT_DIST = args => {
|
|
1639
|
+
const x = argToNumber(args[0]);
|
|
1640
|
+
if (x.kind === RVKind.Error) {
|
|
1641
|
+
return x;
|
|
1642
|
+
}
|
|
1643
|
+
const df = argToNumber(args[1]);
|
|
1644
|
+
if (df.kind === RVKind.Error) {
|
|
1645
|
+
return df;
|
|
1646
|
+
}
|
|
1647
|
+
const cum = boolArg(args, 2);
|
|
1648
|
+
if (!cum.ok) {
|
|
1649
|
+
return cum.error;
|
|
1650
|
+
}
|
|
1651
|
+
if (df.value < 1) {
|
|
1652
|
+
return ERRORS.NUM;
|
|
1653
|
+
}
|
|
1654
|
+
const v = df.value;
|
|
1655
|
+
if (cum.value) {
|
|
1656
|
+
const t = v / (v + x.value * x.value);
|
|
1657
|
+
const halfBeta = 0.5 * betaIncomplete(t, v / 2, 0.5);
|
|
1658
|
+
return rvNumber(x.value >= 0 ? 1 - halfBeta : halfBeta);
|
|
1659
|
+
}
|
|
1660
|
+
return rvNumber(Math.exp(lnGamma((v + 1) / 2) - lnGamma(v / 2)) /
|
|
1661
|
+
(Math.sqrt(v * Math.PI) * Math.pow(1 + (x.value * x.value) / v, (v + 1) / 2)));
|
|
1662
|
+
};
|
|
1663
|
+
export const fnT_INV = args => {
|
|
1664
|
+
const p = argToNumber(args[0]);
|
|
1665
|
+
if (p.kind === RVKind.Error) {
|
|
1666
|
+
return p;
|
|
1667
|
+
}
|
|
1668
|
+
const df = argToNumber(args[1]);
|
|
1669
|
+
if (df.kind === RVKind.Error) {
|
|
1670
|
+
return df;
|
|
1671
|
+
}
|
|
1672
|
+
if (p.value <= 0 || p.value >= 1 || df.value < 1) {
|
|
1673
|
+
return ERRORS.NUM;
|
|
1674
|
+
}
|
|
1675
|
+
let x = normSInv(p.value);
|
|
1676
|
+
const v = df.value;
|
|
1677
|
+
for (let iter = 0; iter < 100; iter++) {
|
|
1678
|
+
const t = v / (v + x * x);
|
|
1679
|
+
const halfBeta = 0.5 * betaIncomplete(t, v / 2, 0.5);
|
|
1680
|
+
const cdf = x >= 0 ? 1 - halfBeta : halfBeta;
|
|
1681
|
+
const pdf = Math.exp(lnGamma((v + 1) / 2) - lnGamma(v / 2)) /
|
|
1682
|
+
(Math.sqrt(v * Math.PI) * Math.pow(1 + (x * x) / v, (v + 1) / 2));
|
|
1683
|
+
if (Math.abs(pdf) < 1e-15) {
|
|
1684
|
+
break;
|
|
1685
|
+
}
|
|
1686
|
+
const delta = (cdf - p.value) / pdf;
|
|
1687
|
+
x -= delta;
|
|
1688
|
+
if (Math.abs(delta) < 1e-10) {
|
|
1689
|
+
break;
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
return rvNumber(x);
|
|
1693
|
+
};
|
|
1694
|
+
export const fnT_DIST_2T = args => {
|
|
1695
|
+
const x = argToNumber(args[0]);
|
|
1696
|
+
if (x.kind === RVKind.Error) {
|
|
1697
|
+
return x;
|
|
1698
|
+
}
|
|
1699
|
+
const df = argToNumber(args[1]);
|
|
1700
|
+
if (df.kind === RVKind.Error) {
|
|
1701
|
+
return df;
|
|
1702
|
+
}
|
|
1703
|
+
if (x.value < 0 || df.value < 1) {
|
|
1704
|
+
return ERRORS.NUM;
|
|
1705
|
+
}
|
|
1706
|
+
const v = df.value;
|
|
1707
|
+
return rvNumber(betaIncomplete(v / (v + x.value * x.value), v / 2, 0.5));
|
|
1708
|
+
};
|
|
1709
|
+
export const fnT_DIST_RT = args => {
|
|
1710
|
+
const x = argToNumber(args[0]);
|
|
1711
|
+
if (x.kind === RVKind.Error) {
|
|
1712
|
+
return x;
|
|
1713
|
+
}
|
|
1714
|
+
const df = argToNumber(args[1]);
|
|
1715
|
+
if (df.kind === RVKind.Error) {
|
|
1716
|
+
return df;
|
|
1717
|
+
}
|
|
1718
|
+
if (df.value < 1) {
|
|
1719
|
+
return ERRORS.NUM;
|
|
1720
|
+
}
|
|
1721
|
+
const v = df.value;
|
|
1722
|
+
const halfBeta = 0.5 * betaIncomplete(v / (v + x.value * x.value), v / 2, 0.5);
|
|
1723
|
+
// T.DIST.RT(x, df) = right-tail = 1 - CDF(x)
|
|
1724
|
+
// For x >= 0: right-tail = halfBeta
|
|
1725
|
+
// For x < 0: right-tail = 1 - halfBeta
|
|
1726
|
+
return rvNumber(x.value >= 0 ? halfBeta : 1 - halfBeta);
|
|
1727
|
+
};
|
|
1728
|
+
export const fnT_INV_2T = args => {
|
|
1729
|
+
const p = argToNumber(args[0]);
|
|
1730
|
+
if (p.kind === RVKind.Error) {
|
|
1731
|
+
return p;
|
|
1732
|
+
}
|
|
1733
|
+
const df = argToNumber(args[1]);
|
|
1734
|
+
if (df.kind === RVKind.Error) {
|
|
1735
|
+
return df;
|
|
1736
|
+
}
|
|
1737
|
+
if (p.value <= 0 || p.value > 1 || df.value < 1) {
|
|
1738
|
+
return ERRORS.NUM;
|
|
1739
|
+
}
|
|
1740
|
+
const result = fnT_INV([rvNumber(1 - p.value / 2), args[1]]);
|
|
1741
|
+
if (isError(result)) {
|
|
1742
|
+
return result;
|
|
1743
|
+
}
|
|
1744
|
+
return rvNumber(Math.abs(result.value));
|
|
1745
|
+
};
|
|
1746
|
+
// ============================================================================
|
|
1747
|
+
// BETA, GAMMA, EXPON, WEIBULL, LOGNORM distributions
|
|
1748
|
+
// ============================================================================
|
|
1749
|
+
export const fnBETA_DIST = args => {
|
|
1750
|
+
const x = argToNumber(args[0]);
|
|
1751
|
+
if (x.kind === RVKind.Error) {
|
|
1752
|
+
return x;
|
|
1753
|
+
}
|
|
1754
|
+
const alpha = argToNumber(args[1]);
|
|
1755
|
+
if (alpha.kind === RVKind.Error) {
|
|
1756
|
+
return alpha;
|
|
1757
|
+
}
|
|
1758
|
+
const beta = argToNumber(args[2]);
|
|
1759
|
+
if (beta.kind === RVKind.Error) {
|
|
1760
|
+
return beta;
|
|
1761
|
+
}
|
|
1762
|
+
let cumVal = true;
|
|
1763
|
+
if (args.length > 3) {
|
|
1764
|
+
const cum = boolArg(args, 3);
|
|
1765
|
+
if (!cum.ok) {
|
|
1766
|
+
return cum.error;
|
|
1767
|
+
}
|
|
1768
|
+
cumVal = cum.value;
|
|
1769
|
+
}
|
|
1770
|
+
let A = 0;
|
|
1771
|
+
if (args.length > 4) {
|
|
1772
|
+
const aRV = argToNumber(args[4]);
|
|
1773
|
+
if (aRV.kind === RVKind.Error) {
|
|
1774
|
+
return aRV;
|
|
1775
|
+
}
|
|
1776
|
+
A = aRV.value;
|
|
1777
|
+
}
|
|
1778
|
+
let B = 1;
|
|
1779
|
+
if (args.length > 5) {
|
|
1780
|
+
const bRV = argToNumber(args[5]);
|
|
1781
|
+
if (bRV.kind === RVKind.Error) {
|
|
1782
|
+
return bRV;
|
|
1783
|
+
}
|
|
1784
|
+
B = bRV.value;
|
|
1785
|
+
}
|
|
1786
|
+
if (alpha.value <= 0 || beta.value <= 0 || B <= A) {
|
|
1787
|
+
return ERRORS.NUM;
|
|
1788
|
+
}
|
|
1789
|
+
const xn = (x.value - A) / (B - A);
|
|
1790
|
+
if (xn < 0 || xn > 1) {
|
|
1791
|
+
return ERRORS.NUM;
|
|
1792
|
+
}
|
|
1793
|
+
if (cumVal) {
|
|
1794
|
+
return rvNumber(betaIncomplete(xn, alpha.value, beta.value));
|
|
1795
|
+
}
|
|
1796
|
+
return rvNumber(Math.exp((alpha.value - 1) * Math.log(xn) +
|
|
1797
|
+
(beta.value - 1) * Math.log(1 - xn) -
|
|
1798
|
+
lnGamma(alpha.value) -
|
|
1799
|
+
lnGamma(beta.value) +
|
|
1800
|
+
lnGamma(alpha.value + beta.value)) /
|
|
1801
|
+
(B - A));
|
|
1802
|
+
};
|
|
1803
|
+
export const fnBETA_INV = args => {
|
|
1804
|
+
const p = argToNumber(args[0]);
|
|
1805
|
+
if (p.kind === RVKind.Error) {
|
|
1806
|
+
return p;
|
|
1807
|
+
}
|
|
1808
|
+
const alpha = argToNumber(args[1]);
|
|
1809
|
+
if (alpha.kind === RVKind.Error) {
|
|
1810
|
+
return alpha;
|
|
1811
|
+
}
|
|
1812
|
+
const beta = argToNumber(args[2]);
|
|
1813
|
+
if (beta.kind === RVKind.Error) {
|
|
1814
|
+
return beta;
|
|
1815
|
+
}
|
|
1816
|
+
let A = 0;
|
|
1817
|
+
if (args.length > 3) {
|
|
1818
|
+
const aRV = argToNumber(args[3]);
|
|
1819
|
+
if (aRV.kind === RVKind.Error) {
|
|
1820
|
+
return aRV;
|
|
1821
|
+
}
|
|
1822
|
+
A = aRV.value;
|
|
1823
|
+
}
|
|
1824
|
+
let B = 1;
|
|
1825
|
+
if (args.length > 4) {
|
|
1826
|
+
const bRV = argToNumber(args[4]);
|
|
1827
|
+
if (bRV.kind === RVKind.Error) {
|
|
1828
|
+
return bRV;
|
|
1829
|
+
}
|
|
1830
|
+
B = bRV.value;
|
|
1831
|
+
}
|
|
1832
|
+
if (p.value < 0 || p.value > 1 || alpha.value <= 0 || beta.value <= 0) {
|
|
1833
|
+
return ERRORS.NUM;
|
|
1834
|
+
}
|
|
1835
|
+
let lo = 0;
|
|
1836
|
+
let hi = 1;
|
|
1837
|
+
for (let i = 0; i < 100; i++) {
|
|
1838
|
+
const mid = (lo + hi) / 2;
|
|
1839
|
+
if (betaIncomplete(mid, alpha.value, beta.value) < p.value) {
|
|
1840
|
+
lo = mid;
|
|
1841
|
+
}
|
|
1842
|
+
else {
|
|
1843
|
+
hi = mid;
|
|
1844
|
+
}
|
|
1845
|
+
if (hi - lo < 1e-12) {
|
|
1846
|
+
break;
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
return rvNumber(A + ((lo + hi) / 2) * (B - A));
|
|
1850
|
+
};
|
|
1851
|
+
export const fnGAMMA = args => {
|
|
1852
|
+
const n = argToNumber(args[0]);
|
|
1853
|
+
if (n.kind === RVKind.Error) {
|
|
1854
|
+
return n;
|
|
1855
|
+
}
|
|
1856
|
+
if (n.value <= 0 && n.value === Math.floor(n.value)) {
|
|
1857
|
+
return ERRORS.NUM;
|
|
1858
|
+
}
|
|
1859
|
+
return rvNumber(gammaFn(n.value));
|
|
1860
|
+
};
|
|
1861
|
+
export const fnGAMMALN = args => {
|
|
1862
|
+
const n = argToNumber(args[0]);
|
|
1863
|
+
if (n.kind === RVKind.Error) {
|
|
1864
|
+
return n;
|
|
1865
|
+
}
|
|
1866
|
+
if (n.value <= 0) {
|
|
1867
|
+
return ERRORS.NUM;
|
|
1868
|
+
}
|
|
1869
|
+
return rvNumber(lnGamma(n.value));
|
|
1870
|
+
};
|
|
1871
|
+
export const fnGAMMA_DIST = args => {
|
|
1872
|
+
const x = argToNumber(args[0]);
|
|
1873
|
+
if (x.kind === RVKind.Error) {
|
|
1874
|
+
return x;
|
|
1875
|
+
}
|
|
1876
|
+
const alpha = argToNumber(args[1]);
|
|
1877
|
+
if (alpha.kind === RVKind.Error) {
|
|
1878
|
+
return alpha;
|
|
1879
|
+
}
|
|
1880
|
+
const beta = argToNumber(args[2]);
|
|
1881
|
+
if (beta.kind === RVKind.Error) {
|
|
1882
|
+
return beta;
|
|
1883
|
+
}
|
|
1884
|
+
const cum = boolArg(args, 3);
|
|
1885
|
+
if (!cum.ok) {
|
|
1886
|
+
return cum.error;
|
|
1887
|
+
}
|
|
1888
|
+
if (x.value < 0 || alpha.value <= 0 || beta.value <= 0) {
|
|
1889
|
+
return ERRORS.NUM;
|
|
1890
|
+
}
|
|
1891
|
+
if (cum.value) {
|
|
1892
|
+
return rvNumber(gammaIncomplete(alpha.value, x.value / beta.value));
|
|
1893
|
+
}
|
|
1894
|
+
return rvNumber(Math.exp((alpha.value - 1) * Math.log(x.value) -
|
|
1895
|
+
x.value / beta.value -
|
|
1896
|
+
alpha.value * Math.log(beta.value) -
|
|
1897
|
+
lnGamma(alpha.value)));
|
|
1898
|
+
};
|
|
1899
|
+
export const fnGAMMA_INV = args => {
|
|
1900
|
+
const p = argToNumber(args[0]);
|
|
1901
|
+
if (p.kind === RVKind.Error) {
|
|
1902
|
+
return p;
|
|
1903
|
+
}
|
|
1904
|
+
const alpha = argToNumber(args[1]);
|
|
1905
|
+
if (alpha.kind === RVKind.Error) {
|
|
1906
|
+
return alpha;
|
|
1907
|
+
}
|
|
1908
|
+
const beta = argToNumber(args[2]);
|
|
1909
|
+
if (beta.kind === RVKind.Error) {
|
|
1910
|
+
return beta;
|
|
1911
|
+
}
|
|
1912
|
+
if (p.value < 0 || p.value >= 1 || alpha.value <= 0 || beta.value <= 0) {
|
|
1913
|
+
return ERRORS.NUM;
|
|
1914
|
+
}
|
|
1915
|
+
let lo = 0;
|
|
1916
|
+
let hi = Math.max(alpha.value * beta.value * 10, 100);
|
|
1917
|
+
for (let i = 0; i < 100; i++) {
|
|
1918
|
+
const mid = (lo + hi) / 2;
|
|
1919
|
+
if (gammaIncomplete(alpha.value, mid / beta.value) < p.value) {
|
|
1920
|
+
lo = mid;
|
|
1921
|
+
}
|
|
1922
|
+
else {
|
|
1923
|
+
hi = mid;
|
|
1924
|
+
}
|
|
1925
|
+
if (hi - lo < 1e-10) {
|
|
1926
|
+
break;
|
|
1927
|
+
}
|
|
1928
|
+
}
|
|
1929
|
+
return rvNumber((lo + hi) / 2);
|
|
1930
|
+
};
|
|
1931
|
+
export const fnEXPON_DIST = args => {
|
|
1932
|
+
const x = argToNumber(args[0]);
|
|
1933
|
+
if (x.kind === RVKind.Error) {
|
|
1934
|
+
return x;
|
|
1935
|
+
}
|
|
1936
|
+
const lambda = argToNumber(args[1]);
|
|
1937
|
+
if (lambda.kind === RVKind.Error) {
|
|
1938
|
+
return lambda;
|
|
1939
|
+
}
|
|
1940
|
+
const cum = boolArg(args, 2);
|
|
1941
|
+
if (!cum.ok) {
|
|
1942
|
+
return cum.error;
|
|
1943
|
+
}
|
|
1944
|
+
if (x.value < 0 || lambda.value <= 0) {
|
|
1945
|
+
return ERRORS.NUM;
|
|
1946
|
+
}
|
|
1947
|
+
return rvNumber(cum.value
|
|
1948
|
+
? 1 - Math.exp(-lambda.value * x.value)
|
|
1949
|
+
: lambda.value * Math.exp(-lambda.value * x.value));
|
|
1950
|
+
};
|
|
1951
|
+
export const fnWEIBULL_DIST = args => {
|
|
1952
|
+
const x = argToNumber(args[0]);
|
|
1953
|
+
if (x.kind === RVKind.Error) {
|
|
1954
|
+
return x;
|
|
1955
|
+
}
|
|
1956
|
+
const alpha = argToNumber(args[1]);
|
|
1957
|
+
if (alpha.kind === RVKind.Error) {
|
|
1958
|
+
return alpha;
|
|
1959
|
+
}
|
|
1960
|
+
const beta = argToNumber(args[2]);
|
|
1961
|
+
if (beta.kind === RVKind.Error) {
|
|
1962
|
+
return beta;
|
|
1963
|
+
}
|
|
1964
|
+
const cum = boolArg(args, 3);
|
|
1965
|
+
if (!cum.ok) {
|
|
1966
|
+
return cum.error;
|
|
1967
|
+
}
|
|
1968
|
+
if (x.value < 0 || alpha.value <= 0 || beta.value <= 0) {
|
|
1969
|
+
return ERRORS.NUM;
|
|
1970
|
+
}
|
|
1971
|
+
if (cum.value) {
|
|
1972
|
+
return rvNumber(1 - Math.exp(-Math.pow(x.value / beta.value, alpha.value)));
|
|
1973
|
+
}
|
|
1974
|
+
return rvNumber((alpha.value / beta.value) *
|
|
1975
|
+
Math.pow(x.value / beta.value, alpha.value - 1) *
|
|
1976
|
+
Math.exp(-Math.pow(x.value / beta.value, alpha.value)));
|
|
1977
|
+
};
|
|
1978
|
+
export const fnLOGNORM_DIST = args => {
|
|
1979
|
+
const x = argToNumber(args[0]);
|
|
1980
|
+
if (x.kind === RVKind.Error) {
|
|
1981
|
+
return x;
|
|
1982
|
+
}
|
|
1983
|
+
const mean = argToNumber(args[1]);
|
|
1984
|
+
if (mean.kind === RVKind.Error) {
|
|
1985
|
+
return mean;
|
|
1986
|
+
}
|
|
1987
|
+
const stddev = argToNumber(args[2]);
|
|
1988
|
+
if (stddev.kind === RVKind.Error) {
|
|
1989
|
+
return stddev;
|
|
1990
|
+
}
|
|
1991
|
+
const cum = boolArg(args, 3);
|
|
1992
|
+
if (!cum.ok) {
|
|
1993
|
+
return cum.error;
|
|
1994
|
+
}
|
|
1995
|
+
if (x.value <= 0 || stddev.value <= 0) {
|
|
1996
|
+
return ERRORS.NUM;
|
|
1997
|
+
}
|
|
1998
|
+
const z = (Math.log(x.value) - mean.value) / stddev.value;
|
|
1999
|
+
if (cum.value) {
|
|
2000
|
+
return rvNumber(normSDist(z));
|
|
2001
|
+
}
|
|
2002
|
+
return rvNumber(normSPdf(z) / (x.value * stddev.value));
|
|
2003
|
+
};
|
|
2004
|
+
export const fnLOGNORM_INV = args => {
|
|
2005
|
+
const p = argToNumber(args[0]);
|
|
2006
|
+
if (p.kind === RVKind.Error) {
|
|
2007
|
+
return p;
|
|
2008
|
+
}
|
|
2009
|
+
const mean = argToNumber(args[1]);
|
|
2010
|
+
if (mean.kind === RVKind.Error) {
|
|
2011
|
+
return mean;
|
|
2012
|
+
}
|
|
2013
|
+
const stddev = argToNumber(args[2]);
|
|
2014
|
+
if (stddev.kind === RVKind.Error) {
|
|
2015
|
+
return stddev;
|
|
2016
|
+
}
|
|
2017
|
+
if (p.value <= 0 || p.value >= 1 || stddev.value <= 0) {
|
|
2018
|
+
return ERRORS.NUM;
|
|
2019
|
+
}
|
|
2020
|
+
return rvNumber(Math.exp(mean.value + stddev.value * normSInv(p.value)));
|
|
2021
|
+
};
|
|
2022
|
+
export const fnPHI = args => {
|
|
2023
|
+
const x = argToNumber(args[0]);
|
|
2024
|
+
return x.kind === RVKind.Error ? x : rvNumber(normSPdf(x.value));
|
|
2025
|
+
};
|
|
2026
|
+
export const fnGAUSS = args => {
|
|
2027
|
+
const z = argToNumber(args[0]);
|
|
2028
|
+
return z.kind === RVKind.Error ? z : rvNumber(normSDist(z.value) - 0.5);
|
|
2029
|
+
};
|
|
2030
|
+
// ============================================================================
|
|
2031
|
+
// ERF, ERFC, STANDARDIZE
|
|
2032
|
+
// ============================================================================
|
|
2033
|
+
function erfFn(x) {
|
|
2034
|
+
// Exact at 0 — the rational approximation below leaves a ~7e-10 drift at
|
|
2035
|
+
// t = 1 / (1 + p·0) = 1, which is visible to callers that expect
|
|
2036
|
+
// `ERF(0) === 0` (and especially to `ERFC(0) === 1`).
|
|
2037
|
+
if (x === 0) {
|
|
2038
|
+
return 0;
|
|
2039
|
+
}
|
|
2040
|
+
const a1 = 0.254829592;
|
|
2041
|
+
const a2 = -0.284496736;
|
|
2042
|
+
const a3 = 1.421413741;
|
|
2043
|
+
const a4 = -1.453152027;
|
|
2044
|
+
const a5 = 1.061405429;
|
|
2045
|
+
const p = 0.3275911;
|
|
2046
|
+
const sign = x < 0 ? -1 : 1;
|
|
2047
|
+
const ax = Math.abs(x);
|
|
2048
|
+
const t = 1.0 / (1.0 + p * ax);
|
|
2049
|
+
const y = 1.0 - ((((a5 * t + a4) * t + a3) * t + a2) * t + a1) * t * Math.exp(-ax * ax);
|
|
2050
|
+
return sign * y;
|
|
2051
|
+
}
|
|
2052
|
+
export const fnERF = args => {
|
|
2053
|
+
const lower = argToNumber(args[0]);
|
|
2054
|
+
if (lower.kind === RVKind.Error) {
|
|
2055
|
+
return lower;
|
|
2056
|
+
}
|
|
2057
|
+
if (args.length > 1) {
|
|
2058
|
+
const upper = argToNumber(args[1]);
|
|
2059
|
+
if (upper.kind === RVKind.Error) {
|
|
2060
|
+
return upper;
|
|
2061
|
+
}
|
|
2062
|
+
return rvNumber(erfFn(upper.value) - erfFn(lower.value));
|
|
2063
|
+
}
|
|
2064
|
+
return rvNumber(erfFn(lower.value));
|
|
2065
|
+
};
|
|
2066
|
+
export const fnERFC = args => {
|
|
2067
|
+
const x = argToNumber(args[0]);
|
|
2068
|
+
return x.kind === RVKind.Error ? x : rvNumber(1 - erfFn(x.value));
|
|
2069
|
+
};
|
|
2070
|
+
export const fnSTANDARDIZE = args => {
|
|
2071
|
+
const x = argToNumber(args[0]);
|
|
2072
|
+
if (x.kind === RVKind.Error) {
|
|
2073
|
+
return x;
|
|
2074
|
+
}
|
|
2075
|
+
const mean = argToNumber(args[1]);
|
|
2076
|
+
if (mean.kind === RVKind.Error) {
|
|
2077
|
+
return mean;
|
|
2078
|
+
}
|
|
2079
|
+
const stddev = argToNumber(args[2]);
|
|
2080
|
+
if (stddev.kind === RVKind.Error) {
|
|
2081
|
+
return stddev;
|
|
2082
|
+
}
|
|
2083
|
+
if (stddev.value <= 0) {
|
|
2084
|
+
return ERRORS.NUM;
|
|
2085
|
+
}
|
|
2086
|
+
return rvNumber((x.value - mean.value) / stddev.value);
|
|
2087
|
+
};
|
|
2088
|
+
// ============================================================================
|
|
2089
|
+
// Array-returning functions: FREQUENCY, GROWTH, TREND, LINEST, LOGEST
|
|
2090
|
+
// ============================================================================
|
|
2091
|
+
export const fnFREQUENCY = args => {
|
|
2092
|
+
if (!isArrayArg(args[0]) || !isArrayArg(args[1])) {
|
|
2093
|
+
return ERRORS.VALUE;
|
|
2094
|
+
}
|
|
2095
|
+
const rawData = flattenNumbers([args[0]]);
|
|
2096
|
+
const dataErr = firstError(rawData);
|
|
2097
|
+
if (dataErr) {
|
|
2098
|
+
return dataErr;
|
|
2099
|
+
}
|
|
2100
|
+
const data = rawData.map(n => n.value);
|
|
2101
|
+
const rawBins = flattenNumbers([args[1]]);
|
|
2102
|
+
const binsErr = firstError(rawBins);
|
|
2103
|
+
if (binsErr) {
|
|
2104
|
+
return binsErr;
|
|
2105
|
+
}
|
|
2106
|
+
const bins = rawBins.map(n => n.value);
|
|
2107
|
+
// IMPORTANT: do NOT sort `bins`. Excel's FREQUENCY bucketises data into
|
|
2108
|
+
// the bins as the user supplied them — both the output count order and
|
|
2109
|
+
// the bucket boundaries follow the original sequence. Sorting would
|
|
2110
|
+
// silently reshuffle the result array, which is a common historical
|
|
2111
|
+
// implementation mistake.
|
|
2112
|
+
//
|
|
2113
|
+
// For each datum we assign it to the first bin `i` whose upper bound
|
|
2114
|
+
// satisfies `data <= bins[i]`; if no such bin exists the datum falls
|
|
2115
|
+
// into the overflow bucket at index `bins.length`. This matches Excel's
|
|
2116
|
+
// semantics for any bin order (monotonic or not).
|
|
2117
|
+
const result = [];
|
|
2118
|
+
const counts = new Array(bins.length + 1).fill(0);
|
|
2119
|
+
for (const d of data) {
|
|
2120
|
+
let assigned = false;
|
|
2121
|
+
for (let i = 0; i < bins.length; i++) {
|
|
2122
|
+
if (d <= bins[i]) {
|
|
2123
|
+
counts[i]++;
|
|
2124
|
+
assigned = true;
|
|
2125
|
+
break;
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
if (!assigned) {
|
|
2129
|
+
counts[bins.length]++;
|
|
2130
|
+
}
|
|
2131
|
+
}
|
|
2132
|
+
for (const c of counts) {
|
|
2133
|
+
result.push([rvNumber(c)]);
|
|
2134
|
+
}
|
|
2135
|
+
return rvArray(result);
|
|
2136
|
+
};
|
|
2137
|
+
/** Narrow `T | ErrorValue` to `ErrorValue`. */
|
|
2138
|
+
function isErr(v) {
|
|
2139
|
+
return v.kind === RVKind.Error;
|
|
2140
|
+
}
|
|
2141
|
+
function extractMatrix(arg) {
|
|
2142
|
+
if (arg.kind !== RVKind.Array) {
|
|
2143
|
+
const sv = topLeft(arg);
|
|
2144
|
+
if (sv.kind === RVKind.Error) {
|
|
2145
|
+
return sv;
|
|
2146
|
+
}
|
|
2147
|
+
const n = toNumberRV(sv);
|
|
2148
|
+
if (n.kind === RVKind.Error) {
|
|
2149
|
+
return n;
|
|
2150
|
+
}
|
|
2151
|
+
return { m: { data: [[n.value]], rows: 1, cols: 1 }, orient: "row" };
|
|
2152
|
+
}
|
|
2153
|
+
const data = [];
|
|
2154
|
+
for (const row of arg.rows) {
|
|
2155
|
+
const out = [];
|
|
2156
|
+
for (const cell of row) {
|
|
2157
|
+
if (cell.kind === RVKind.Error) {
|
|
2158
|
+
return cell;
|
|
2159
|
+
}
|
|
2160
|
+
if (cell.kind === RVKind.Number) {
|
|
2161
|
+
out.push(cell.value);
|
|
2162
|
+
}
|
|
2163
|
+
else if (cell.kind === RVKind.Blank) {
|
|
2164
|
+
return ERRORS.VALUE;
|
|
2165
|
+
}
|
|
2166
|
+
else {
|
|
2167
|
+
const n = toNumberRV(cell);
|
|
2168
|
+
if (n.kind === RVKind.Error) {
|
|
2169
|
+
return n;
|
|
2170
|
+
}
|
|
2171
|
+
out.push(n.value);
|
|
2172
|
+
}
|
|
2173
|
+
}
|
|
2174
|
+
data.push(out);
|
|
2175
|
+
}
|
|
2176
|
+
const rows = arg.height;
|
|
2177
|
+
const cols = arg.width;
|
|
2178
|
+
const orient = rows === 1 ? "row" : cols === 1 ? "col" : "matrix";
|
|
2179
|
+
return { m: { data, rows, cols }, orient };
|
|
2180
|
+
}
|
|
2181
|
+
/**
|
|
2182
|
+
* Normalize the `known_x's` argument to a design matrix X of shape [n, k],
|
|
2183
|
+
* where n is the number of observations and k is the number of predictor
|
|
2184
|
+
* variables. Excel infers k from the orientation of known_x's: when y is a
|
|
2185
|
+
* column vector (or square), each column of known_x's is one predictor; when
|
|
2186
|
+
* y is a row vector, each row of known_x's is one predictor.
|
|
2187
|
+
*/
|
|
2188
|
+
function buildDesignMatrix(m, nObs, yOrient) {
|
|
2189
|
+
// Decide orientation of predictors based on y's orientation and matrix shape
|
|
2190
|
+
let byRows;
|
|
2191
|
+
let xOrient;
|
|
2192
|
+
if (m.rows === 1) {
|
|
2193
|
+
// Row vector of length n: single predictor, one observation per column
|
|
2194
|
+
byRows = false;
|
|
2195
|
+
xOrient = "row";
|
|
2196
|
+
}
|
|
2197
|
+
else if (m.cols === 1) {
|
|
2198
|
+
// Column vector of length n: single predictor, one observation per row
|
|
2199
|
+
byRows = true;
|
|
2200
|
+
xOrient = "col";
|
|
2201
|
+
}
|
|
2202
|
+
else if (yOrient === "row") {
|
|
2203
|
+
// y is a row vector: predictors are rows of known_x's
|
|
2204
|
+
byRows = false;
|
|
2205
|
+
xOrient = "row";
|
|
2206
|
+
}
|
|
2207
|
+
else {
|
|
2208
|
+
// y is a column/matrix: predictors are columns of known_x's
|
|
2209
|
+
byRows = true;
|
|
2210
|
+
xOrient = "col";
|
|
2211
|
+
}
|
|
2212
|
+
const n = byRows ? m.rows : m.cols;
|
|
2213
|
+
const k = byRows ? m.cols : m.rows;
|
|
2214
|
+
if (n !== nObs) {
|
|
2215
|
+
return ERRORS.REF;
|
|
2216
|
+
}
|
|
2217
|
+
const X = [];
|
|
2218
|
+
for (let i = 0; i < n; i++) {
|
|
2219
|
+
const row = [];
|
|
2220
|
+
for (let j = 0; j < k; j++) {
|
|
2221
|
+
row.push(byRows ? m.data[i][j] : m.data[j][i]);
|
|
2222
|
+
}
|
|
2223
|
+
X.push(row);
|
|
2224
|
+
}
|
|
2225
|
+
return { X, k, xOrient };
|
|
2226
|
+
}
|
|
2227
|
+
/** Solve (A^T A) β = A^T b via Gauss–Jordan on the augmented matrix. */
|
|
2228
|
+
function solveNormalEquations(A, b) {
|
|
2229
|
+
const n = A.length;
|
|
2230
|
+
const k = A[0]?.length ?? 0;
|
|
2231
|
+
// Build augmented [AᵀA | Aᵀb]
|
|
2232
|
+
const aug = Array.from({ length: k }, () => new Array(k + 1).fill(0));
|
|
2233
|
+
for (let i = 0; i < k; i++) {
|
|
2234
|
+
for (let j = 0; j < k; j++) {
|
|
2235
|
+
let s = 0;
|
|
2236
|
+
for (let r = 0; r < n; r++) {
|
|
2237
|
+
s += A[r][i] * A[r][j];
|
|
2238
|
+
}
|
|
2239
|
+
aug[i][j] = s;
|
|
2240
|
+
}
|
|
2241
|
+
let sb = 0;
|
|
2242
|
+
for (let r = 0; r < n; r++) {
|
|
2243
|
+
sb += A[r][i] * b[r];
|
|
2244
|
+
}
|
|
2245
|
+
aug[i][k] = sb;
|
|
2246
|
+
}
|
|
2247
|
+
// Gauss–Jordan with partial pivoting
|
|
2248
|
+
for (let i = 0; i < k; i++) {
|
|
2249
|
+
let piv = i;
|
|
2250
|
+
for (let r = i + 1; r < k; r++) {
|
|
2251
|
+
if (Math.abs(aug[r][i]) > Math.abs(aug[piv][i])) {
|
|
2252
|
+
piv = r;
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
if (Math.abs(aug[piv][i]) < 1e-12) {
|
|
2256
|
+
return null;
|
|
2257
|
+
}
|
|
2258
|
+
if (piv !== i) {
|
|
2259
|
+
[aug[i], aug[piv]] = [aug[piv], aug[i]];
|
|
2260
|
+
}
|
|
2261
|
+
const d = aug[i][i];
|
|
2262
|
+
for (let j = i; j <= k; j++) {
|
|
2263
|
+
aug[i][j] /= d;
|
|
2264
|
+
}
|
|
2265
|
+
for (let r = 0; r < k; r++) {
|
|
2266
|
+
if (r === i) {
|
|
2267
|
+
continue;
|
|
2268
|
+
}
|
|
2269
|
+
const f = aug[r][i];
|
|
2270
|
+
if (f === 0) {
|
|
2271
|
+
continue;
|
|
2272
|
+
}
|
|
2273
|
+
for (let j = i; j <= k; j++) {
|
|
2274
|
+
aug[r][j] -= f * aug[i][j];
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
return aug.map(row => row[k]);
|
|
2279
|
+
}
|
|
2280
|
+
/** Compute inverse of k×k symmetric positive-definite matrix via Gauss–Jordan. */
|
|
2281
|
+
function invertSquareMatrix(M) {
|
|
2282
|
+
const k = M.length;
|
|
2283
|
+
const aug = Array.from({ length: k }, (_, i) => {
|
|
2284
|
+
const row = new Array(2 * k).fill(0);
|
|
2285
|
+
for (let j = 0; j < k; j++) {
|
|
2286
|
+
row[j] = M[i][j];
|
|
2287
|
+
}
|
|
2288
|
+
row[k + i] = 1;
|
|
2289
|
+
return row;
|
|
2290
|
+
});
|
|
2291
|
+
for (let i = 0; i < k; i++) {
|
|
2292
|
+
let piv = i;
|
|
2293
|
+
for (let r = i + 1; r < k; r++) {
|
|
2294
|
+
if (Math.abs(aug[r][i]) > Math.abs(aug[piv][i])) {
|
|
2295
|
+
piv = r;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
if (Math.abs(aug[piv][i]) < 1e-12) {
|
|
2299
|
+
return null;
|
|
2300
|
+
}
|
|
2301
|
+
if (piv !== i) {
|
|
2302
|
+
[aug[i], aug[piv]] = [aug[piv], aug[i]];
|
|
2303
|
+
}
|
|
2304
|
+
const d = aug[i][i];
|
|
2305
|
+
for (let j = 0; j < 2 * k; j++) {
|
|
2306
|
+
aug[i][j] /= d;
|
|
2307
|
+
}
|
|
2308
|
+
for (let r = 0; r < k; r++) {
|
|
2309
|
+
if (r === i) {
|
|
2310
|
+
continue;
|
|
2311
|
+
}
|
|
2312
|
+
const f = aug[r][i];
|
|
2313
|
+
if (f === 0) {
|
|
2314
|
+
continue;
|
|
2315
|
+
}
|
|
2316
|
+
for (let j = 0; j < 2 * k; j++) {
|
|
2317
|
+
aug[r][j] -= f * aug[i][j];
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
}
|
|
2321
|
+
return aug.map(row => row.slice(k));
|
|
2322
|
+
}
|
|
2323
|
+
function runLeastSquares(input, withStats) {
|
|
2324
|
+
const { includeIntercept, logMode } = input;
|
|
2325
|
+
const n = input.y.length;
|
|
2326
|
+
const k = input.X[0]?.length ?? 0;
|
|
2327
|
+
if (n < 1 || k < 1) {
|
|
2328
|
+
return ERRORS.VALUE;
|
|
2329
|
+
}
|
|
2330
|
+
// y in log domain for LOGEST/GROWTH
|
|
2331
|
+
const y = new Array(n);
|
|
2332
|
+
for (let i = 0; i < n; i++) {
|
|
2333
|
+
const yi = input.y[i];
|
|
2334
|
+
if (logMode) {
|
|
2335
|
+
if (yi <= 0) {
|
|
2336
|
+
return ERRORS.NUM;
|
|
2337
|
+
}
|
|
2338
|
+
y[i] = Math.log(yi);
|
|
2339
|
+
}
|
|
2340
|
+
else {
|
|
2341
|
+
y[i] = yi;
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
// Augment X with intercept column (as the last column → Excel puts b at the end)
|
|
2345
|
+
const kAug = includeIntercept ? k + 1 : k;
|
|
2346
|
+
const A = new Array(n);
|
|
2347
|
+
for (let i = 0; i < n; i++) {
|
|
2348
|
+
const row = new Array(kAug);
|
|
2349
|
+
for (let j = 0; j < k; j++) {
|
|
2350
|
+
row[j] = input.X[i][j];
|
|
2351
|
+
}
|
|
2352
|
+
if (includeIntercept) {
|
|
2353
|
+
row[k] = 1;
|
|
2354
|
+
}
|
|
2355
|
+
A[i] = row;
|
|
2356
|
+
}
|
|
2357
|
+
const beta = solveNormalEquations(A, y);
|
|
2358
|
+
if (beta === null) {
|
|
2359
|
+
return ERRORS.NUM;
|
|
2360
|
+
}
|
|
2361
|
+
// Excel orders coefficients as [mk, m(k-1), ..., m1, b] — reverse the slope
|
|
2362
|
+
// portion but keep the intercept last.
|
|
2363
|
+
const coeffs = [];
|
|
2364
|
+
for (let j = k - 1; j >= 0; j--) {
|
|
2365
|
+
coeffs.push(beta[j]);
|
|
2366
|
+
}
|
|
2367
|
+
if (includeIntercept) {
|
|
2368
|
+
coeffs.push(beta[k]);
|
|
2369
|
+
}
|
|
2370
|
+
else {
|
|
2371
|
+
coeffs.push(0);
|
|
2372
|
+
}
|
|
2373
|
+
if (!withStats) {
|
|
2374
|
+
return { coeffs };
|
|
2375
|
+
}
|
|
2376
|
+
// Residuals, sums of squares
|
|
2377
|
+
const yhat = new Array(n);
|
|
2378
|
+
for (let i = 0; i < n; i++) {
|
|
2379
|
+
let s = 0;
|
|
2380
|
+
for (let j = 0; j < kAug; j++) {
|
|
2381
|
+
s += A[i][j] * beta[j];
|
|
2382
|
+
}
|
|
2383
|
+
yhat[i] = s;
|
|
2384
|
+
}
|
|
2385
|
+
let ybar = 0;
|
|
2386
|
+
if (includeIntercept) {
|
|
2387
|
+
for (const v of y) {
|
|
2388
|
+
ybar += v;
|
|
2389
|
+
}
|
|
2390
|
+
ybar /= n;
|
|
2391
|
+
}
|
|
2392
|
+
let ssResid = 0, ssTot = 0, ssReg = 0;
|
|
2393
|
+
for (let i = 0; i < n; i++) {
|
|
2394
|
+
ssResid += (y[i] - yhat[i]) ** 2;
|
|
2395
|
+
ssTot += (y[i] - ybar) ** 2;
|
|
2396
|
+
ssReg += (yhat[i] - ybar) ** 2;
|
|
2397
|
+
}
|
|
2398
|
+
const df = n - kAug;
|
|
2399
|
+
const r2 = ssTot === 0 ? 1 : 1 - ssResid / ssTot;
|
|
2400
|
+
const sey = df > 0 ? Math.sqrt(ssResid / df) : 0;
|
|
2401
|
+
const fStat = df > 0 && ssResid > 0 ? ssReg / k / (ssResid / df) : Number.POSITIVE_INFINITY;
|
|
2402
|
+
// Standard errors: diag(sey² · (AᵀA)⁻¹)
|
|
2403
|
+
const AtA = Array.from({ length: kAug }, () => new Array(kAug).fill(0));
|
|
2404
|
+
for (let i = 0; i < kAug; i++) {
|
|
2405
|
+
for (let j = 0; j < kAug; j++) {
|
|
2406
|
+
let s = 0;
|
|
2407
|
+
for (let r = 0; r < n; r++) {
|
|
2408
|
+
s += A[r][i] * A[r][j];
|
|
2409
|
+
}
|
|
2410
|
+
AtA[i][j] = s;
|
|
2411
|
+
}
|
|
2412
|
+
}
|
|
2413
|
+
const inv = invertSquareMatrix(AtA);
|
|
2414
|
+
const seCoeffs = [];
|
|
2415
|
+
if (inv) {
|
|
2416
|
+
for (let j = k - 1; j >= 0; j--) {
|
|
2417
|
+
const v = sey * sey * inv[j][j];
|
|
2418
|
+
seCoeffs.push(v >= 0 ? Math.sqrt(v) : 0);
|
|
2419
|
+
}
|
|
2420
|
+
if (includeIntercept) {
|
|
2421
|
+
const v = sey * sey * inv[k][k];
|
|
2422
|
+
seCoeffs.push(v >= 0 ? Math.sqrt(v) : 0);
|
|
2423
|
+
}
|
|
2424
|
+
else {
|
|
2425
|
+
seCoeffs.push(0);
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
else {
|
|
2429
|
+
// Cannot invert — fill with NaN per Excel behavior (but we use 0 since
|
|
2430
|
+
// downstream consumers expect numbers).
|
|
2431
|
+
for (let j = 0; j <= k; j++) {
|
|
2432
|
+
seCoeffs.push(0);
|
|
2433
|
+
}
|
|
2434
|
+
}
|
|
2435
|
+
return { coeffs, seCoeffs, r2, sey, fStat, df, ssReg, ssResid };
|
|
2436
|
+
}
|
|
2437
|
+
/** Build Excel's 5×(k+1) LINEST stats block. */
|
|
2438
|
+
function buildStatsBlock(res) {
|
|
2439
|
+
const kPlus1 = res.coeffs.length;
|
|
2440
|
+
const row1 = res.coeffs.map(v => rvNumber(v));
|
|
2441
|
+
const row2 = (res.seCoeffs ?? []).map(v => rvNumber(v));
|
|
2442
|
+
// Row 3: r² in col 0, sey in col 1, then #N/A for k+1 ≥ 3
|
|
2443
|
+
const row3 = new Array(kPlus1).fill(ERRORS.NA);
|
|
2444
|
+
row3[0] = rvNumber(res.r2 ?? 0);
|
|
2445
|
+
if (kPlus1 >= 2) {
|
|
2446
|
+
row3[1] = rvNumber(res.sey ?? 0);
|
|
2447
|
+
}
|
|
2448
|
+
// Row 4: F in col 0, df in col 1, then #N/A
|
|
2449
|
+
const row4 = new Array(kPlus1).fill(ERRORS.NA);
|
|
2450
|
+
row4[0] = rvNumber(res.fStat ?? 0);
|
|
2451
|
+
if (kPlus1 >= 2) {
|
|
2452
|
+
row4[1] = rvNumber(res.df ?? 0);
|
|
2453
|
+
}
|
|
2454
|
+
// Row 5: ssReg in col 0, ssResid in col 1, then #N/A
|
|
2455
|
+
const row5 = new Array(kPlus1).fill(ERRORS.NA);
|
|
2456
|
+
row5[0] = rvNumber(res.ssReg ?? 0);
|
|
2457
|
+
if (kPlus1 >= 2) {
|
|
2458
|
+
row5[1] = rvNumber(res.ssResid ?? 0);
|
|
2459
|
+
}
|
|
2460
|
+
return [row1, row2, row3, row4, row5];
|
|
2461
|
+
}
|
|
2462
|
+
/** Parse known_y / known_x from args, returning design matrix and orientation info. */
|
|
2463
|
+
function parseRegressionInputs(args) {
|
|
2464
|
+
if (!args[0]) {
|
|
2465
|
+
return ERRORS.VALUE;
|
|
2466
|
+
}
|
|
2467
|
+
const yInfo = extractMatrix(args[0]);
|
|
2468
|
+
if (isErr(yInfo)) {
|
|
2469
|
+
return yInfo;
|
|
2470
|
+
}
|
|
2471
|
+
const y = [];
|
|
2472
|
+
for (const row of yInfo.m.data) {
|
|
2473
|
+
for (const v of row) {
|
|
2474
|
+
y.push(v);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
const nObs = y.length;
|
|
2478
|
+
if (nObs < 1) {
|
|
2479
|
+
return ERRORS.VALUE;
|
|
2480
|
+
}
|
|
2481
|
+
if (args.length > 1 && args[1].kind !== RVKind.Blank) {
|
|
2482
|
+
const xInfo = extractMatrix(args[1]);
|
|
2483
|
+
if (isErr(xInfo)) {
|
|
2484
|
+
return xInfo;
|
|
2485
|
+
}
|
|
2486
|
+
const built = buildDesignMatrix(xInfo.m, nObs, yInfo.orient);
|
|
2487
|
+
if (isErr(built)) {
|
|
2488
|
+
return built;
|
|
2489
|
+
}
|
|
2490
|
+
return { y, X: built.X, k: built.k, yOrient: yInfo.orient, xOrient: built.xOrient };
|
|
2491
|
+
}
|
|
2492
|
+
// Default known_x's = 1..n as a column vector
|
|
2493
|
+
const X = y.map((_, i) => [i + 1]);
|
|
2494
|
+
return { y, X, k: 1, yOrient: yInfo.orient, xOrient: yInfo.orient };
|
|
2495
|
+
}
|
|
2496
|
+
/** Parse new_x (third arg of TREND/GROWTH). Returns new-X matrix [m, k] and output orientation. */
|
|
2497
|
+
function parseNewX(arg, fallback, k, xOrient) {
|
|
2498
|
+
if (!arg || arg.kind === RVKind.Blank) {
|
|
2499
|
+
return { X: fallback, outOrient: xOrient };
|
|
2500
|
+
}
|
|
2501
|
+
const info = extractMatrix(arg);
|
|
2502
|
+
if (isErr(info)) {
|
|
2503
|
+
return info;
|
|
2504
|
+
}
|
|
2505
|
+
const m = info.m;
|
|
2506
|
+
// Determine orientation: need one of the dimensions to equal k (the number of predictors)
|
|
2507
|
+
if (k === 1) {
|
|
2508
|
+
// Single predictor → flatten whatever shape was given
|
|
2509
|
+
const X = [];
|
|
2510
|
+
for (const row of m.data) {
|
|
2511
|
+
for (const v of row) {
|
|
2512
|
+
X.push([v]);
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
const outOrient = m.rows === 1 ? "row" : "col";
|
|
2516
|
+
return { X, outOrient };
|
|
2517
|
+
}
|
|
2518
|
+
// Multi-predictor: match orientation with known_x's
|
|
2519
|
+
if (xOrient === "col" || xOrient === "matrix") {
|
|
2520
|
+
// Each row is one observation with k columns of predictors
|
|
2521
|
+
if (m.cols !== k) {
|
|
2522
|
+
return ERRORS.REF;
|
|
2523
|
+
}
|
|
2524
|
+
return { X: m.data.map(r => r.slice()), outOrient: "col" };
|
|
2525
|
+
}
|
|
2526
|
+
// xOrient === "row": each column is one observation
|
|
2527
|
+
if (m.rows !== k) {
|
|
2528
|
+
return ERRORS.REF;
|
|
2529
|
+
}
|
|
2530
|
+
const X = [];
|
|
2531
|
+
for (let c = 0; c < m.cols; c++) {
|
|
2532
|
+
const row = [];
|
|
2533
|
+
for (let r = 0; r < k; r++) {
|
|
2534
|
+
row.push(m.data[r][c]);
|
|
2535
|
+
}
|
|
2536
|
+
X.push(row);
|
|
2537
|
+
}
|
|
2538
|
+
return { X, outOrient: "row" };
|
|
2539
|
+
}
|
|
2540
|
+
/** Emit an array of predictions matching the requested output orientation. */
|
|
2541
|
+
function emitPredictions(values, outOrient) {
|
|
2542
|
+
if (outOrient === "row") {
|
|
2543
|
+
return rvArray([values.map(v => rvNumber(v))]);
|
|
2544
|
+
}
|
|
2545
|
+
return rvArray(values.map(v => [rvNumber(v)]));
|
|
2546
|
+
}
|
|
2547
|
+
export const fnGROWTH = args => {
|
|
2548
|
+
const parsed = parseRegressionInputs(args);
|
|
2549
|
+
if (isErr(parsed)) {
|
|
2550
|
+
return parsed;
|
|
2551
|
+
}
|
|
2552
|
+
const { y, X, k, xOrient } = parsed;
|
|
2553
|
+
const lsq = runLeastSquares({ y, X, includeIntercept: true, logMode: true }, false);
|
|
2554
|
+
if (isErr(lsq)) {
|
|
2555
|
+
return lsq;
|
|
2556
|
+
}
|
|
2557
|
+
// coeffs order: [mk, ..., m1, b]; slopes in log space correspond to positions [0..k-1]
|
|
2558
|
+
const b = lsq.coeffs[k];
|
|
2559
|
+
const slopes = new Array(k);
|
|
2560
|
+
for (let j = 0; j < k; j++) {
|
|
2561
|
+
slopes[j] = lsq.coeffs[k - 1 - j];
|
|
2562
|
+
} // m1..mk
|
|
2563
|
+
const newInfo = parseNewX(args[2], X, k, xOrient);
|
|
2564
|
+
if (isErr(newInfo)) {
|
|
2565
|
+
return newInfo;
|
|
2566
|
+
}
|
|
2567
|
+
const preds = newInfo.X.map(row => {
|
|
2568
|
+
let lp = b;
|
|
2569
|
+
for (let j = 0; j < k; j++) {
|
|
2570
|
+
lp += slopes[j] * row[j];
|
|
2571
|
+
}
|
|
2572
|
+
return Math.exp(lp);
|
|
2573
|
+
});
|
|
2574
|
+
return emitPredictions(preds, newInfo.outOrient);
|
|
2575
|
+
};
|
|
2576
|
+
export const fnTREND = args => {
|
|
2577
|
+
const parsed = parseRegressionInputs(args);
|
|
2578
|
+
if (isErr(parsed)) {
|
|
2579
|
+
return parsed;
|
|
2580
|
+
}
|
|
2581
|
+
const { y, X, k, xOrient } = parsed;
|
|
2582
|
+
const lsq = runLeastSquares({ y, X, includeIntercept: true, logMode: false }, false);
|
|
2583
|
+
if (isErr(lsq)) {
|
|
2584
|
+
return lsq;
|
|
2585
|
+
}
|
|
2586
|
+
const b = lsq.coeffs[k];
|
|
2587
|
+
const slopes = new Array(k);
|
|
2588
|
+
for (let j = 0; j < k; j++) {
|
|
2589
|
+
slopes[j] = lsq.coeffs[k - 1 - j];
|
|
2590
|
+
}
|
|
2591
|
+
const newInfo = parseNewX(args[2], X, k, xOrient);
|
|
2592
|
+
if (isErr(newInfo)) {
|
|
2593
|
+
return newInfo;
|
|
2594
|
+
}
|
|
2595
|
+
const preds = newInfo.X.map(row => {
|
|
2596
|
+
let p = b;
|
|
2597
|
+
for (let j = 0; j < k; j++) {
|
|
2598
|
+
p += slopes[j] * row[j];
|
|
2599
|
+
}
|
|
2600
|
+
return p;
|
|
2601
|
+
});
|
|
2602
|
+
return emitPredictions(preds, newInfo.outOrient);
|
|
2603
|
+
};
|
|
2604
|
+
export const fnLINEST = args => {
|
|
2605
|
+
const parsed = parseRegressionInputs(args);
|
|
2606
|
+
if (isErr(parsed)) {
|
|
2607
|
+
return parsed;
|
|
2608
|
+
}
|
|
2609
|
+
const { y, X } = parsed;
|
|
2610
|
+
// 3rd arg: const (default TRUE — include intercept). FALSE → force intercept = 0.
|
|
2611
|
+
let includeIntercept = true;
|
|
2612
|
+
if (args.length > 2 && args[2].kind !== RVKind.Blank) {
|
|
2613
|
+
const b = toBooleanRV(topLeft(args[2]));
|
|
2614
|
+
if (b.kind === RVKind.Error) {
|
|
2615
|
+
return b;
|
|
2616
|
+
}
|
|
2617
|
+
includeIntercept = b.value;
|
|
2618
|
+
}
|
|
2619
|
+
// 4th arg: stats (default FALSE).
|
|
2620
|
+
let withStats = false;
|
|
2621
|
+
if (args.length > 3 && args[3].kind !== RVKind.Blank) {
|
|
2622
|
+
const b = toBooleanRV(topLeft(args[3]));
|
|
2623
|
+
if (b.kind === RVKind.Error) {
|
|
2624
|
+
return b;
|
|
2625
|
+
}
|
|
2626
|
+
withStats = b.value;
|
|
2627
|
+
}
|
|
2628
|
+
const lsq = runLeastSquares({ y, X, includeIntercept, logMode: false }, withStats);
|
|
2629
|
+
if (isErr(lsq)) {
|
|
2630
|
+
return lsq;
|
|
2631
|
+
}
|
|
2632
|
+
if (withStats) {
|
|
2633
|
+
return rvArray(buildStatsBlock(lsq));
|
|
2634
|
+
}
|
|
2635
|
+
return rvArray([lsq.coeffs.map(v => rvNumber(v))]);
|
|
2636
|
+
};
|
|
2637
|
+
export const fnLOGEST = args => {
|
|
2638
|
+
const parsed = parseRegressionInputs(args);
|
|
2639
|
+
if (isErr(parsed)) {
|
|
2640
|
+
return parsed;
|
|
2641
|
+
}
|
|
2642
|
+
const { y, X } = parsed;
|
|
2643
|
+
let includeIntercept = true;
|
|
2644
|
+
if (args.length > 2 && args[2].kind !== RVKind.Blank) {
|
|
2645
|
+
const b = toBooleanRV(topLeft(args[2]));
|
|
2646
|
+
if (b.kind === RVKind.Error) {
|
|
2647
|
+
return b;
|
|
2648
|
+
}
|
|
2649
|
+
includeIntercept = b.value;
|
|
2650
|
+
}
|
|
2651
|
+
let withStats = false;
|
|
2652
|
+
if (args.length > 3 && args[3].kind !== RVKind.Blank) {
|
|
2653
|
+
const b = toBooleanRV(topLeft(args[3]));
|
|
2654
|
+
if (b.kind === RVKind.Error) {
|
|
2655
|
+
return b;
|
|
2656
|
+
}
|
|
2657
|
+
withStats = b.value;
|
|
2658
|
+
}
|
|
2659
|
+
const lsq = runLeastSquares({ y, X, includeIntercept, logMode: true }, withStats);
|
|
2660
|
+
if (isErr(lsq)) {
|
|
2661
|
+
return lsq;
|
|
2662
|
+
}
|
|
2663
|
+
if (withStats) {
|
|
2664
|
+
// LOGEST reports exp-transformed slopes/intercept in row 1 but keeps rows 2-5 as LINEST
|
|
2665
|
+
const block = buildStatsBlock(lsq);
|
|
2666
|
+
block[0] = lsq.coeffs.map(v => rvNumber(Math.exp(v)));
|
|
2667
|
+
return rvArray(block);
|
|
2668
|
+
}
|
|
2669
|
+
return rvArray([lsq.coeffs.map(v => rvNumber(Math.exp(v)))]);
|
|
2670
|
+
};
|
|
2671
|
+
// ============================================================================
|
|
2672
|
+
// F.DIST.RT, F.INV.RT — F-distribution right tail
|
|
2673
|
+
// ============================================================================
|
|
2674
|
+
/**
|
|
2675
|
+
* F.DIST.RT(x, d1, d2) — right-tail probability of the F-distribution.
|
|
2676
|
+
*
|
|
2677
|
+
* Equivalent to `1 - F.DIST(x, d1, d2, TRUE)`. Using the symmetry of the
|
|
2678
|
+
* regularized incomplete beta function, this can be expressed as
|
|
2679
|
+
* `I(d2/(d2 + d1*x), d2/2, d1/2)`, which avoids subtracting from 1 and
|
|
2680
|
+
* is numerically stable in the upper tail.
|
|
2681
|
+
*/
|
|
2682
|
+
export const fnF_DIST_RT = args => {
|
|
2683
|
+
const x = argToNumber(args[0]);
|
|
2684
|
+
if (x.kind === RVKind.Error) {
|
|
2685
|
+
return x;
|
|
2686
|
+
}
|
|
2687
|
+
const df1 = argToNumber(args[1]);
|
|
2688
|
+
if (df1.kind === RVKind.Error) {
|
|
2689
|
+
return df1;
|
|
2690
|
+
}
|
|
2691
|
+
const df2 = argToNumber(args[2]);
|
|
2692
|
+
if (df2.kind === RVKind.Error) {
|
|
2693
|
+
return df2;
|
|
2694
|
+
}
|
|
2695
|
+
if (x.value < 0 || df1.value < 1 || df2.value < 1) {
|
|
2696
|
+
return ERRORS.NUM;
|
|
2697
|
+
}
|
|
2698
|
+
// At x=0 the right tail equals 1 (entire distribution above 0).
|
|
2699
|
+
if (x.value === 0) {
|
|
2700
|
+
return rvNumber(1);
|
|
2701
|
+
}
|
|
2702
|
+
const d1 = df1.value;
|
|
2703
|
+
const d2 = df2.value;
|
|
2704
|
+
// Right tail via symmetry: I(d2/(d2 + d1*x), d2/2, d1/2).
|
|
2705
|
+
return rvNumber(betaIncomplete(d2 / (d2 + d1 * x.value), d2 / 2, d1 / 2));
|
|
2706
|
+
};
|
|
2707
|
+
/**
|
|
2708
|
+
* F.INV.RT(p, d1, d2) — inverse right-tail of the F-distribution.
|
|
2709
|
+
* Returns x such that P(F > x) = p. Implemented via binary search on the
|
|
2710
|
+
* right-tail CDF (monotonically decreasing from 1 at x=0 to 0 at x=∞).
|
|
2711
|
+
*/
|
|
2712
|
+
export const fnF_INV_RT = args => {
|
|
2713
|
+
const p = argToNumber(args[0]);
|
|
2714
|
+
if (p.kind === RVKind.Error) {
|
|
2715
|
+
return p;
|
|
2716
|
+
}
|
|
2717
|
+
const df1 = argToNumber(args[1]);
|
|
2718
|
+
if (df1.kind === RVKind.Error) {
|
|
2719
|
+
return df1;
|
|
2720
|
+
}
|
|
2721
|
+
const df2 = argToNumber(args[2]);
|
|
2722
|
+
if (df2.kind === RVKind.Error) {
|
|
2723
|
+
return df2;
|
|
2724
|
+
}
|
|
2725
|
+
if (p.value <= 0 || p.value > 1 || df1.value < 1 || df2.value < 1) {
|
|
2726
|
+
return ERRORS.NUM;
|
|
2727
|
+
}
|
|
2728
|
+
const d1 = df1.value;
|
|
2729
|
+
const d2 = df2.value;
|
|
2730
|
+
// Right-tail CDF at x: I(d2/(d2 + d1*x), d2/2, d1/2), decreases with x.
|
|
2731
|
+
let lo = 0;
|
|
2732
|
+
let hi = 1e6;
|
|
2733
|
+
for (let i = 0; i < 200; i++) {
|
|
2734
|
+
const mid = (lo + hi) / 2;
|
|
2735
|
+
const rt = betaIncomplete(d2 / (d2 + d1 * mid), d2 / 2, d1 / 2);
|
|
2736
|
+
if (rt > p.value) {
|
|
2737
|
+
lo = mid;
|
|
2738
|
+
}
|
|
2739
|
+
else {
|
|
2740
|
+
hi = mid;
|
|
2741
|
+
}
|
|
2742
|
+
if (hi - lo < 1e-10) {
|
|
2743
|
+
break;
|
|
2744
|
+
}
|
|
2745
|
+
}
|
|
2746
|
+
return rvNumber((lo + hi) / 2);
|
|
2747
|
+
};
|
|
2748
|
+
// ============================================================================
|
|
2749
|
+
// SKEW, SKEW.P, KURT
|
|
2750
|
+
// ============================================================================
|
|
2751
|
+
/**
|
|
2752
|
+
* SKEW — sample skewness.
|
|
2753
|
+
* Formula: n / ((n-1)(n-2)) * Σ((xi-mean)/s)^3, where s is the sample stdev.
|
|
2754
|
+
*/
|
|
2755
|
+
export const fnSKEW = args => {
|
|
2756
|
+
const nums = flattenNumbers(args);
|
|
2757
|
+
const err = firstError(nums);
|
|
2758
|
+
if (err) {
|
|
2759
|
+
return err;
|
|
2760
|
+
}
|
|
2761
|
+
const xs = nums.map(n => n.value);
|
|
2762
|
+
const n = xs.length;
|
|
2763
|
+
if (n < 3) {
|
|
2764
|
+
return ERRORS.DIV0;
|
|
2765
|
+
}
|
|
2766
|
+
let sum = 0;
|
|
2767
|
+
for (const v of xs) {
|
|
2768
|
+
sum += v;
|
|
2769
|
+
}
|
|
2770
|
+
const mean = sum / n;
|
|
2771
|
+
let sumSq = 0;
|
|
2772
|
+
for (const v of xs) {
|
|
2773
|
+
sumSq += (v - mean) ** 2;
|
|
2774
|
+
}
|
|
2775
|
+
const sampleStd = Math.sqrt(sumSq / (n - 1));
|
|
2776
|
+
if (sampleStd === 0) {
|
|
2777
|
+
return ERRORS.DIV0;
|
|
2778
|
+
}
|
|
2779
|
+
let sumCubed = 0;
|
|
2780
|
+
for (const v of xs) {
|
|
2781
|
+
sumCubed += ((v - mean) / sampleStd) ** 3;
|
|
2782
|
+
}
|
|
2783
|
+
return rvNumber((n / ((n - 1) * (n - 2))) * sumCubed);
|
|
2784
|
+
};
|
|
2785
|
+
/**
|
|
2786
|
+
* SKEW.P — population skewness.
|
|
2787
|
+
* Formula: (1/n) * Σ((xi-mean)/σ)^3, where σ is the population stdev.
|
|
2788
|
+
*/
|
|
2789
|
+
export const fnSKEW_P = args => {
|
|
2790
|
+
const nums = flattenNumbers(args);
|
|
2791
|
+
const err = firstError(nums);
|
|
2792
|
+
if (err) {
|
|
2793
|
+
return err;
|
|
2794
|
+
}
|
|
2795
|
+
const xs = nums.map(n => n.value);
|
|
2796
|
+
const n = xs.length;
|
|
2797
|
+
if (n < 1) {
|
|
2798
|
+
return ERRORS.DIV0;
|
|
2799
|
+
}
|
|
2800
|
+
let sum = 0;
|
|
2801
|
+
for (const v of xs) {
|
|
2802
|
+
sum += v;
|
|
2803
|
+
}
|
|
2804
|
+
const mean = sum / n;
|
|
2805
|
+
let sumSq = 0;
|
|
2806
|
+
for (const v of xs) {
|
|
2807
|
+
sumSq += (v - mean) ** 2;
|
|
2808
|
+
}
|
|
2809
|
+
const popStd = Math.sqrt(sumSq / n);
|
|
2810
|
+
if (popStd === 0) {
|
|
2811
|
+
return ERRORS.DIV0;
|
|
2812
|
+
}
|
|
2813
|
+
let sumCubed = 0;
|
|
2814
|
+
for (const v of xs) {
|
|
2815
|
+
sumCubed += ((v - mean) / popStd) ** 3;
|
|
2816
|
+
}
|
|
2817
|
+
return rvNumber(sumCubed / n);
|
|
2818
|
+
};
|
|
2819
|
+
/**
|
|
2820
|
+
* KURT — sample excess kurtosis.
|
|
2821
|
+
* Formula: n(n+1) / ((n-1)(n-2)(n-3)) * Σ((xi-mean)/s)^4 - 3(n-1)^2 / ((n-2)(n-3)).
|
|
2822
|
+
*/
|
|
2823
|
+
export const fnKURT = args => {
|
|
2824
|
+
const nums = flattenNumbers(args);
|
|
2825
|
+
const err = firstError(nums);
|
|
2826
|
+
if (err) {
|
|
2827
|
+
return err;
|
|
2828
|
+
}
|
|
2829
|
+
const xs = nums.map(n => n.value);
|
|
2830
|
+
const n = xs.length;
|
|
2831
|
+
if (n < 4) {
|
|
2832
|
+
return ERRORS.DIV0;
|
|
2833
|
+
}
|
|
2834
|
+
let sum = 0;
|
|
2835
|
+
for (const v of xs) {
|
|
2836
|
+
sum += v;
|
|
2837
|
+
}
|
|
2838
|
+
const mean = sum / n;
|
|
2839
|
+
let sumSq = 0;
|
|
2840
|
+
for (const v of xs) {
|
|
2841
|
+
sumSq += (v - mean) ** 2;
|
|
2842
|
+
}
|
|
2843
|
+
const sampleStd = Math.sqrt(sumSq / (n - 1));
|
|
2844
|
+
if (sampleStd === 0) {
|
|
2845
|
+
return ERRORS.DIV0;
|
|
2846
|
+
}
|
|
2847
|
+
let sumQuad = 0;
|
|
2848
|
+
for (const v of xs) {
|
|
2849
|
+
sumQuad += ((v - mean) / sampleStd) ** 4;
|
|
2850
|
+
}
|
|
2851
|
+
const term1 = (n * (n + 1)) / ((n - 1) * (n - 2) * (n - 3));
|
|
2852
|
+
const term2 = (3 * (n - 1) ** 2) / ((n - 2) * (n - 3));
|
|
2853
|
+
return rvNumber(term1 * sumQuad - term2);
|
|
2854
|
+
};
|
|
2855
|
+
// ============================================================================
|
|
2856
|
+
// PERCENTRANK family
|
|
2857
|
+
// ============================================================================
|
|
2858
|
+
/**
|
|
2859
|
+
* Compute PERCENTRANK's significance-truncated result.
|
|
2860
|
+
*
|
|
2861
|
+
* Excel's `significance` truncates the return value to that many
|
|
2862
|
+
* digits — `significance=3` (default) yields 0.123 rather than
|
|
2863
|
+
* 0.12345. It is a display truncation, not a rounding, so we
|
|
2864
|
+
* implement it by `floor(value * 10^n) / 10^n`.
|
|
2865
|
+
*/
|
|
2866
|
+
function truncateToSignificance(value, significance) {
|
|
2867
|
+
if (significance < 1) {
|
|
2868
|
+
return Number.NaN;
|
|
2869
|
+
}
|
|
2870
|
+
// Significance must be an integer; Excel truncates toward zero.
|
|
2871
|
+
const n = Math.trunc(significance);
|
|
2872
|
+
const scale = Math.pow(10, n);
|
|
2873
|
+
return Math.floor(value * scale) / scale;
|
|
2874
|
+
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Compute the PERCENTRANK.INC or PERCENTRANK.EXC value.
|
|
2877
|
+
*
|
|
2878
|
+
* For PERCENTRANK.INC (inclusive):
|
|
2879
|
+
* rank = i / (n - 1) when x is at sorted index i (0-based, exact match),
|
|
2880
|
+
* or linear interpolation between the two bracketing values.
|
|
2881
|
+
* x < min or x > max → #N/A.
|
|
2882
|
+
*
|
|
2883
|
+
* For PERCENTRANK.EXC (exclusive):
|
|
2884
|
+
* rank = (i + 1) / (n + 1) when x is at sorted index i (0-based, exact match).
|
|
2885
|
+
* x outside [min, max] or rank outside [1/(n+1), n/(n+1)] → #N/A.
|
|
2886
|
+
*/
|
|
2887
|
+
function computePercentRank(sorted, x, inclusive) {
|
|
2888
|
+
const n = sorted.length;
|
|
2889
|
+
if (n === 0) {
|
|
2890
|
+
return null;
|
|
2891
|
+
}
|
|
2892
|
+
// Exact match: find the first index where sorted[i] === x.
|
|
2893
|
+
// Binary search for lower bound.
|
|
2894
|
+
let lo = 0;
|
|
2895
|
+
let hi = n - 1;
|
|
2896
|
+
let found = -1;
|
|
2897
|
+
while (lo <= hi) {
|
|
2898
|
+
const mid = (lo + hi) >>> 1;
|
|
2899
|
+
if (sorted[mid] === x) {
|
|
2900
|
+
// Find first occurrence (ties go to earliest index).
|
|
2901
|
+
let i = mid;
|
|
2902
|
+
while (i > 0 && sorted[i - 1] === x) {
|
|
2903
|
+
i--;
|
|
2904
|
+
}
|
|
2905
|
+
found = i;
|
|
2906
|
+
break;
|
|
2907
|
+
}
|
|
2908
|
+
if (sorted[mid] < x) {
|
|
2909
|
+
lo = mid + 1;
|
|
2910
|
+
}
|
|
2911
|
+
else {
|
|
2912
|
+
hi = mid - 1;
|
|
2913
|
+
}
|
|
2914
|
+
}
|
|
2915
|
+
if (found >= 0) {
|
|
2916
|
+
if (inclusive) {
|
|
2917
|
+
return n === 1 ? 1 : found / (n - 1);
|
|
2918
|
+
}
|
|
2919
|
+
return (found + 1) / (n + 1);
|
|
2920
|
+
}
|
|
2921
|
+
// Interpolation case — x is strictly between sorted[hi] and sorted[lo].
|
|
2922
|
+
// After the loop, lo = insertion point; hi = lo - 1.
|
|
2923
|
+
if (hi < 0 || lo >= n) {
|
|
2924
|
+
return null; // out of range
|
|
2925
|
+
}
|
|
2926
|
+
// Linear interpolation between the two neighbors.
|
|
2927
|
+
const lower = sorted[hi];
|
|
2928
|
+
const upper = sorted[lo];
|
|
2929
|
+
if (upper === lower) {
|
|
2930
|
+
// Degenerate — shouldn't happen given the exact-match check above.
|
|
2931
|
+
return null;
|
|
2932
|
+
}
|
|
2933
|
+
const fraction = (x - lower) / (upper - lower);
|
|
2934
|
+
if (inclusive) {
|
|
2935
|
+
if (n === 1) {
|
|
2936
|
+
return 1;
|
|
2937
|
+
}
|
|
2938
|
+
return (hi + fraction) / (n - 1);
|
|
2939
|
+
}
|
|
2940
|
+
const rank = (hi + 1 + fraction) / (n + 1);
|
|
2941
|
+
// PERCENTRANK.EXC return range is [1/(n+1), n/(n+1)].
|
|
2942
|
+
const minR = 1 / (n + 1);
|
|
2943
|
+
const maxR = n / (n + 1);
|
|
2944
|
+
if (rank < minR || rank > maxR) {
|
|
2945
|
+
return null;
|
|
2946
|
+
}
|
|
2947
|
+
return rank;
|
|
2948
|
+
}
|
|
2949
|
+
function fnPercentRankImpl(args, inclusive) {
|
|
2950
|
+
if (args.length < 2) {
|
|
2951
|
+
return ERRORS.VALUE;
|
|
2952
|
+
}
|
|
2953
|
+
const vals = flattenNumbers([args[0]]);
|
|
2954
|
+
const err = firstError(vals);
|
|
2955
|
+
if (err) {
|
|
2956
|
+
return err;
|
|
2957
|
+
}
|
|
2958
|
+
const xV = toNumberRV(topLeft(args[1]));
|
|
2959
|
+
if (isError(xV)) {
|
|
2960
|
+
return xV;
|
|
2961
|
+
}
|
|
2962
|
+
const significanceV = args.length > 2 ? toNumberRV(topLeft(args[2])) : rvNumber(3);
|
|
2963
|
+
if (isError(significanceV)) {
|
|
2964
|
+
return significanceV;
|
|
2965
|
+
}
|
|
2966
|
+
const significance = significanceV.value;
|
|
2967
|
+
if (significance < 1) {
|
|
2968
|
+
return ERRORS.NUM;
|
|
2969
|
+
}
|
|
2970
|
+
const nums = vals.map(v => v.value);
|
|
2971
|
+
if (nums.length === 0) {
|
|
2972
|
+
return ERRORS.NUM;
|
|
2973
|
+
}
|
|
2974
|
+
const sorted = [...nums].sort((a, b) => a - b);
|
|
2975
|
+
const rank = computePercentRank(sorted, xV.value, inclusive);
|
|
2976
|
+
if (rank === null) {
|
|
2977
|
+
return ERRORS.NA;
|
|
2978
|
+
}
|
|
2979
|
+
const truncated = truncateToSignificance(rank, significance);
|
|
2980
|
+
if (Number.isNaN(truncated)) {
|
|
2981
|
+
return ERRORS.NUM;
|
|
2982
|
+
}
|
|
2983
|
+
return rvNumber(truncated);
|
|
2984
|
+
}
|
|
2985
|
+
export const fnPERCENTRANK = args => fnPercentRankImpl(args, true);
|
|
2986
|
+
export const fnPERCENTRANK_INC = args => fnPercentRankImpl(args, true);
|
|
2987
|
+
export const fnPERCENTRANK_EXC = args => fnPercentRankImpl(args, false);
|
|
2988
|
+
// ============================================================================
|
|
2989
|
+
// PROB — probability that values in X-range are between two limits
|
|
2990
|
+
// ============================================================================
|
|
2991
|
+
/**
|
|
2992
|
+
* PROB(x_range, prob_range, lower_limit, [upper_limit]) — probability that
|
|
2993
|
+
* values in x_range are between lower_limit and upper_limit inclusive.
|
|
2994
|
+
*
|
|
2995
|
+
* Excel rules:
|
|
2996
|
+
* - x_range and prob_range must have the same dimensions
|
|
2997
|
+
* - prob_range entries must sum to 1 (±ε); otherwise #NUM!
|
|
2998
|
+
* - Any prob entry ≤ 0 or > 1 → #NUM!
|
|
2999
|
+
* - upper_limit omitted → probability that x = lower_limit
|
|
3000
|
+
* - Result is the sum of prob_range entries for which
|
|
3001
|
+
* lower_limit ≤ x_range value ≤ upper_limit
|
|
3002
|
+
*/
|
|
3003
|
+
export const fnPROB = args => {
|
|
3004
|
+
if (args.length < 3 || args.length > 4) {
|
|
3005
|
+
return ERRORS.VALUE;
|
|
3006
|
+
}
|
|
3007
|
+
const xVals = flattenNumbers([args[0]]);
|
|
3008
|
+
const xErr = firstError(xVals);
|
|
3009
|
+
if (xErr) {
|
|
3010
|
+
return xErr;
|
|
3011
|
+
}
|
|
3012
|
+
const pVals = flattenNumbers([args[1]]);
|
|
3013
|
+
const pErr = firstError(pVals);
|
|
3014
|
+
if (pErr) {
|
|
3015
|
+
return pErr;
|
|
3016
|
+
}
|
|
3017
|
+
if (xVals.length !== pVals.length) {
|
|
3018
|
+
return ERRORS.NA;
|
|
3019
|
+
}
|
|
3020
|
+
if (xVals.length === 0) {
|
|
3021
|
+
return ERRORS.NUM;
|
|
3022
|
+
}
|
|
3023
|
+
const lowerV = toNumberRV(topLeft(args[2]));
|
|
3024
|
+
if (isError(lowerV)) {
|
|
3025
|
+
return lowerV;
|
|
3026
|
+
}
|
|
3027
|
+
const upperV = args.length > 3 ? toNumberRV(topLeft(args[3])) : lowerV;
|
|
3028
|
+
if (isError(upperV)) {
|
|
3029
|
+
return upperV;
|
|
3030
|
+
}
|
|
3031
|
+
const lower = lowerV.value;
|
|
3032
|
+
const upper = upperV.value;
|
|
3033
|
+
if (lower > upper) {
|
|
3034
|
+
return ERRORS.NUM;
|
|
3035
|
+
}
|
|
3036
|
+
// Probabilities must be in (0, 1] and sum to 1.
|
|
3037
|
+
let total = 0;
|
|
3038
|
+
for (const p of pVals) {
|
|
3039
|
+
const pv = p.value;
|
|
3040
|
+
if (pv <= 0 || pv > 1) {
|
|
3041
|
+
return ERRORS.NUM;
|
|
3042
|
+
}
|
|
3043
|
+
total += pv;
|
|
3044
|
+
}
|
|
3045
|
+
// Excel's tolerance is fairly loose — well within float noise.
|
|
3046
|
+
if (Math.abs(total - 1) > 1e-9) {
|
|
3047
|
+
return ERRORS.NUM;
|
|
3048
|
+
}
|
|
3049
|
+
let result = 0;
|
|
3050
|
+
for (let i = 0; i < xVals.length; i++) {
|
|
3051
|
+
const xv = xVals[i].value;
|
|
3052
|
+
if (xv >= lower && xv <= upper) {
|
|
3053
|
+
result += pVals[i].value;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
return rvNumber(result);
|
|
3057
|
+
};
|
|
3058
|
+
// ============================================================================
|
|
3059
|
+
// Z.TEST, T.TEST, F.TEST, CHISQ.TEST — hypothesis tests
|
|
3060
|
+
// ============================================================================
|
|
3061
|
+
/**
|
|
3062
|
+
* Standard normal CDF — shared by Z.TEST and the T.TEST approximations.
|
|
3063
|
+
* Uses the same erf-based formula as fnNORMSDIST.
|
|
3064
|
+
*/
|
|
3065
|
+
function normalCDF(z) {
|
|
3066
|
+
// Abramowitz & Stegun 7.1.26 approximation, same family used elsewhere.
|
|
3067
|
+
const sign = z < 0 ? -1 : 1;
|
|
3068
|
+
const x = Math.abs(z) / Math.sqrt(2);
|
|
3069
|
+
const t = 1 / (1 + 0.3275911 * x);
|
|
3070
|
+
const y = 1 -
|
|
3071
|
+
((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) * t + 0.254829592) *
|
|
3072
|
+
t *
|
|
3073
|
+
Math.exp(-x * x);
|
|
3074
|
+
return 0.5 * (1 + sign * y);
|
|
3075
|
+
}
|
|
3076
|
+
/**
|
|
3077
|
+
* Z.TEST(array, x, [sigma]) — one-tailed probability value for z-test.
|
|
3078
|
+
* mean = AVERAGE(array), n = COUNT(array)
|
|
3079
|
+
* sigma = provided OR sample standard deviation of array
|
|
3080
|
+
* z = (mean - x) / (sigma / √n)
|
|
3081
|
+
* Z.TEST = 1 - NORM.S.DIST(z, TRUE)
|
|
3082
|
+
*/
|
|
3083
|
+
export const fnZ_TEST = args => {
|
|
3084
|
+
const vals = flattenNumbers([args[0]]);
|
|
3085
|
+
const err = firstError(vals);
|
|
3086
|
+
if (err) {
|
|
3087
|
+
return err;
|
|
3088
|
+
}
|
|
3089
|
+
const xV = toNumberRV(topLeft(args[1]));
|
|
3090
|
+
if (isError(xV)) {
|
|
3091
|
+
return xV;
|
|
3092
|
+
}
|
|
3093
|
+
const nums = vals.map(v => v.value);
|
|
3094
|
+
if (nums.length === 0) {
|
|
3095
|
+
return ERRORS.NA;
|
|
3096
|
+
}
|
|
3097
|
+
const mean = nums.reduce((s, v) => s + v, 0) / nums.length;
|
|
3098
|
+
let sigma;
|
|
3099
|
+
if (args.length > 2) {
|
|
3100
|
+
const sigmaV = toNumberRV(topLeft(args[2]));
|
|
3101
|
+
if (isError(sigmaV)) {
|
|
3102
|
+
return sigmaV;
|
|
3103
|
+
}
|
|
3104
|
+
if (sigmaV.value <= 0) {
|
|
3105
|
+
return ERRORS.NUM;
|
|
3106
|
+
}
|
|
3107
|
+
sigma = sigmaV.value;
|
|
3108
|
+
}
|
|
3109
|
+
else {
|
|
3110
|
+
// Sample std dev.
|
|
3111
|
+
if (nums.length < 2) {
|
|
3112
|
+
return ERRORS.DIV0;
|
|
3113
|
+
}
|
|
3114
|
+
let ss = 0;
|
|
3115
|
+
for (const v of nums) {
|
|
3116
|
+
ss += (v - mean) * (v - mean);
|
|
3117
|
+
}
|
|
3118
|
+
sigma = Math.sqrt(ss / (nums.length - 1));
|
|
3119
|
+
if (sigma === 0) {
|
|
3120
|
+
return ERRORS.DIV0;
|
|
3121
|
+
}
|
|
3122
|
+
}
|
|
3123
|
+
const z = (mean - xV.value) / (sigma / Math.sqrt(nums.length));
|
|
3124
|
+
return rvNumber(1 - normalCDF(z));
|
|
3125
|
+
};
|
|
3126
|
+
/**
|
|
3127
|
+
* F.TEST(array1, array2) — two-tailed F-test probability comparing
|
|
3128
|
+
* the variances of two samples.
|
|
3129
|
+
*
|
|
3130
|
+
* F = var(larger) / var(smaller) (always >= 1)
|
|
3131
|
+
* P = 2 × P(F_dist(df1, df2) > F)
|
|
3132
|
+
*/
|
|
3133
|
+
export const fnF_TEST = args => {
|
|
3134
|
+
const v1 = flattenNumbers([args[0]]);
|
|
3135
|
+
const e1 = firstError(v1);
|
|
3136
|
+
if (e1) {
|
|
3137
|
+
return e1;
|
|
3138
|
+
}
|
|
3139
|
+
const v2 = flattenNumbers([args[1]]);
|
|
3140
|
+
const e2 = firstError(v2);
|
|
3141
|
+
if (e2) {
|
|
3142
|
+
return e2;
|
|
3143
|
+
}
|
|
3144
|
+
const a1 = v1.map(v => v.value);
|
|
3145
|
+
const a2 = v2.map(v => v.value);
|
|
3146
|
+
if (a1.length < 2 || a2.length < 2) {
|
|
3147
|
+
return ERRORS.DIV0;
|
|
3148
|
+
}
|
|
3149
|
+
const varOf = (xs) => {
|
|
3150
|
+
const mean = xs.reduce((s, v) => s + v, 0) / xs.length;
|
|
3151
|
+
let ss = 0;
|
|
3152
|
+
for (const v of xs) {
|
|
3153
|
+
ss += (v - mean) * (v - mean);
|
|
3154
|
+
}
|
|
3155
|
+
return ss / (xs.length - 1);
|
|
3156
|
+
};
|
|
3157
|
+
const var1 = varOf(a1);
|
|
3158
|
+
const var2 = varOf(a2);
|
|
3159
|
+
if (var1 === 0 || var2 === 0) {
|
|
3160
|
+
return ERRORS.DIV0;
|
|
3161
|
+
}
|
|
3162
|
+
// Ensure f >= 1 so we evaluate the right tail.
|
|
3163
|
+
const f = var1 / var2;
|
|
3164
|
+
const df1 = a1.length - 1;
|
|
3165
|
+
const df2 = a2.length - 1;
|
|
3166
|
+
// Regularised incomplete beta: Ix(a,b).
|
|
3167
|
+
// F-distribution right-tail = I_{df2/(df2+df1*F)}(df2/2, df1/2).
|
|
3168
|
+
// Two-tailed p = 2 × min(right, left) = 2 × min(right, 1-right).
|
|
3169
|
+
const x = df2 / (df2 + df1 * f);
|
|
3170
|
+
const rightTail = incompleteBeta(x, df2 / 2, df1 / 2);
|
|
3171
|
+
const twoTail = 2 * Math.min(rightTail, 1 - rightTail);
|
|
3172
|
+
return rvNumber(twoTail);
|
|
3173
|
+
};
|
|
3174
|
+
/**
|
|
3175
|
+
* Regularised incomplete beta function I_x(a, b). Lentz's continued
|
|
3176
|
+
* fraction, adapted from Numerical Recipes §6.4. Sufficient precision
|
|
3177
|
+
* for probability-value calculations.
|
|
3178
|
+
*/
|
|
3179
|
+
function incompleteBeta(x, a, b) {
|
|
3180
|
+
if (x <= 0) {
|
|
3181
|
+
return 0;
|
|
3182
|
+
}
|
|
3183
|
+
if (x >= 1) {
|
|
3184
|
+
return 1;
|
|
3185
|
+
}
|
|
3186
|
+
const bt = Math.exp(lnGamma(a + b) - lnGamma(a) - lnGamma(b) + a * Math.log(x) + b * Math.log(1 - x));
|
|
3187
|
+
const useFront = x < (a + 1) / (a + b + 2);
|
|
3188
|
+
const cf = (xx, aa, bb) => {
|
|
3189
|
+
const maxIt = 200;
|
|
3190
|
+
const eps = 3e-7;
|
|
3191
|
+
const qab = aa + bb;
|
|
3192
|
+
const qap = aa + 1;
|
|
3193
|
+
const qam = aa - 1;
|
|
3194
|
+
let c = 1;
|
|
3195
|
+
let d = 1 - (qab * xx) / qap;
|
|
3196
|
+
if (Math.abs(d) < 1e-30) {
|
|
3197
|
+
d = 1e-30;
|
|
3198
|
+
}
|
|
3199
|
+
d = 1 / d;
|
|
3200
|
+
let h = d;
|
|
3201
|
+
for (let m = 1; m <= maxIt; m++) {
|
|
3202
|
+
const m2 = 2 * m;
|
|
3203
|
+
let aa2 = (m * (bb - m) * xx) / ((qam + m2) * (aa + m2));
|
|
3204
|
+
d = 1 + aa2 * d;
|
|
3205
|
+
if (Math.abs(d) < 1e-30) {
|
|
3206
|
+
d = 1e-30;
|
|
3207
|
+
}
|
|
3208
|
+
c = 1 + aa2 / c;
|
|
3209
|
+
if (Math.abs(c) < 1e-30) {
|
|
3210
|
+
c = 1e-30;
|
|
3211
|
+
}
|
|
3212
|
+
d = 1 / d;
|
|
3213
|
+
h *= d * c;
|
|
3214
|
+
aa2 = (-(aa + m) * (qab + m) * xx) / ((aa + m2) * (qap + m2));
|
|
3215
|
+
d = 1 + aa2 * d;
|
|
3216
|
+
if (Math.abs(d) < 1e-30) {
|
|
3217
|
+
d = 1e-30;
|
|
3218
|
+
}
|
|
3219
|
+
c = 1 + aa2 / c;
|
|
3220
|
+
if (Math.abs(c) < 1e-30) {
|
|
3221
|
+
c = 1e-30;
|
|
3222
|
+
}
|
|
3223
|
+
d = 1 / d;
|
|
3224
|
+
const del = d * c;
|
|
3225
|
+
h *= del;
|
|
3226
|
+
if (Math.abs(del - 1) < eps) {
|
|
3227
|
+
break;
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
return h;
|
|
3231
|
+
};
|
|
3232
|
+
if (useFront) {
|
|
3233
|
+
return (bt * cf(x, a, b)) / a;
|
|
3234
|
+
}
|
|
3235
|
+
return 1 - (bt * cf(1 - x, b, a)) / b;
|
|
3236
|
+
}
|
|
3237
|
+
/**
|
|
3238
|
+
* T.TEST(array1, array2, tails, type) — tails in {1, 2}, type in {1, 2, 3}.
|
|
3239
|
+
* type 1: paired
|
|
3240
|
+
* type 2: two-sample, equal variance
|
|
3241
|
+
* type 3: two-sample, unequal variance (Welch's)
|
|
3242
|
+
*/
|
|
3243
|
+
export const fnT_TEST = args => {
|
|
3244
|
+
const v1 = flattenNumbers([args[0]]);
|
|
3245
|
+
const e1 = firstError(v1);
|
|
3246
|
+
if (e1) {
|
|
3247
|
+
return e1;
|
|
3248
|
+
}
|
|
3249
|
+
const v2 = flattenNumbers([args[1]]);
|
|
3250
|
+
const e2 = firstError(v2);
|
|
3251
|
+
if (e2) {
|
|
3252
|
+
return e2;
|
|
3253
|
+
}
|
|
3254
|
+
const tailsV = toNumberRV(topLeft(args[2]));
|
|
3255
|
+
if (isError(tailsV)) {
|
|
3256
|
+
return tailsV;
|
|
3257
|
+
}
|
|
3258
|
+
const typeV = toNumberRV(topLeft(args[3]));
|
|
3259
|
+
if (isError(typeV)) {
|
|
3260
|
+
return typeV;
|
|
3261
|
+
}
|
|
3262
|
+
const tails = Math.trunc(tailsV.value);
|
|
3263
|
+
const type = Math.trunc(typeV.value);
|
|
3264
|
+
if (tails !== 1 && tails !== 2) {
|
|
3265
|
+
return ERRORS.NUM;
|
|
3266
|
+
}
|
|
3267
|
+
if (type !== 1 && type !== 2 && type !== 3) {
|
|
3268
|
+
return ERRORS.NUM;
|
|
3269
|
+
}
|
|
3270
|
+
const a1 = v1.map(v => v.value);
|
|
3271
|
+
const a2 = v2.map(v => v.value);
|
|
3272
|
+
const mean = (xs) => xs.reduce((s, v) => s + v, 0) / xs.length;
|
|
3273
|
+
const sampleVar = (xs, m) => {
|
|
3274
|
+
let ss = 0;
|
|
3275
|
+
for (const v of xs) {
|
|
3276
|
+
ss += (v - m) * (v - m);
|
|
3277
|
+
}
|
|
3278
|
+
return ss / (xs.length - 1);
|
|
3279
|
+
};
|
|
3280
|
+
let t;
|
|
3281
|
+
let df;
|
|
3282
|
+
if (type === 1) {
|
|
3283
|
+
// Paired
|
|
3284
|
+
if (a1.length !== a2.length) {
|
|
3285
|
+
return ERRORS.NA;
|
|
3286
|
+
}
|
|
3287
|
+
if (a1.length < 2) {
|
|
3288
|
+
return ERRORS.DIV0;
|
|
3289
|
+
}
|
|
3290
|
+
const diffs = a1.map((v, i) => v - a2[i]);
|
|
3291
|
+
const md = mean(diffs);
|
|
3292
|
+
const sd2 = sampleVar(diffs, md);
|
|
3293
|
+
if (sd2 === 0) {
|
|
3294
|
+
return ERRORS.DIV0;
|
|
3295
|
+
}
|
|
3296
|
+
t = md / Math.sqrt(sd2 / diffs.length);
|
|
3297
|
+
df = diffs.length - 1;
|
|
3298
|
+
}
|
|
3299
|
+
else if (type === 2) {
|
|
3300
|
+
// Two-sample, equal variance (pooled)
|
|
3301
|
+
const n1 = a1.length;
|
|
3302
|
+
const n2 = a2.length;
|
|
3303
|
+
if (n1 < 2 || n2 < 2) {
|
|
3304
|
+
return ERRORS.DIV0;
|
|
3305
|
+
}
|
|
3306
|
+
const m1 = mean(a1);
|
|
3307
|
+
const m2 = mean(a2);
|
|
3308
|
+
const s1 = sampleVar(a1, m1);
|
|
3309
|
+
const s2 = sampleVar(a2, m2);
|
|
3310
|
+
const sp2 = ((n1 - 1) * s1 + (n2 - 1) * s2) / (n1 + n2 - 2);
|
|
3311
|
+
if (sp2 === 0) {
|
|
3312
|
+
return ERRORS.DIV0;
|
|
3313
|
+
}
|
|
3314
|
+
t = (m1 - m2) / Math.sqrt(sp2 * (1 / n1 + 1 / n2));
|
|
3315
|
+
df = n1 + n2 - 2;
|
|
3316
|
+
}
|
|
3317
|
+
else {
|
|
3318
|
+
// Two-sample, unequal variance (Welch's)
|
|
3319
|
+
const n1 = a1.length;
|
|
3320
|
+
const n2 = a2.length;
|
|
3321
|
+
if (n1 < 2 || n2 < 2) {
|
|
3322
|
+
return ERRORS.DIV0;
|
|
3323
|
+
}
|
|
3324
|
+
const m1 = mean(a1);
|
|
3325
|
+
const m2 = mean(a2);
|
|
3326
|
+
const s1 = sampleVar(a1, m1);
|
|
3327
|
+
const s2 = sampleVar(a2, m2);
|
|
3328
|
+
if (s1 === 0 && s2 === 0) {
|
|
3329
|
+
return ERRORS.DIV0;
|
|
3330
|
+
}
|
|
3331
|
+
const se2 = s1 / n1 + s2 / n2;
|
|
3332
|
+
t = (m1 - m2) / Math.sqrt(se2);
|
|
3333
|
+
df = (se2 * se2) / (((s1 / n1) * (s1 / n1)) / (n1 - 1) + ((s2 / n2) * (s2 / n2)) / (n2 - 1));
|
|
3334
|
+
}
|
|
3335
|
+
// Two-tailed p = I_{df/(df + t^2)}(df/2, 1/2)
|
|
3336
|
+
const x = df / (df + t * t);
|
|
3337
|
+
const oneTail = 0.5 * incompleteBeta(x, df / 2, 0.5);
|
|
3338
|
+
return rvNumber(tails === 1 ? oneTail : 2 * oneTail);
|
|
3339
|
+
};
|
|
3340
|
+
/**
|
|
3341
|
+
* CHISQ.TEST(actual_range, expected_range) — chi-square independence test.
|
|
3342
|
+
*
|
|
3343
|
+
* χ² = Σ (actual_i - expected_i)² / expected_i
|
|
3344
|
+
* df = (rows-1)(cols-1) for a contingency table, or n-1 for 1-D
|
|
3345
|
+
* p = 1 - CHISQ.DIST(χ², df, TRUE)
|
|
3346
|
+
*/
|
|
3347
|
+
export const fnCHISQ_TEST = args => {
|
|
3348
|
+
if (args[0].kind !== RVKind.Array || args[1].kind !== RVKind.Array) {
|
|
3349
|
+
return ERRORS.VALUE;
|
|
3350
|
+
}
|
|
3351
|
+
const a = args[0];
|
|
3352
|
+
const e = args[1];
|
|
3353
|
+
if (a.height !== e.height || a.width !== e.width) {
|
|
3354
|
+
return ERRORS.NA;
|
|
3355
|
+
}
|
|
3356
|
+
let chi2 = 0;
|
|
3357
|
+
for (let r = 0; r < a.height; r++) {
|
|
3358
|
+
for (let c = 0; c < a.width; c++) {
|
|
3359
|
+
const av = a.rows[r][c];
|
|
3360
|
+
const ev = e.rows[r][c];
|
|
3361
|
+
if (av.kind === RVKind.Error) {
|
|
3362
|
+
return av;
|
|
3363
|
+
}
|
|
3364
|
+
if (ev.kind === RVKind.Error) {
|
|
3365
|
+
return ev;
|
|
3366
|
+
}
|
|
3367
|
+
if (av.kind !== RVKind.Number || ev.kind !== RVKind.Number) {
|
|
3368
|
+
return ERRORS.VALUE;
|
|
3369
|
+
}
|
|
3370
|
+
if (ev.value <= 0) {
|
|
3371
|
+
return ERRORS.NUM;
|
|
3372
|
+
}
|
|
3373
|
+
const diff = av.value - ev.value;
|
|
3374
|
+
chi2 += (diff * diff) / ev.value;
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
// df: (rows-1)(cols-1) for contingency tables, n-1 for 1-D.
|
|
3378
|
+
let df;
|
|
3379
|
+
if (a.height === 1 || a.width === 1) {
|
|
3380
|
+
df = a.height * a.width - 1;
|
|
3381
|
+
}
|
|
3382
|
+
else {
|
|
3383
|
+
df = (a.height - 1) * (a.width - 1);
|
|
3384
|
+
}
|
|
3385
|
+
if (df < 1) {
|
|
3386
|
+
return ERRORS.NA;
|
|
3387
|
+
}
|
|
3388
|
+
// p = 1 - CHISQ.DIST.CDF(chi2, df) = right tail
|
|
3389
|
+
return rvNumber(1 - gammaIncomplete(df / 2, chi2 / 2));
|
|
3390
|
+
};
|