@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.
Files changed (383) hide show
  1. package/README.md +25 -2
  2. package/README_zh.md +29 -6
  3. package/dist/browser/index.browser.d.ts +1 -1
  4. package/dist/browser/index.browser.js +4 -0
  5. package/dist/browser/index.d.ts +1 -1
  6. package/dist/browser/index.js +4 -0
  7. package/dist/browser/modules/excel/cell.d.ts +17 -3
  8. package/dist/browser/modules/excel/cell.js +170 -22
  9. package/dist/browser/modules/excel/defined-names.d.ts +96 -1
  10. package/dist/browser/modules/excel/defined-names.js +411 -21
  11. package/dist/browser/modules/excel/image.d.ts +11 -0
  12. package/dist/browser/modules/excel/image.js +24 -1
  13. package/dist/browser/modules/excel/stream/workbook-reader.browser.d.ts +9 -3
  14. package/dist/browser/modules/excel/stream/workbook-reader.browser.js +14 -0
  15. package/dist/browser/modules/excel/stream/workbook-reader.d.ts +2 -1
  16. package/dist/browser/modules/excel/stream/workbook-writer.browser.d.ts +39 -5
  17. package/dist/browser/modules/excel/stream/workbook-writer.browser.js +48 -1
  18. package/dist/browser/modules/excel/stream/workbook-writer.d.ts +3 -2
  19. package/dist/browser/modules/excel/stream/worksheet-reader.js +17 -1
  20. package/dist/browser/modules/excel/stream/worksheet-writer.d.ts +39 -6
  21. package/dist/browser/modules/excel/stream/worksheet-writer.js +45 -5
  22. package/dist/browser/modules/excel/table.js +15 -2
  23. package/dist/browser/modules/excel/types.d.ts +133 -2
  24. package/dist/browser/modules/excel/utils/col-cache.d.ts +1 -0
  25. package/dist/browser/modules/excel/utils/col-cache.js +15 -0
  26. package/dist/browser/modules/excel/utils/drawing-utils.d.ts +3 -3
  27. package/dist/browser/modules/excel/utils/drawing-utils.js +4 -0
  28. package/dist/browser/modules/excel/utils/external-link-formula.d.ts +76 -0
  29. package/dist/browser/modules/excel/utils/external-link-formula.js +208 -0
  30. package/dist/browser/modules/excel/utils/iterate-stream.d.ts +9 -3
  31. package/dist/browser/modules/excel/utils/iterate-stream.js +3 -1
  32. package/dist/browser/modules/excel/utils/ooxml-paths.d.ts +19 -0
  33. package/dist/browser/modules/excel/utils/ooxml-paths.js +37 -2
  34. package/dist/browser/modules/excel/utils/shared-strings.d.ts +8 -3
  35. package/dist/browser/modules/excel/utils/shared-strings.js +21 -2
  36. package/dist/browser/modules/excel/utils/workbook-protection.d.ts +30 -0
  37. package/dist/browser/modules/excel/utils/workbook-protection.js +30 -0
  38. package/dist/browser/modules/excel/workbook.browser.d.ts +257 -6
  39. package/dist/browser/modules/excel/workbook.browser.js +318 -34
  40. package/dist/browser/modules/excel/workbook.d.ts +1 -1
  41. package/dist/browser/modules/excel/worksheet.d.ts +3 -1
  42. package/dist/browser/modules/excel/worksheet.js +21 -2
  43. package/dist/browser/modules/excel/xlsx/rel-type.d.ts +15 -0
  44. package/dist/browser/modules/excel/xlsx/rel-type.js +16 -1
  45. package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +6 -5
  46. package/dist/browser/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
  47. package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.d.ts +84 -0
  48. package/dist/browser/modules/excel/xlsx/xform/book/external-link-xform.js +330 -0
  49. package/dist/browser/modules/excel/xlsx/xform/book/external-reference-xform.d.ts +17 -0
  50. package/dist/browser/modules/excel/xlsx/xform/book/external-reference-xform.js +24 -0
  51. package/dist/browser/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.d.ts +3 -0
  52. package/dist/browser/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
  53. package/dist/browser/modules/excel/xlsx/xform/book/workbook-protection-xform.d.ts +20 -0
  54. package/dist/browser/modules/excel/xlsx/xform/book/workbook-protection-xform.js +66 -0
  55. package/dist/browser/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
  56. package/dist/browser/modules/excel/xlsx/xform/core/content-types-xform.js +19 -1
  57. package/dist/browser/modules/excel/xlsx/xform/core/metadata-xform.d.ts +56 -0
  58. package/dist/browser/modules/excel/xlsx/xform/core/metadata-xform.js +158 -0
  59. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.d.ts +26 -0
  60. package/dist/browser/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +105 -0
  61. package/dist/browser/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
  62. package/dist/browser/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
  63. package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.d.ts +1 -1
  64. package/dist/browser/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
  65. package/dist/browser/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
  66. package/dist/browser/modules/excel/xlsx/xform/sheet/ignored-errors-xform.d.ts +21 -0
  67. package/dist/browser/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +80 -0
  68. package/dist/browser/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
  69. package/dist/browser/modules/excel/xlsx/xform/style/border-xform.js +4 -1
  70. package/dist/browser/modules/excel/xlsx/xlsx.browser.d.ts +172 -13
  71. package/dist/browser/modules/excel/xlsx/xlsx.browser.js +410 -20
  72. package/dist/browser/modules/excel/xlsx/xlsx.d.ts +7 -4
  73. package/dist/browser/modules/excel/xlsx/xlsx.js +4 -5
  74. package/dist/browser/modules/formula/compile/address-utils.d.ts +62 -0
  75. package/dist/browser/modules/formula/compile/address-utils.js +83 -0
  76. package/dist/browser/modules/formula/compile/binder.d.ts +42 -0
  77. package/dist/browser/modules/formula/compile/binder.js +487 -0
  78. package/dist/browser/modules/formula/compile/bound-ast.d.ts +230 -0
  79. package/dist/browser/modules/formula/compile/bound-ast.js +80 -0
  80. package/dist/browser/modules/formula/compile/compiled-formula.d.ts +137 -0
  81. package/dist/browser/modules/formula/compile/compiled-formula.js +383 -0
  82. package/dist/browser/modules/formula/compile/dependency-analysis.d.ts +93 -0
  83. package/dist/browser/modules/formula/compile/dependency-analysis.js +432 -0
  84. package/dist/browser/modules/formula/compile/structured-ref-utils.d.ts +93 -0
  85. package/dist/browser/modules/formula/compile/structured-ref-utils.js +136 -0
  86. package/dist/browser/modules/formula/default-syntax-probe.d.ts +79 -0
  87. package/dist/browser/modules/formula/default-syntax-probe.js +83 -0
  88. package/dist/browser/modules/formula/functions/_date-context.d.ts +4 -0
  89. package/dist/browser/modules/formula/functions/_date-context.js +29 -0
  90. package/dist/browser/modules/formula/functions/_shared.d.ts +121 -0
  91. package/dist/browser/modules/formula/functions/_shared.js +381 -0
  92. package/dist/browser/modules/formula/functions/conditional.d.ts +27 -0
  93. package/dist/browser/modules/formula/functions/conditional.js +343 -0
  94. package/dist/browser/modules/formula/functions/database.d.ts +37 -0
  95. package/dist/browser/modules/formula/functions/database.js +274 -0
  96. package/dist/browser/modules/formula/functions/date.d.ts +61 -0
  97. package/dist/browser/modules/formula/functions/date.js +855 -0
  98. package/dist/browser/modules/formula/functions/dynamic-array.d.ts +23 -0
  99. package/dist/browser/modules/formula/functions/dynamic-array.js +860 -0
  100. package/dist/browser/modules/formula/functions/engineering.d.ts +57 -0
  101. package/dist/browser/modules/formula/functions/engineering.js +1128 -0
  102. package/dist/browser/modules/formula/functions/financial.d.ts +202 -0
  103. package/dist/browser/modules/formula/functions/financial.js +2296 -0
  104. package/dist/browser/modules/formula/functions/lookup.d.ts +18 -0
  105. package/dist/browser/modules/formula/functions/lookup.js +886 -0
  106. package/dist/browser/modules/formula/functions/math.d.ts +114 -0
  107. package/dist/browser/modules/formula/functions/math.js +1406 -0
  108. package/dist/browser/modules/formula/functions/statistical.d.ts +193 -0
  109. package/dist/browser/modules/formula/functions/statistical.js +3390 -0
  110. package/dist/browser/modules/formula/functions/text.d.ts +86 -0
  111. package/dist/browser/modules/formula/functions/text.js +1845 -0
  112. package/dist/browser/modules/formula/host-registry.d.ts +53 -0
  113. package/dist/browser/modules/formula/host-registry.js +69 -0
  114. package/dist/browser/modules/formula/index.d.ts +39 -0
  115. package/dist/browser/modules/formula/index.js +49 -0
  116. package/dist/browser/modules/formula/install.d.ts +62 -0
  117. package/dist/browser/modules/formula/install.js +88 -0
  118. package/dist/browser/modules/formula/integration/apply-writeback-plan.d.ts +26 -0
  119. package/dist/browser/modules/formula/integration/apply-writeback-plan.js +210 -0
  120. package/dist/browser/modules/formula/integration/calculate-formulas-impl.d.ts +30 -0
  121. package/dist/browser/modules/formula/integration/calculate-formulas-impl.js +616 -0
  122. package/dist/browser/modules/formula/integration/calculate-formulas.d.ts +67 -0
  123. package/dist/browser/modules/formula/integration/calculate-formulas.js +68 -0
  124. package/dist/browser/modules/formula/integration/formula-instance.d.ts +64 -0
  125. package/dist/browser/modules/formula/integration/formula-instance.js +79 -0
  126. package/dist/browser/modules/formula/integration/workbook-adapter.d.ts +26 -0
  127. package/dist/browser/modules/formula/integration/workbook-adapter.js +324 -0
  128. package/dist/browser/modules/formula/integration/workbook-snapshot.d.ts +267 -0
  129. package/dist/browser/modules/formula/integration/workbook-snapshot.js +77 -0
  130. package/dist/browser/modules/formula/materialize/build-writeback-plan.d.ts +34 -0
  131. package/dist/browser/modules/formula/materialize/build-writeback-plan.js +473 -0
  132. package/dist/browser/modules/formula/materialize/spill-engine.d.ts +9 -0
  133. package/dist/browser/modules/formula/materialize/spill-engine.js +38 -0
  134. package/dist/browser/modules/formula/materialize/types.d.ts +179 -0
  135. package/dist/browser/modules/formula/materialize/types.js +29 -0
  136. package/dist/browser/modules/formula/materialize/writeback-plan.d.ts +167 -0
  137. package/dist/browser/modules/formula/materialize/writeback-plan.js +27 -0
  138. package/dist/browser/modules/formula/runtime/evaluator.d.ts +151 -0
  139. package/dist/browser/modules/formula/runtime/evaluator.js +2291 -0
  140. package/dist/browser/modules/formula/runtime/function-registry.d.ts +47 -0
  141. package/dist/browser/modules/formula/runtime/function-registry.js +840 -0
  142. package/dist/browser/modules/formula/runtime/values.d.ts +211 -0
  143. package/dist/browser/modules/formula/runtime/values.js +385 -0
  144. package/dist/browser/modules/formula/syntax/ast.d.ts +129 -0
  145. package/dist/browser/modules/formula/syntax/ast.js +28 -0
  146. package/dist/browser/modules/formula/syntax/parser.d.ts +18 -0
  147. package/dist/browser/modules/formula/syntax/parser.js +439 -0
  148. package/dist/browser/modules/formula/syntax/token-types.d.ts +153 -0
  149. package/dist/browser/modules/formula/syntax/token-types.js +59 -0
  150. package/dist/browser/modules/formula/syntax/tokenizer.d.ts +10 -0
  151. package/dist/browser/modules/formula/syntax/tokenizer.js +1074 -0
  152. package/dist/browser/modules/pdf/excel-bridge.js +9 -0
  153. package/dist/cjs/index.js +4 -0
  154. package/dist/cjs/modules/excel/cell.js +170 -22
  155. package/dist/cjs/modules/excel/defined-names.js +411 -21
  156. package/dist/cjs/modules/excel/image.js +24 -1
  157. package/dist/cjs/modules/excel/stream/workbook-reader.browser.js +14 -0
  158. package/dist/cjs/modules/excel/stream/workbook-writer.browser.js +48 -1
  159. package/dist/cjs/modules/excel/stream/worksheet-reader.js +17 -1
  160. package/dist/cjs/modules/excel/stream/worksheet-writer.js +45 -5
  161. package/dist/cjs/modules/excel/table.js +15 -2
  162. package/dist/cjs/modules/excel/utils/col-cache.js +15 -0
  163. package/dist/cjs/modules/excel/utils/drawing-utils.js +4 -0
  164. package/dist/cjs/modules/excel/utils/external-link-formula.js +212 -0
  165. package/dist/cjs/modules/excel/utils/iterate-stream.js +3 -1
  166. package/dist/cjs/modules/excel/utils/ooxml-paths.js +42 -2
  167. package/dist/cjs/modules/excel/utils/shared-strings.js +21 -2
  168. package/dist/cjs/modules/excel/utils/workbook-protection.js +33 -0
  169. package/dist/cjs/modules/excel/workbook.browser.js +318 -34
  170. package/dist/cjs/modules/excel/worksheet.js +20 -1
  171. package/dist/cjs/modules/excel/xlsx/rel-type.js +16 -1
  172. package/dist/cjs/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
  173. package/dist/cjs/modules/excel/xlsx/xform/book/external-link-xform.js +333 -0
  174. package/dist/cjs/modules/excel/xlsx/xform/book/external-reference-xform.js +27 -0
  175. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
  176. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-protection-xform.js +69 -0
  177. package/dist/cjs/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
  178. package/dist/cjs/modules/excel/xlsx/xform/core/content-types-xform.js +18 -0
  179. package/dist/cjs/modules/excel/xlsx/xform/core/metadata-xform.js +161 -0
  180. package/dist/cjs/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +108 -0
  181. package/dist/cjs/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
  182. package/dist/cjs/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
  183. package/dist/cjs/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
  184. package/dist/cjs/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
  185. package/dist/cjs/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +83 -0
  186. package/dist/cjs/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
  187. package/dist/cjs/modules/excel/xlsx/xform/style/border-xform.js +4 -1
  188. package/dist/cjs/modules/excel/xlsx/xlsx.browser.js +408 -18
  189. package/dist/cjs/modules/excel/xlsx/xlsx.js +4 -5
  190. package/dist/cjs/modules/formula/compile/address-utils.js +89 -0
  191. package/dist/cjs/modules/formula/compile/binder.js +489 -0
  192. package/dist/cjs/modules/formula/compile/bound-ast.js +68 -0
  193. package/dist/cjs/modules/formula/compile/compiled-formula.js +387 -0
  194. package/dist/cjs/modules/formula/compile/dependency-analysis.js +437 -0
  195. package/dist/cjs/modules/formula/compile/structured-ref-utils.js +141 -0
  196. package/dist/cjs/modules/formula/default-syntax-probe.js +87 -0
  197. package/dist/cjs/modules/formula/functions/_date-context.js +33 -0
  198. package/dist/cjs/modules/formula/functions/_shared.js +396 -0
  199. package/dist/cjs/modules/formula/functions/conditional.js +354 -0
  200. package/dist/cjs/modules/formula/functions/database.js +288 -0
  201. package/dist/cjs/modules/formula/functions/date.js +883 -0
  202. package/dist/cjs/modules/formula/functions/dynamic-array.js +881 -0
  203. package/dist/cjs/modules/formula/functions/engineering.js +1183 -0
  204. package/dist/cjs/modules/formula/functions/financial.js +2348 -0
  205. package/dist/cjs/modules/formula/functions/lookup.js +902 -0
  206. package/dist/cjs/modules/formula/functions/math.js +1487 -0
  207. package/dist/cjs/modules/formula/functions/statistical.js +3488 -0
  208. package/dist/cjs/modules/formula/functions/text.js +1889 -0
  209. package/dist/cjs/modules/formula/host-registry.js +75 -0
  210. package/dist/cjs/modules/formula/index.js +58 -0
  211. package/dist/cjs/modules/formula/install.js +93 -0
  212. package/dist/cjs/modules/formula/integration/apply-writeback-plan.js +213 -0
  213. package/dist/cjs/modules/formula/integration/calculate-formulas-impl.js +619 -0
  214. package/dist/cjs/modules/formula/integration/calculate-formulas.js +71 -0
  215. package/dist/cjs/modules/formula/integration/formula-instance.js +82 -0
  216. package/dist/cjs/modules/formula/integration/workbook-adapter.js +327 -0
  217. package/dist/cjs/modules/formula/integration/workbook-snapshot.js +84 -0
  218. package/dist/cjs/modules/formula/materialize/build-writeback-plan.js +475 -0
  219. package/dist/cjs/modules/formula/materialize/spill-engine.js +42 -0
  220. package/dist/cjs/modules/formula/materialize/types.js +32 -0
  221. package/dist/cjs/modules/formula/materialize/writeback-plan.js +28 -0
  222. package/dist/cjs/modules/formula/runtime/evaluator.js +2298 -0
  223. package/dist/cjs/modules/formula/runtime/function-registry.js +846 -0
  224. package/dist/cjs/modules/formula/runtime/values.js +385 -0
  225. package/dist/cjs/modules/formula/syntax/ast.js +8 -0
  226. package/dist/cjs/modules/formula/syntax/parser.js +440 -0
  227. package/dist/cjs/modules/formula/syntax/token-types.js +32 -0
  228. package/dist/cjs/modules/formula/syntax/tokenizer.js +1076 -0
  229. package/dist/cjs/modules/pdf/excel-bridge.js +9 -0
  230. package/dist/esm/index.browser.js +4 -0
  231. package/dist/esm/index.js +4 -0
  232. package/dist/esm/modules/excel/cell.js +170 -22
  233. package/dist/esm/modules/excel/defined-names.js +411 -21
  234. package/dist/esm/modules/excel/image.js +24 -1
  235. package/dist/esm/modules/excel/stream/workbook-reader.browser.js +14 -0
  236. package/dist/esm/modules/excel/stream/workbook-writer.browser.js +48 -1
  237. package/dist/esm/modules/excel/stream/worksheet-reader.js +17 -1
  238. package/dist/esm/modules/excel/stream/worksheet-writer.js +45 -5
  239. package/dist/esm/modules/excel/table.js +15 -2
  240. package/dist/esm/modules/excel/utils/col-cache.js +15 -0
  241. package/dist/esm/modules/excel/utils/drawing-utils.js +4 -0
  242. package/dist/esm/modules/excel/utils/external-link-formula.js +208 -0
  243. package/dist/esm/modules/excel/utils/iterate-stream.js +3 -1
  244. package/dist/esm/modules/excel/utils/ooxml-paths.js +37 -2
  245. package/dist/esm/modules/excel/utils/shared-strings.js +21 -2
  246. package/dist/esm/modules/excel/utils/workbook-protection.js +30 -0
  247. package/dist/esm/modules/excel/workbook.browser.js +318 -34
  248. package/dist/esm/modules/excel/worksheet.js +21 -2
  249. package/dist/esm/modules/excel/xlsx/rel-type.js +16 -1
  250. package/dist/esm/modules/excel/xlsx/xform/book/defined-name-xform.js +21 -86
  251. package/dist/esm/modules/excel/xlsx/xform/book/external-link-xform.js +330 -0
  252. package/dist/esm/modules/excel/xlsx/xform/book/external-reference-xform.js +24 -0
  253. package/dist/esm/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.js +11 -2
  254. package/dist/esm/modules/excel/xlsx/xform/book/workbook-protection-xform.js +66 -0
  255. package/dist/esm/modules/excel/xlsx/xform/book/workbook-xform.js +38 -5
  256. package/dist/esm/modules/excel/xlsx/xform/core/content-types-xform.js +19 -1
  257. package/dist/esm/modules/excel/xlsx/xform/core/metadata-xform.js +158 -0
  258. package/dist/esm/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.js +105 -0
  259. package/dist/esm/modules/excel/xlsx/xform/drawing/base-cell-anchor-xform.js +3 -0
  260. package/dist/esm/modules/excel/xlsx/xform/drawing/drawing-xform.js +10 -2
  261. package/dist/esm/modules/excel/xlsx/xform/sheet/cell-xform.js +166 -8
  262. package/dist/esm/modules/excel/xlsx/xform/sheet/data-validations-xform.js +1 -1
  263. package/dist/esm/modules/excel/xlsx/xform/sheet/ignored-errors-xform.js +80 -0
  264. package/dist/esm/modules/excel/xlsx/xform/sheet/worksheet-xform.js +9 -4
  265. package/dist/esm/modules/excel/xlsx/xform/style/border-xform.js +4 -1
  266. package/dist/esm/modules/excel/xlsx/xlsx.browser.js +410 -20
  267. package/dist/esm/modules/excel/xlsx/xlsx.js +4 -5
  268. package/dist/esm/modules/formula/compile/address-utils.js +83 -0
  269. package/dist/esm/modules/formula/compile/binder.js +487 -0
  270. package/dist/esm/modules/formula/compile/bound-ast.js +80 -0
  271. package/dist/esm/modules/formula/compile/compiled-formula.js +383 -0
  272. package/dist/esm/modules/formula/compile/dependency-analysis.js +432 -0
  273. package/dist/esm/modules/formula/compile/structured-ref-utils.js +136 -0
  274. package/dist/esm/modules/formula/default-syntax-probe.js +83 -0
  275. package/dist/esm/modules/formula/functions/_date-context.js +29 -0
  276. package/dist/esm/modules/formula/functions/_shared.js +381 -0
  277. package/dist/esm/modules/formula/functions/conditional.js +343 -0
  278. package/dist/esm/modules/formula/functions/database.js +274 -0
  279. package/dist/esm/modules/formula/functions/date.js +855 -0
  280. package/dist/esm/modules/formula/functions/dynamic-array.js +860 -0
  281. package/dist/esm/modules/formula/functions/engineering.js +1128 -0
  282. package/dist/esm/modules/formula/functions/financial.js +2296 -0
  283. package/dist/esm/modules/formula/functions/lookup.js +886 -0
  284. package/dist/esm/modules/formula/functions/math.js +1406 -0
  285. package/dist/esm/modules/formula/functions/statistical.js +3390 -0
  286. package/dist/esm/modules/formula/functions/text.js +1845 -0
  287. package/dist/esm/modules/formula/host-registry.js +69 -0
  288. package/dist/esm/modules/formula/index.js +49 -0
  289. package/dist/esm/modules/formula/install.js +88 -0
  290. package/dist/esm/modules/formula/integration/apply-writeback-plan.js +210 -0
  291. package/dist/esm/modules/formula/integration/calculate-formulas-impl.js +616 -0
  292. package/dist/esm/modules/formula/integration/calculate-formulas.js +68 -0
  293. package/dist/esm/modules/formula/integration/formula-instance.js +79 -0
  294. package/dist/esm/modules/formula/integration/workbook-adapter.js +324 -0
  295. package/dist/esm/modules/formula/integration/workbook-snapshot.js +77 -0
  296. package/dist/esm/modules/formula/materialize/build-writeback-plan.js +473 -0
  297. package/dist/esm/modules/formula/materialize/spill-engine.js +38 -0
  298. package/dist/esm/modules/formula/materialize/types.js +29 -0
  299. package/dist/esm/modules/formula/materialize/writeback-plan.js +27 -0
  300. package/dist/esm/modules/formula/runtime/evaluator.js +2291 -0
  301. package/dist/esm/modules/formula/runtime/function-registry.js +840 -0
  302. package/dist/esm/modules/formula/runtime/values.js +385 -0
  303. package/dist/esm/modules/formula/syntax/ast.js +28 -0
  304. package/dist/esm/modules/formula/syntax/parser.js +439 -0
  305. package/dist/esm/modules/formula/syntax/token-types.js +59 -0
  306. package/dist/esm/modules/formula/syntax/tokenizer.js +1074 -0
  307. package/dist/esm/modules/pdf/excel-bridge.js +9 -0
  308. package/dist/iife/excelts.iife.js +2302 -373
  309. package/dist/iife/excelts.iife.js.map +1 -1
  310. package/dist/iife/excelts.iife.min.js +34 -34
  311. package/dist/types/index.browser.d.ts +1 -1
  312. package/dist/types/index.d.ts +1 -1
  313. package/dist/types/modules/excel/cell.d.ts +17 -3
  314. package/dist/types/modules/excel/defined-names.d.ts +96 -1
  315. package/dist/types/modules/excel/image.d.ts +11 -0
  316. package/dist/types/modules/excel/stream/workbook-reader.browser.d.ts +9 -3
  317. package/dist/types/modules/excel/stream/workbook-reader.d.ts +2 -1
  318. package/dist/types/modules/excel/stream/workbook-writer.browser.d.ts +39 -5
  319. package/dist/types/modules/excel/stream/workbook-writer.d.ts +3 -2
  320. package/dist/types/modules/excel/stream/worksheet-writer.d.ts +39 -6
  321. package/dist/types/modules/excel/types.d.ts +133 -2
  322. package/dist/types/modules/excel/utils/col-cache.d.ts +1 -0
  323. package/dist/types/modules/excel/utils/drawing-utils.d.ts +3 -3
  324. package/dist/types/modules/excel/utils/external-link-formula.d.ts +76 -0
  325. package/dist/types/modules/excel/utils/iterate-stream.d.ts +9 -3
  326. package/dist/types/modules/excel/utils/ooxml-paths.d.ts +19 -0
  327. package/dist/types/modules/excel/utils/shared-strings.d.ts +8 -3
  328. package/dist/types/modules/excel/utils/workbook-protection.d.ts +30 -0
  329. package/dist/types/modules/excel/workbook.browser.d.ts +257 -6
  330. package/dist/types/modules/excel/workbook.d.ts +1 -1
  331. package/dist/types/modules/excel/worksheet.d.ts +3 -1
  332. package/dist/types/modules/excel/xlsx/rel-type.d.ts +15 -0
  333. package/dist/types/modules/excel/xlsx/xform/book/defined-name-xform.d.ts +6 -5
  334. package/dist/types/modules/excel/xlsx/xform/book/external-link-xform.d.ts +84 -0
  335. package/dist/types/modules/excel/xlsx/xform/book/external-reference-xform.d.ts +17 -0
  336. package/dist/types/modules/excel/xlsx/xform/book/workbook-calc-properties-xform.d.ts +3 -0
  337. package/dist/types/modules/excel/xlsx/xform/book/workbook-protection-xform.d.ts +20 -0
  338. package/dist/types/modules/excel/xlsx/xform/core/metadata-xform.d.ts +56 -0
  339. package/dist/types/modules/excel/xlsx/xform/drawing/absolute-anchor-xform.d.ts +26 -0
  340. package/dist/types/modules/excel/xlsx/xform/sheet/cell-xform.d.ts +1 -1
  341. package/dist/types/modules/excel/xlsx/xform/sheet/ignored-errors-xform.d.ts +21 -0
  342. package/dist/types/modules/excel/xlsx/xlsx.browser.d.ts +172 -13
  343. package/dist/types/modules/excel/xlsx/xlsx.d.ts +7 -4
  344. package/dist/types/modules/formula/compile/address-utils.d.ts +62 -0
  345. package/dist/types/modules/formula/compile/binder.d.ts +42 -0
  346. package/dist/types/modules/formula/compile/bound-ast.d.ts +230 -0
  347. package/dist/types/modules/formula/compile/compiled-formula.d.ts +137 -0
  348. package/dist/types/modules/formula/compile/dependency-analysis.d.ts +93 -0
  349. package/dist/types/modules/formula/compile/structured-ref-utils.d.ts +93 -0
  350. package/dist/types/modules/formula/default-syntax-probe.d.ts +79 -0
  351. package/dist/types/modules/formula/functions/_date-context.d.ts +4 -0
  352. package/dist/types/modules/formula/functions/_shared.d.ts +121 -0
  353. package/dist/types/modules/formula/functions/conditional.d.ts +27 -0
  354. package/dist/types/modules/formula/functions/database.d.ts +37 -0
  355. package/dist/types/modules/formula/functions/date.d.ts +61 -0
  356. package/dist/types/modules/formula/functions/dynamic-array.d.ts +23 -0
  357. package/dist/types/modules/formula/functions/engineering.d.ts +57 -0
  358. package/dist/types/modules/formula/functions/financial.d.ts +202 -0
  359. package/dist/types/modules/formula/functions/lookup.d.ts +18 -0
  360. package/dist/types/modules/formula/functions/math.d.ts +114 -0
  361. package/dist/types/modules/formula/functions/statistical.d.ts +193 -0
  362. package/dist/types/modules/formula/functions/text.d.ts +86 -0
  363. package/dist/types/modules/formula/host-registry.d.ts +53 -0
  364. package/dist/types/modules/formula/index.d.ts +39 -0
  365. package/dist/types/modules/formula/install.d.ts +62 -0
  366. package/dist/types/modules/formula/integration/apply-writeback-plan.d.ts +26 -0
  367. package/dist/types/modules/formula/integration/calculate-formulas-impl.d.ts +30 -0
  368. package/dist/types/modules/formula/integration/calculate-formulas.d.ts +67 -0
  369. package/dist/types/modules/formula/integration/formula-instance.d.ts +64 -0
  370. package/dist/types/modules/formula/integration/workbook-adapter.d.ts +26 -0
  371. package/dist/types/modules/formula/integration/workbook-snapshot.d.ts +267 -0
  372. package/dist/types/modules/formula/materialize/build-writeback-plan.d.ts +34 -0
  373. package/dist/types/modules/formula/materialize/spill-engine.d.ts +9 -0
  374. package/dist/types/modules/formula/materialize/types.d.ts +179 -0
  375. package/dist/types/modules/formula/materialize/writeback-plan.d.ts +167 -0
  376. package/dist/types/modules/formula/runtime/evaluator.d.ts +151 -0
  377. package/dist/types/modules/formula/runtime/function-registry.d.ts +47 -0
  378. package/dist/types/modules/formula/runtime/values.d.ts +211 -0
  379. package/dist/types/modules/formula/syntax/ast.d.ts +129 -0
  380. package/dist/types/modules/formula/syntax/parser.d.ts +18 -0
  381. package/dist/types/modules/formula/syntax/token-types.d.ts +153 -0
  382. package/dist/types/modules/formula/syntax/tokenizer.d.ts +10 -0
  383. package/package.json +28 -28
@@ -0,0 +1,1406 @@
1
+ /**
2
+ * Math / Aggregate Functions — Native RuntimeValue implementation.
3
+ */
4
+ import { RVKind, ERRORS, rvNumber, rvString, rvArray, toNumberRV, toStringRV, topLeft, isError, isArray } from "../runtime/values.js";
5
+ import { argToNumber, flattenAll, flattenNumbers, firstError } from "./_shared.js";
6
+ // ============================================================================
7
+ // Internal Helpers
8
+ // ============================================================================
9
+ /**
10
+ * Apply a rounding operation using an integer factor, avoiding the
11
+ * floating-point drift that `Math.pow(10, d) / Math.pow(10, d)` introduces
12
+ * when `d` is negative (e.g. `1/0.1` is not exactly `10`).
13
+ *
14
+ * For `digits >= 0` we scale up by `10^digits`, round, and scale back down.
15
+ * For `digits < 0` we divide by `10^|digits|`, round, and multiply back up.
16
+ * In both branches the scaling factor is an integer, so only one rounding
17
+ * error is introduced.
18
+ */
19
+ function applyRounding(value, digits, round) {
20
+ const d = Math.trunc(digits);
21
+ // Clamp digits to Excel's documented range [-127, 127]. Without this
22
+ // guard, extreme inputs blow `Math.pow(10, d)` up to Infinity; the
23
+ // resulting `round(value * Infinity) / Infinity` is NaN, which then
24
+ // leaks through the whole rounding family (ROUND / ROUNDDOWN /
25
+ // ROUNDUP / TRUNC / MROUND) and persists to the worksheet. Return
26
+ // `value` itself for well-behaved "nothing to do" edge cases (d very
27
+ // large is effectively "no rounding"; d very negative rounds to 0).
28
+ if (!Number.isFinite(value)) {
29
+ return value;
30
+ }
31
+ if (d >= 308) {
32
+ return value;
33
+ }
34
+ if (d <= -308) {
35
+ return round(0);
36
+ }
37
+ if (d >= 0) {
38
+ const factor = Math.pow(10, d);
39
+ if (!Number.isFinite(factor) || factor === 0) {
40
+ return value;
41
+ }
42
+ return round(value * factor) / factor;
43
+ }
44
+ const factor = Math.pow(10, -d);
45
+ if (!Number.isFinite(factor) || factor === 0) {
46
+ return value;
47
+ }
48
+ return round(value / factor) * factor;
49
+ }
50
+ /**
51
+ * Round half away from zero, matching Excel's ROUND semantics.
52
+ *
53
+ * JavaScript's `Math.round` rounds half toward +∞ (so `Math.round(-0.5)`
54
+ * returns `-0` instead of `-1`), whereas Excel rounds the magnitude and
55
+ * preserves the sign: `ROUND(-0.5, 0) = -1`. This helper implements the
56
+ * Excel rule by operating on the absolute value and restoring the sign.
57
+ */
58
+ function roundHalfAwayFromZero(n) {
59
+ return n < 0 ? -Math.round(-n) : Math.round(n);
60
+ }
61
+ // ============================================================================
62
+ // Trigonometric Functions
63
+ // ============================================================================
64
+ export const fnSIN = args => {
65
+ const n = argToNumber(args[0]);
66
+ return isError(n) ? n : rvNumber(Math.sin(n.value));
67
+ };
68
+ export const fnCOS = args => {
69
+ const n = argToNumber(args[0]);
70
+ return isError(n) ? n : rvNumber(Math.cos(n.value));
71
+ };
72
+ export const fnTAN = args => {
73
+ const n = argToNumber(args[0]);
74
+ return isError(n) ? n : rvNumber(Math.tan(n.value));
75
+ };
76
+ export const fnASIN = args => {
77
+ const n = argToNumber(args[0]);
78
+ if (isError(n)) {
79
+ return n;
80
+ }
81
+ if (n.value < -1 || n.value > 1) {
82
+ return ERRORS.NUM;
83
+ }
84
+ return rvNumber(Math.asin(n.value));
85
+ };
86
+ export const fnACOS = args => {
87
+ const n = argToNumber(args[0]);
88
+ if (isError(n)) {
89
+ return n;
90
+ }
91
+ if (n.value < -1 || n.value > 1) {
92
+ return ERRORS.NUM;
93
+ }
94
+ return rvNumber(Math.acos(n.value));
95
+ };
96
+ export const fnATAN = args => {
97
+ const n = argToNumber(args[0]);
98
+ return isError(n) ? n : rvNumber(Math.atan(n.value));
99
+ };
100
+ export const fnATAN2 = args => {
101
+ const x = argToNumber(args[0]);
102
+ if (isError(x)) {
103
+ return x;
104
+ }
105
+ const y = argToNumber(args[1]);
106
+ if (isError(y)) {
107
+ return y;
108
+ }
109
+ if (x.value === 0 && y.value === 0) {
110
+ return ERRORS.DIV0;
111
+ }
112
+ return rvNumber(Math.atan2(y.value, x.value));
113
+ };
114
+ export const fnSINH = args => {
115
+ const n = argToNumber(args[0]);
116
+ if (isError(n)) {
117
+ return n;
118
+ }
119
+ // sinh(x) overflows double for |x| > ~710. Excel returns #NUM! rather
120
+ // than a silent Infinity (which would poison downstream arithmetic).
121
+ const r = Math.sinh(n.value);
122
+ return isFinite(r) ? rvNumber(r) : ERRORS.NUM;
123
+ };
124
+ export const fnCOSH = args => {
125
+ const n = argToNumber(args[0]);
126
+ if (isError(n)) {
127
+ return n;
128
+ }
129
+ const r = Math.cosh(n.value);
130
+ return isFinite(r) ? rvNumber(r) : ERRORS.NUM;
131
+ };
132
+ export const fnTANH = args => {
133
+ const n = argToNumber(args[0]);
134
+ return isError(n) ? n : rvNumber(Math.tanh(n.value));
135
+ };
136
+ export const fnASINH = args => {
137
+ const n = argToNumber(args[0]);
138
+ return isError(n) ? n : rvNumber(Math.asinh(n.value));
139
+ };
140
+ export const fnACOSH = args => {
141
+ const n = argToNumber(args[0]);
142
+ if (isError(n)) {
143
+ return n;
144
+ }
145
+ if (n.value < 1) {
146
+ return ERRORS.NUM;
147
+ }
148
+ return rvNumber(Math.acosh(n.value));
149
+ };
150
+ export const fnATANH = args => {
151
+ const n = argToNumber(args[0]);
152
+ if (isError(n)) {
153
+ return n;
154
+ }
155
+ if (n.value <= -1 || n.value >= 1) {
156
+ return ERRORS.NUM;
157
+ }
158
+ return rvNumber(Math.atanh(n.value));
159
+ };
160
+ /**
161
+ * Secondary trigonometric family (SEC / CSC / COT and hyperbolic /
162
+ * inverse variants). None of these exist on the JavaScript Math object
163
+ * so we derive them from the standard sin / cos / tan primitives, with
164
+ * explicit guards at the discontinuities (π/2 for SEC, multiples of π
165
+ * for CSC / COT, zero for the H variants).
166
+ */
167
+ export const fnSEC = args => {
168
+ const n = argToNumber(args[0]);
169
+ if (isError(n)) {
170
+ return n;
171
+ }
172
+ const c = Math.cos(n.value);
173
+ return c === 0 ? ERRORS.DIV0 : rvNumber(1 / c);
174
+ };
175
+ export const fnCSC = args => {
176
+ const n = argToNumber(args[0]);
177
+ if (isError(n)) {
178
+ return n;
179
+ }
180
+ const s = Math.sin(n.value);
181
+ return s === 0 ? ERRORS.DIV0 : rvNumber(1 / s);
182
+ };
183
+ export const fnCOT = args => {
184
+ const n = argToNumber(args[0]);
185
+ if (isError(n)) {
186
+ return n;
187
+ }
188
+ const s = Math.sin(n.value);
189
+ if (s === 0) {
190
+ return ERRORS.DIV0;
191
+ }
192
+ return rvNumber(Math.cos(n.value) / s);
193
+ };
194
+ export const fnSECH = args => {
195
+ const n = argToNumber(args[0]);
196
+ if (isError(n)) {
197
+ return n;
198
+ }
199
+ // sech(x) = 1/cosh(x); cosh is never zero for real x, but it does
200
+ // overflow to Infinity for |x| > ~710 — return 0 (the mathematical
201
+ // limit) rather than dividing and producing NaN.
202
+ const c = Math.cosh(n.value);
203
+ return Number.isFinite(c) ? rvNumber(1 / c) : rvNumber(0);
204
+ };
205
+ export const fnCSCH = args => {
206
+ const n = argToNumber(args[0]);
207
+ if (isError(n)) {
208
+ return n;
209
+ }
210
+ if (n.value === 0) {
211
+ return ERRORS.DIV0;
212
+ }
213
+ const s = Math.sinh(n.value);
214
+ if (!Number.isFinite(s)) {
215
+ return rvNumber(0);
216
+ }
217
+ return rvNumber(1 / s);
218
+ };
219
+ export const fnCOTH = args => {
220
+ const n = argToNumber(args[0]);
221
+ if (isError(n)) {
222
+ return n;
223
+ }
224
+ if (n.value === 0) {
225
+ return ERRORS.DIV0;
226
+ }
227
+ return rvNumber(1 / Math.tanh(n.value));
228
+ };
229
+ export const fnACOT = args => {
230
+ const n = argToNumber(args[0]);
231
+ if (isError(n)) {
232
+ return n;
233
+ }
234
+ // Excel's ACOT returns values in (0, π), not (-π/2, π/2) like
235
+ // Math.atan. Map: ACOT(x) = π/2 − atan(x).
236
+ return rvNumber(Math.PI / 2 - Math.atan(n.value));
237
+ };
238
+ export const fnACOTH = args => {
239
+ const n = argToNumber(args[0]);
240
+ if (isError(n)) {
241
+ return n;
242
+ }
243
+ // Defined only for |x| > 1.
244
+ if (n.value >= -1 && n.value <= 1) {
245
+ return ERRORS.NUM;
246
+ }
247
+ return rvNumber(0.5 * Math.log((n.value + 1) / (n.value - 1)));
248
+ };
249
+ // ============================================================================
250
+ // Math / Aggregate Functions
251
+ // ============================================================================
252
+ export const fnSUM = args => {
253
+ const nums = flattenNumbers(args);
254
+ const err = firstError(nums);
255
+ if (err) {
256
+ return err;
257
+ }
258
+ let sum = 0;
259
+ for (const n of nums) {
260
+ sum += n.value;
261
+ }
262
+ // Fail fast on overflow to Infinity; otherwise the result leaks into
263
+ // any formula that aggregates it (AVERAGE, STDEV, etc.) and those
264
+ // downstream callers would then fan #NUM! out across the graph.
265
+ return Number.isFinite(sum) ? rvNumber(sum) : ERRORS.NUM;
266
+ };
267
+ export const fnAVERAGE = args => {
268
+ const nums = flattenNumbers(args);
269
+ const err = firstError(nums);
270
+ if (err) {
271
+ return err;
272
+ }
273
+ if (nums.length === 0) {
274
+ return ERRORS.DIV0;
275
+ }
276
+ let sum = 0;
277
+ for (const n of nums) {
278
+ sum += n.value;
279
+ }
280
+ const avg = sum / nums.length;
281
+ return Number.isFinite(avg) ? rvNumber(avg) : ERRORS.NUM;
282
+ };
283
+ export const fnMIN = args => {
284
+ const nums = flattenNumbers(args);
285
+ const err = firstError(nums);
286
+ if (err) {
287
+ return err;
288
+ }
289
+ if (nums.length === 0) {
290
+ return rvNumber(0);
291
+ }
292
+ let min = Infinity;
293
+ for (const n of nums) {
294
+ if (n.value < min) {
295
+ min = n.value;
296
+ }
297
+ }
298
+ return rvNumber(min);
299
+ };
300
+ export const fnMAX = args => {
301
+ const nums = flattenNumbers(args);
302
+ const err = firstError(nums);
303
+ if (err) {
304
+ return err;
305
+ }
306
+ if (nums.length === 0) {
307
+ return rvNumber(0);
308
+ }
309
+ let max = -Infinity;
310
+ for (const n of nums) {
311
+ if (n.value > max) {
312
+ max = n.value;
313
+ }
314
+ }
315
+ return rvNumber(max);
316
+ };
317
+ export const fnCOUNT = args => {
318
+ let count = 0;
319
+ const all = flattenAll(args);
320
+ for (const v of all) {
321
+ if (v.kind === RVKind.Number) {
322
+ count++;
323
+ }
324
+ }
325
+ return rvNumber(count);
326
+ };
327
+ export const fnCOUNTA = args => {
328
+ let count = 0;
329
+ const all = flattenAll(args);
330
+ for (const v of all) {
331
+ // Count everything that is not blank and not empty string
332
+ if (v.kind !== RVKind.Blank && !(v.kind === RVKind.String && v.value === "")) {
333
+ count++;
334
+ }
335
+ }
336
+ return rvNumber(count);
337
+ };
338
+ export const fnCOUNTBLANK = args => {
339
+ let count = 0;
340
+ const all = flattenAll(args);
341
+ for (const v of all) {
342
+ if (v.kind === RVKind.Blank || (v.kind === RVKind.String && v.value === "")) {
343
+ count++;
344
+ }
345
+ }
346
+ return rvNumber(count);
347
+ };
348
+ export const fnPRODUCT = args => {
349
+ const nums = flattenNumbers(args);
350
+ const err = firstError(nums);
351
+ if (err) {
352
+ return err;
353
+ }
354
+ if (nums.length === 0) {
355
+ return rvNumber(0);
356
+ }
357
+ let product = 1;
358
+ for (const n of nums) {
359
+ product *= n.value;
360
+ }
361
+ // Excel surfaces an overflow as #NUM! rather than letting Infinity
362
+ // propagate into subsequent arithmetic.
363
+ return isFinite(product) ? rvNumber(product) : ERRORS.NUM;
364
+ };
365
+ export const fnSUMPRODUCT = args => {
366
+ if (args.length === 0) {
367
+ return ERRORS.VALUE;
368
+ }
369
+ // Promote scalar args to 1x1 arrays. Excel allows a scalar (or 1x1 array)
370
+ // to broadcast to the surrounding array's shape, e.g. SUMPRODUCT(1, A1:A10)
371
+ // is equivalent to SUMPRODUCT(A1:A10). All non-broadcast arrays must share
372
+ // the same height/width — only 1x1 may be broadcast.
373
+ const arrays = [];
374
+ for (const a of args) {
375
+ if (isArray(a)) {
376
+ arrays.push(a);
377
+ }
378
+ else {
379
+ arrays.push(rvArray([[topLeft(a)]]));
380
+ }
381
+ }
382
+ // Find the target dimensions: the max height/width across all non-1x1 arrays.
383
+ // 1x1 arrays are eligible to broadcast to whatever size is chosen.
384
+ let rows = 1;
385
+ let cols = 1;
386
+ for (const arr of arrays) {
387
+ if (arr.height !== 1 || arr.width !== 1) {
388
+ if (rows === 1 && cols === 1) {
389
+ rows = arr.height;
390
+ cols = arr.width;
391
+ }
392
+ else if (arr.height !== rows || arr.width !== cols) {
393
+ // Two different non-1x1 shapes — incompatible.
394
+ return ERRORS.VALUE;
395
+ }
396
+ }
397
+ }
398
+ let sum = 0;
399
+ for (let r = 0; r < rows; r++) {
400
+ for (let c = 0; c < cols; c++) {
401
+ let product = 1;
402
+ for (const arr of arrays) {
403
+ // Broadcast 1x1 arrays to the target cell position.
404
+ const val = arr.height === 1 && arr.width === 1 ? arr.rows[0][0] : arr.rows[r][c];
405
+ if (val.kind === RVKind.Error) {
406
+ return val;
407
+ }
408
+ const n = val.kind === RVKind.Number
409
+ ? val.value
410
+ : val.kind === RVKind.Boolean
411
+ ? val.value
412
+ ? 1
413
+ : 0
414
+ : 0;
415
+ product *= n;
416
+ }
417
+ sum += product;
418
+ }
419
+ }
420
+ if (!Number.isFinite(sum)) {
421
+ return ERRORS.NUM;
422
+ }
423
+ return rvNumber(sum);
424
+ };
425
+ // ============================================================================
426
+ // Math Functions
427
+ // ============================================================================
428
+ export const fnABS = args => {
429
+ const n = argToNumber(args[0]);
430
+ return isError(n) ? n : rvNumber(Math.abs(n.value));
431
+ };
432
+ export const fnCEILING = args => {
433
+ const num = argToNumber(args[0]);
434
+ if (isError(num)) {
435
+ return num;
436
+ }
437
+ const sigRV = args.length > 1 ? argToNumber(args[1]) : rvNumber(1);
438
+ if (isError(sigRV)) {
439
+ return sigRV;
440
+ }
441
+ const sig = sigRV.value;
442
+ if (sig === 0) {
443
+ return rvNumber(0);
444
+ }
445
+ // CEILING (the non-MATH variant) requires number and significance to share
446
+ // a sign. `CEILING(2, -1)` in Excel is #NUM!; only `CEILING.MATH` tolerates
447
+ // a negative significance with a positive number.
448
+ if (num.value !== 0 && Math.sign(num.value) !== Math.sign(sig)) {
449
+ return ERRORS.NUM;
450
+ }
451
+ return rvNumber(Math.ceil(num.value / sig) * sig);
452
+ };
453
+ export const fnFLOOR = args => {
454
+ const num = argToNumber(args[0]);
455
+ if (isError(num)) {
456
+ return num;
457
+ }
458
+ const sigRV = args.length > 1 ? argToNumber(args[1]) : rvNumber(1);
459
+ if (isError(sigRV)) {
460
+ return sigRV;
461
+ }
462
+ const sig = sigRV.value;
463
+ if (sig === 0) {
464
+ return ERRORS.DIV0;
465
+ }
466
+ if (num.value !== 0 && Math.sign(num.value) !== Math.sign(sig)) {
467
+ return ERRORS.NUM;
468
+ }
469
+ return rvNumber(Math.floor(num.value / sig) * sig);
470
+ };
471
+ export const fnINT = args => {
472
+ const n = argToNumber(args[0]);
473
+ return isError(n) ? n : rvNumber(Math.floor(n.value));
474
+ };
475
+ export const fnMOD = args => {
476
+ const num = argToNumber(args[0]);
477
+ if (isError(num)) {
478
+ return num;
479
+ }
480
+ const div = argToNumber(args[1]);
481
+ if (isError(div)) {
482
+ return div;
483
+ }
484
+ if (div.value === 0) {
485
+ return ERRORS.DIV0;
486
+ }
487
+ return rvNumber(num.value - div.value * Math.floor(num.value / div.value));
488
+ };
489
+ export const fnPOWER = args => {
490
+ const base = argToNumber(args[0]);
491
+ if (isError(base)) {
492
+ return base;
493
+ }
494
+ const exp = argToNumber(args[1]);
495
+ if (isError(exp)) {
496
+ return exp;
497
+ }
498
+ // Distinguish the two degenerate-base cases Excel handles separately:
499
+ // POWER(0, 0) → 1 (by convention, matches Excel)
500
+ // POWER(0, <0) → #DIV/0!
501
+ // POWER(<0, non-int) → #NUM! (complex result; Math.pow returns NaN)
502
+ if (base.value === 0) {
503
+ if (exp.value < 0) {
504
+ return ERRORS.DIV0;
505
+ }
506
+ if (exp.value === 0) {
507
+ return rvNumber(1);
508
+ }
509
+ }
510
+ const result = Math.pow(base.value, exp.value);
511
+ if (Number.isNaN(result)) {
512
+ return ERRORS.NUM;
513
+ }
514
+ return !isFinite(result) ? ERRORS.NUM : rvNumber(result);
515
+ };
516
+ export const fnROUND = args => {
517
+ const num = argToNumber(args[0]);
518
+ if (isError(num)) {
519
+ return num;
520
+ }
521
+ const digitsRV = args.length > 1 ? argToNumber(args[1]) : rvNumber(0);
522
+ if (isError(digitsRV)) {
523
+ return digitsRV;
524
+ }
525
+ return rvNumber(applyRounding(num.value, digitsRV.value, roundHalfAwayFromZero));
526
+ };
527
+ export const fnROUNDDOWN = args => {
528
+ const num = argToNumber(args[0]);
529
+ if (isError(num)) {
530
+ return num;
531
+ }
532
+ const digitsRV = args.length > 1 ? argToNumber(args[1]) : rvNumber(0);
533
+ if (isError(digitsRV)) {
534
+ return digitsRV;
535
+ }
536
+ return rvNumber(applyRounding(num.value, digitsRV.value, Math.trunc));
537
+ };
538
+ export const fnROUNDUP = args => {
539
+ const num = argToNumber(args[0]);
540
+ if (isError(num)) {
541
+ return num;
542
+ }
543
+ const digitsRV = args.length > 1 ? argToNumber(args[1]) : rvNumber(0);
544
+ if (isError(digitsRV)) {
545
+ return digitsRV;
546
+ }
547
+ // ROUNDUP rounds away from zero for the fractional part at the requested
548
+ // precision. We emulate this by ceiling the scaled absolute value and
549
+ // restoring the sign.
550
+ const sign = num.value >= 0 ? 1 : -1;
551
+ const rounded = applyRounding(Math.abs(num.value), digitsRV.value, Math.ceil);
552
+ return rvNumber(sign * rounded);
553
+ };
554
+ export const fnSQRT = args => {
555
+ const n = argToNumber(args[0]);
556
+ if (isError(n)) {
557
+ return n;
558
+ }
559
+ if (n.value < 0) {
560
+ return ERRORS.NUM;
561
+ }
562
+ return rvNumber(Math.sqrt(n.value));
563
+ };
564
+ /**
565
+ * SQRTPI(number) — returns the square root of (number × π). Useful in
566
+ * statistical formulas and Gauss integrals.
567
+ */
568
+ export const fnSQRTPI = args => {
569
+ const n = argToNumber(args[0]);
570
+ if (isError(n)) {
571
+ return n;
572
+ }
573
+ if (n.value < 0) {
574
+ return ERRORS.NUM;
575
+ }
576
+ return rvNumber(Math.sqrt(n.value * Math.PI));
577
+ };
578
+ export const fnLN = args => {
579
+ const n = argToNumber(args[0]);
580
+ if (isError(n)) {
581
+ return n;
582
+ }
583
+ if (n.value <= 0) {
584
+ return ERRORS.NUM;
585
+ }
586
+ return rvNumber(Math.log(n.value));
587
+ };
588
+ export const fnLOG = args => {
589
+ const n = argToNumber(args[0]);
590
+ if (isError(n)) {
591
+ return n;
592
+ }
593
+ if (n.value <= 0) {
594
+ return ERRORS.NUM;
595
+ }
596
+ const baseRV = args.length > 1 ? argToNumber(args[1]) : rvNumber(10);
597
+ if (isError(baseRV)) {
598
+ return baseRV;
599
+ }
600
+ if (baseRV.value <= 0 || baseRV.value === 1) {
601
+ return ERRORS.NUM;
602
+ }
603
+ return rvNumber(Math.log(n.value) / Math.log(baseRV.value));
604
+ };
605
+ export const fnLOG10 = args => {
606
+ const n = argToNumber(args[0]);
607
+ if (isError(n)) {
608
+ return n;
609
+ }
610
+ if (n.value <= 0) {
611
+ return ERRORS.NUM;
612
+ }
613
+ return rvNumber(Math.log10(n.value));
614
+ };
615
+ export const fnEXP = args => {
616
+ const n = argToNumber(args[0]);
617
+ if (isError(n)) {
618
+ return n;
619
+ }
620
+ // EXP(~710) overflows double to Infinity. Excel returns #NUM! in that
621
+ // regime rather than letting the non-finite result propagate.
622
+ const r = Math.exp(n.value);
623
+ return isFinite(r) ? rvNumber(r) : ERRORS.NUM;
624
+ };
625
+ export const fnPI = () => rvNumber(Math.PI);
626
+ export const fnRAND = () => rvNumber(Math.random());
627
+ export const fnRANDBETWEEN = args => {
628
+ const bottom = argToNumber(args[0]);
629
+ if (isError(bottom)) {
630
+ return bottom;
631
+ }
632
+ const top = argToNumber(args[1]);
633
+ if (isError(top)) {
634
+ return top;
635
+ }
636
+ const lo = Math.ceil(bottom.value);
637
+ const hi = Math.floor(top.value);
638
+ // Excel returns #NUM! when bottom > top; otherwise the formula below would
639
+ // produce a garbage integer from a negative range.
640
+ if (lo > hi) {
641
+ return ERRORS.NUM;
642
+ }
643
+ return rvNumber(Math.floor(Math.random() * (hi - lo + 1)) + lo);
644
+ };
645
+ export const fnSIGN = args => {
646
+ const n = argToNumber(args[0]);
647
+ if (isError(n)) {
648
+ return n;
649
+ }
650
+ // `Math.sign(-0)` returns `-0` (preserving IEEE-754 sign bit). Excel's
651
+ // SIGN normalises zero to +0 (since the documented result for a zero
652
+ // input is 0, not distinguishing signed zero). The `|| 0` collapses
653
+ // both ±0 into `+0` while leaving ±1 untouched.
654
+ return rvNumber(Math.sign(n.value) || 0);
655
+ };
656
+ // ============================================================================
657
+ // Additional Math Functions
658
+ // ============================================================================
659
+ export const fnTRUNC = args => {
660
+ const num = argToNumber(args[0]);
661
+ if (isError(num)) {
662
+ return num;
663
+ }
664
+ const digitsRV = args.length > 1 ? argToNumber(args[1]) : rvNumber(0);
665
+ if (isError(digitsRV)) {
666
+ return digitsRV;
667
+ }
668
+ return rvNumber(applyRounding(num.value, digitsRV.value, Math.trunc));
669
+ };
670
+ export const fnSUMSQ = args => {
671
+ const nums = flattenNumbers(args);
672
+ const err = firstError(nums);
673
+ if (err) {
674
+ return err;
675
+ }
676
+ let sum = 0;
677
+ for (const n of nums) {
678
+ sum += n.value ** 2;
679
+ }
680
+ return isFinite(sum) ? rvNumber(sum) : ERRORS.NUM;
681
+ };
682
+ export const fnGCD = args => {
683
+ const nums = flattenNumbers(args);
684
+ const err = firstError(nums);
685
+ if (err) {
686
+ return err;
687
+ }
688
+ if (nums.length === 0) {
689
+ return rvNumber(0);
690
+ }
691
+ // Excel rejects any negative argument with #NUM!; truncate toward zero
692
+ // for non-integer positives (previous `Math.floor(-5.5) = -6` then
693
+ // `Math.abs` produced 6 instead of #NUM!).
694
+ const coerce = (v) => {
695
+ if (v < 0) {
696
+ return ERRORS.NUM;
697
+ }
698
+ return Math.trunc(v);
699
+ };
700
+ const first = coerce(nums[0].value);
701
+ if (typeof first !== "number") {
702
+ return first;
703
+ }
704
+ let result = first;
705
+ for (let i = 1; i < nums.length; i++) {
706
+ const bi = coerce(nums[i].value);
707
+ if (typeof bi !== "number") {
708
+ return bi;
709
+ }
710
+ let b = bi;
711
+ while (b) {
712
+ const t = b;
713
+ b = result % b;
714
+ result = t;
715
+ }
716
+ }
717
+ return rvNumber(result);
718
+ };
719
+ export const fnLCM = args => {
720
+ const nums = flattenNumbers(args);
721
+ const err = firstError(nums);
722
+ if (err) {
723
+ return err;
724
+ }
725
+ if (nums.length === 0) {
726
+ return rvNumber(0);
727
+ }
728
+ const coerce = (v) => {
729
+ if (v < 0) {
730
+ return ERRORS.NUM;
731
+ }
732
+ return Math.trunc(v);
733
+ };
734
+ const first = coerce(nums[0].value);
735
+ if (typeof first !== "number") {
736
+ return first;
737
+ }
738
+ let result = first;
739
+ for (let i = 1; i < nums.length; i++) {
740
+ const bi = coerce(nums[i].value);
741
+ if (typeof bi !== "number") {
742
+ return bi;
743
+ }
744
+ const b = bi;
745
+ if (result === 0 && b === 0) {
746
+ result = 0;
747
+ }
748
+ else {
749
+ let g = result;
750
+ let t = b;
751
+ while (t) {
752
+ const tmp = t;
753
+ t = g % t;
754
+ g = tmp;
755
+ }
756
+ result = (result * b) / g;
757
+ }
758
+ }
759
+ return rvNumber(result);
760
+ };
761
+ // ============================================================================
762
+ // More Math Functions
763
+ // ============================================================================
764
+ export const fnEVEN = args => {
765
+ const n = argToNumber(args[0]);
766
+ if (isError(n)) {
767
+ return n;
768
+ }
769
+ const sign = n.value >= 0 ? 1 : -1;
770
+ const abs = Math.abs(n.value);
771
+ const ceil = Math.ceil(abs);
772
+ return rvNumber(sign * (ceil % 2 === 0 ? ceil : ceil + 1));
773
+ };
774
+ export const fnODD = args => {
775
+ const n = argToNumber(args[0]);
776
+ if (isError(n)) {
777
+ return n;
778
+ }
779
+ if (n.value === 0) {
780
+ return rvNumber(1);
781
+ }
782
+ const sign = n.value >= 0 ? 1 : -1;
783
+ const abs = Math.abs(n.value);
784
+ const ceil = Math.ceil(abs);
785
+ return rvNumber(sign * (ceil % 2 === 1 ? ceil : ceil + 1));
786
+ };
787
+ export const fnMROUND = args => {
788
+ const num = argToNumber(args[0]);
789
+ if (isError(num)) {
790
+ return num;
791
+ }
792
+ const multiple = argToNumber(args[1]);
793
+ if (isError(multiple)) {
794
+ return multiple;
795
+ }
796
+ if (multiple.value === 0) {
797
+ return rvNumber(0);
798
+ }
799
+ if ((num.value > 0 && multiple.value < 0) || (num.value < 0 && multiple.value > 0)) {
800
+ return ERRORS.NUM;
801
+ }
802
+ return rvNumber(roundHalfAwayFromZero(num.value / multiple.value) * multiple.value);
803
+ };
804
+ export const fnQUOTIENT = args => {
805
+ const num = argToNumber(args[0]);
806
+ if (isError(num)) {
807
+ return num;
808
+ }
809
+ const den = argToNumber(args[1]);
810
+ if (isError(den)) {
811
+ return den;
812
+ }
813
+ if (den.value === 0) {
814
+ return ERRORS.DIV0;
815
+ }
816
+ return rvNumber(Math.trunc(num.value / den.value));
817
+ };
818
+ export const fnBASE = args => {
819
+ const num = argToNumber(args[0]);
820
+ if (isError(num)) {
821
+ return num;
822
+ }
823
+ const radix = argToNumber(args[1]);
824
+ if (isError(radix)) {
825
+ return radix;
826
+ }
827
+ if (radix.value < 2 || radix.value > 36) {
828
+ return ERRORS.NUM;
829
+ }
830
+ const minLenRV = args.length > 2 ? argToNumber(args[2]) : rvNumber(0);
831
+ if (isError(minLenRV)) {
832
+ return minLenRV;
833
+ }
834
+ const result = Math.floor(num.value).toString(Math.floor(radix.value)).toUpperCase();
835
+ return rvString(minLenRV.value > 0 ? result.padStart(minLenRV.value, "0") : result);
836
+ };
837
+ export const fnDECIMAL = args => {
838
+ const e = topLeft(args[0]);
839
+ if (e.kind === RVKind.Error) {
840
+ return e;
841
+ }
842
+ const text = toStringRV(e);
843
+ const radix = argToNumber(args[1]);
844
+ if (isError(radix)) {
845
+ return radix;
846
+ }
847
+ if (radix.value < 2 || radix.value > 36) {
848
+ return ERRORS.NUM;
849
+ }
850
+ // `parseInt("1G", 16)` returns 1 because JavaScript silently stops at
851
+ // the first invalid digit. Excel's DECIMAL requires every character in
852
+ // the input to be a valid digit for the given radix, so we validate
853
+ // strictly before delegating.
854
+ const base = Math.floor(radix.value);
855
+ const trimmed = text.trim();
856
+ if (trimmed === "") {
857
+ return ERRORS.NUM;
858
+ }
859
+ // Digit alphabet up to base 36: 0-9, A-Z (case-insensitive).
860
+ const match = /^[+-]?([0-9A-Za-z]+)$/.exec(trimmed);
861
+ if (!match) {
862
+ return ERRORS.NUM;
863
+ }
864
+ const digits = match[1].toUpperCase();
865
+ for (const ch of digits) {
866
+ const d = ch >= "0" && ch <= "9" ? ch.charCodeAt(0) - 48 : ch.charCodeAt(0) - 55;
867
+ if (d < 0 || d >= base) {
868
+ return ERRORS.NUM;
869
+ }
870
+ }
871
+ const result = parseInt(trimmed, base);
872
+ if (isNaN(result)) {
873
+ return ERRORS.NUM;
874
+ }
875
+ return rvNumber(result);
876
+ };
877
+ export const fnROMAN = args => {
878
+ const num = argToNumber(args[0]);
879
+ if (isError(num)) {
880
+ return num;
881
+ }
882
+ let n = Math.floor(num.value);
883
+ if (n < 0 || n > 3999) {
884
+ return ERRORS.VALUE;
885
+ }
886
+ if (n === 0) {
887
+ return rvString("");
888
+ }
889
+ // The optional `form` argument (0=classic through 4=simplified) controls
890
+ // how far Excel will collapse repeated characters into subtractive pairs.
891
+ // Higher forms introduce additional patterns beyond the classic IV/IX/
892
+ // XL/XC/CD/CM pairs — e.g. form 1 allows LM for 950, form 4 uses the
893
+ // maximally short forms. A value of TRUE (1) / FALSE (0) maps to form 0
894
+ // / 4 the way Excel does.
895
+ let form = 0;
896
+ if (args.length > 1 && args[1].kind !== RVKind.Blank) {
897
+ const f = argToNumber(args[1]);
898
+ if (isError(f)) {
899
+ return f;
900
+ }
901
+ if (f.value === 1 && f.value === 1) {
902
+ // Boolean inputs flow through argToNumber as 0/1 already.
903
+ }
904
+ form = Math.floor(f.value);
905
+ if (form < 0 || form > 4) {
906
+ return ERRORS.VALUE;
907
+ }
908
+ }
909
+ // Classic table (form 0) — subtractive pairs IV, IX, XL, XC, CD, CM.
910
+ const vals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
911
+ const syms = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"];
912
+ // Forms 1-4 progressively introduce aggressive subtractive pairs
913
+ // (LM=950, VC=95, IC=99, etc.). Each extra must only trigger at its
914
+ // `minForm` or higher — and must NOT duplicate a value the classic
915
+ // table already covers, or we'd emit the same subtractive pair twice
916
+ // per occurrence (e.g. 1999 would become "CMCMCXCIX" instead of
917
+ // "MCMXCIX").
918
+ const extraByForm = [
919
+ { value: 995, sym: "VM", minForm: 4 },
920
+ { value: 990, sym: "XM", minForm: 3 },
921
+ { value: 950, sym: "LM", minForm: 1 },
922
+ { value: 495, sym: "VD", minForm: 4 },
923
+ { value: 490, sym: "XD", minForm: 3 },
924
+ { value: 450, sym: "LD", minForm: 1 },
925
+ { value: 99, sym: "IC", minForm: 2 },
926
+ { value: 95, sym: "VC", minForm: 1 },
927
+ { value: 49, sym: "IL", minForm: 2 },
928
+ { value: 45, sym: "VL", minForm: 1 }
929
+ ];
930
+ let result = "";
931
+ // Merge: walk from largest remainder, trying any applicable extra then
932
+ // the classic table entry.
933
+ while (n > 0) {
934
+ let matched = false;
935
+ for (const ex of extraByForm) {
936
+ if (form >= ex.minForm && n >= ex.value) {
937
+ result += ex.sym;
938
+ n -= ex.value;
939
+ matched = true;
940
+ break;
941
+ }
942
+ }
943
+ if (matched) {
944
+ continue;
945
+ }
946
+ for (let i = 0; i < vals.length; i++) {
947
+ if (n >= vals[i]) {
948
+ result += syms[i];
949
+ n -= vals[i];
950
+ matched = true;
951
+ break;
952
+ }
953
+ }
954
+ if (!matched) {
955
+ // Safety net — should not happen since the classic table covers 1.
956
+ break;
957
+ }
958
+ }
959
+ return rvString(result);
960
+ };
961
+ export const fnARABIC = args => {
962
+ const s = topLeft(args[0]);
963
+ if (s.kind === RVKind.Error) {
964
+ return s;
965
+ }
966
+ const text = toStringRV(s).toUpperCase().trim();
967
+ if (text === "") {
968
+ return rvNumber(0);
969
+ }
970
+ const romanMap = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 };
971
+ let result = 0;
972
+ for (let i = 0; i < text.length; i++) {
973
+ const current = romanMap[text[i]];
974
+ const next = romanMap[text[i + 1]];
975
+ if (current === undefined) {
976
+ return ERRORS.VALUE;
977
+ }
978
+ if (next && current < next) {
979
+ result -= current;
980
+ }
981
+ else {
982
+ result += current;
983
+ }
984
+ }
985
+ return rvNumber(result);
986
+ };
987
+ export const fnDEGREES = args => {
988
+ const n = argToNumber(args[0]);
989
+ return isError(n) ? n : rvNumber((n.value * 180) / Math.PI);
990
+ };
991
+ export const fnRADIANS = args => {
992
+ const n = argToNumber(args[0]);
993
+ return isError(n) ? n : rvNumber((n.value * Math.PI) / 180);
994
+ };
995
+ /**
996
+ * Shared driver for SUMX2MY2 / SUMX2PY2 / SUMXMY2. Walks two arrays in
997
+ * lock-step and reduces `combine(x, y)` across matching numeric cells.
998
+ * Non-numeric cells are skipped (Excel behaviour) and errors propagate.
999
+ */
1000
+ function sumPairedArrays(args, combine) {
1001
+ const a0 = args[0];
1002
+ const a1 = args[1];
1003
+ if (a0.kind !== RVKind.Array || a1.kind !== RVKind.Array) {
1004
+ return ERRORS.VALUE;
1005
+ }
1006
+ const h = Math.min(a0.height, a1.height);
1007
+ const w = Math.min(a0.width, a1.width);
1008
+ let sum = 0;
1009
+ for (let r = 0; r < h; r++) {
1010
+ for (let c = 0; c < w; c++) {
1011
+ const x = a0.rows[r][c];
1012
+ const y = a1.rows[r][c];
1013
+ if (x.kind === RVKind.Error) {
1014
+ return x;
1015
+ }
1016
+ if (y.kind === RVKind.Error) {
1017
+ return y;
1018
+ }
1019
+ if (x.kind !== RVKind.Number || y.kind !== RVKind.Number) {
1020
+ continue;
1021
+ }
1022
+ sum += combine(x.value, y.value);
1023
+ }
1024
+ }
1025
+ return rvNumber(sum);
1026
+ }
1027
+ export const fnSUMX2MY2 = args => sumPairedArrays(args, (x, y) => x * x - y * y);
1028
+ export const fnSUMX2PY2 = args => sumPairedArrays(args, (x, y) => x * x + y * y);
1029
+ export const fnSUMXMY2 = args => sumPairedArrays(args, (x, y) => (x - y) ** 2);
1030
+ export const fnMULTINOMIAL = args => {
1031
+ const nums = flattenNumbers(args);
1032
+ const err = firstError(nums);
1033
+ if (err) {
1034
+ return err;
1035
+ }
1036
+ // Work in log space so the numerator (sum! ≈ up to ~1e307 around sum = 170)
1037
+ // doesn't overflow before we divide. Summing logs avoids the NaN case
1038
+ // `Infinity / Infinity` that the direct product formulation would hit
1039
+ // for large inputs.
1040
+ let sum = 0;
1041
+ let lnDenom = 0;
1042
+ for (const n of nums) {
1043
+ const ni = Math.floor(n.value);
1044
+ if (ni < 0) {
1045
+ return ERRORS.NUM;
1046
+ }
1047
+ sum += ni;
1048
+ for (let i = 2; i <= ni; i++) {
1049
+ lnDenom += Math.log(i);
1050
+ }
1051
+ }
1052
+ let lnNumer = 0;
1053
+ for (let i = 2; i <= sum; i++) {
1054
+ lnNumer += Math.log(i);
1055
+ }
1056
+ const result = Math.exp(lnNumer - lnDenom);
1057
+ if (!isFinite(result)) {
1058
+ return ERRORS.NUM;
1059
+ }
1060
+ // Round to the nearest integer — the log-exp round-trip introduces sub-ulp
1061
+ // noise that would otherwise leave us with things like `20.0000000001`.
1062
+ return rvNumber(Math.round(result));
1063
+ };
1064
+ export const fnFACT = args => {
1065
+ const n = argToNumber(args[0]);
1066
+ if (isError(n)) {
1067
+ return n;
1068
+ }
1069
+ const num = Math.floor(n.value);
1070
+ if (num < 0) {
1071
+ return ERRORS.NUM;
1072
+ }
1073
+ if (num > 170) {
1074
+ return ERRORS.NUM;
1075
+ }
1076
+ let result = 1;
1077
+ for (let i = 2; i <= num; i++) {
1078
+ result *= i;
1079
+ }
1080
+ return rvNumber(result);
1081
+ };
1082
+ export const fnFACTDOUBLE = args => {
1083
+ const n = argToNumber(args[0]);
1084
+ if (isError(n)) {
1085
+ return n;
1086
+ }
1087
+ const num = Math.floor(n.value);
1088
+ if (num < -1) {
1089
+ return ERRORS.NUM;
1090
+ }
1091
+ if (num <= 0) {
1092
+ return rvNumber(1);
1093
+ }
1094
+ let result = 1;
1095
+ for (let i = num; i > 0; i -= 2) {
1096
+ result *= i;
1097
+ if (!isFinite(result)) {
1098
+ return ERRORS.NUM;
1099
+ }
1100
+ }
1101
+ return rvNumber(result);
1102
+ };
1103
+ export const fnCOMBIN = args => {
1104
+ const nRV = argToNumber(args[0]);
1105
+ if (isError(nRV)) {
1106
+ return nRV;
1107
+ }
1108
+ const kRV = argToNumber(args[1]);
1109
+ if (isError(kRV)) {
1110
+ return kRV;
1111
+ }
1112
+ const ni = Math.floor(nRV.value);
1113
+ const ki = Math.floor(kRV.value);
1114
+ if (ni < 0 || ki < 0 || ki > ni) {
1115
+ return ERRORS.NUM;
1116
+ }
1117
+ // Use the `C(n, k) = C(n, k-1) * (n-k+1)/k` recurrence and pick the
1118
+ // smaller of k / (n-k) so the loop runs at most n/2 iterations. The
1119
+ // interleaved divide keeps the running value bounded and preserves
1120
+ // near-full double precision until the final product overflows
1121
+ // magnitude ~1e308 (at which point we surface #NUM!).
1122
+ const kEff = Math.min(ki, ni - ki);
1123
+ let result = 1;
1124
+ for (let i = 0; i < kEff; i++) {
1125
+ result = (result * (ni - i)) / (i + 1);
1126
+ if (!isFinite(result)) {
1127
+ return ERRORS.NUM;
1128
+ }
1129
+ }
1130
+ // Excel returns an integer for COMBIN when the value fits in a double
1131
+ // exactly (≤ 2^53); beyond that (e.g. COMBIN(100, 50) ≈ 1e29) the
1132
+ // result is fundamentally approximate, so rounding only makes sense
1133
+ // below 2^53.
1134
+ return rvNumber(result < 9.007199254740992e15 ? Math.round(result) : result);
1135
+ };
1136
+ export const fnCOMBINA = args => {
1137
+ const nRV = argToNumber(args[0]);
1138
+ if (isError(nRV)) {
1139
+ return nRV;
1140
+ }
1141
+ const kRV = argToNumber(args[1]);
1142
+ if (isError(kRV)) {
1143
+ return kRV;
1144
+ }
1145
+ // Special case: Excel's COMBINA(0, 0) is 1 even though the delegated
1146
+ // `COMBIN(-1, 0)` would flag #NUM! under our generic validation.
1147
+ if (nRV.value === 0 && kRV.value === 0) {
1148
+ return rvNumber(1);
1149
+ }
1150
+ return fnCOMBIN([rvNumber(nRV.value + kRV.value - 1), kRV]);
1151
+ };
1152
+ export const fnPERMUT = args => {
1153
+ const nRV = argToNumber(args[0]);
1154
+ if (isError(nRV)) {
1155
+ return nRV;
1156
+ }
1157
+ const kRV = argToNumber(args[1]);
1158
+ if (isError(kRV)) {
1159
+ return kRV;
1160
+ }
1161
+ const ni = Math.floor(nRV.value);
1162
+ const ki = Math.floor(kRV.value);
1163
+ if (ni < 0 || ki < 0 || ki > ni) {
1164
+ return ERRORS.NUM;
1165
+ }
1166
+ let result = 1;
1167
+ for (let i = 0; i < ki; i++) {
1168
+ result *= ni - i;
1169
+ }
1170
+ return rvNumber(result);
1171
+ };
1172
+ // ============================================================================
1173
+ // Matrix functions: MMULT, MDETERM, MINVERSE, MUNIT
1174
+ // ============================================================================
1175
+ /**
1176
+ * Extract a numeric matrix from an ArrayValue. Returns #VALUE! when any
1177
+ * cell is non-numeric or an error propagates.
1178
+ */
1179
+ function asNumericMatrix(v) {
1180
+ if (!isArray(v)) {
1181
+ return ERRORS.VALUE;
1182
+ }
1183
+ const arr = v;
1184
+ const out = [];
1185
+ for (const row of arr.rows) {
1186
+ const r = [];
1187
+ for (const cell of row) {
1188
+ if (cell.kind === RVKind.Error) {
1189
+ return cell;
1190
+ }
1191
+ if (cell.kind !== RVKind.Number) {
1192
+ return ERRORS.VALUE;
1193
+ }
1194
+ r.push(cell.value);
1195
+ }
1196
+ out.push(r);
1197
+ }
1198
+ return out;
1199
+ }
1200
+ /**
1201
+ * MMULT(array1, array2) — matrix product. Dimensions must be
1202
+ * (m×k) × (k×n) = (m×n); mismatched sizes return #VALUE!.
1203
+ */
1204
+ export const fnMMULT = args => {
1205
+ const a = asNumericMatrix(args[0]);
1206
+ if ("kind" in a) {
1207
+ return a;
1208
+ }
1209
+ const b = asNumericMatrix(args[1]);
1210
+ if ("kind" in b) {
1211
+ return b;
1212
+ }
1213
+ const m = a.length;
1214
+ const k = a[0]?.length ?? 0;
1215
+ const k2 = b.length;
1216
+ const n = b[0]?.length ?? 0;
1217
+ if (m === 0 || k === 0 || n === 0 || k !== k2) {
1218
+ return ERRORS.VALUE;
1219
+ }
1220
+ const rows = [];
1221
+ for (let i = 0; i < m; i++) {
1222
+ const row = [];
1223
+ for (let j = 0; j < n; j++) {
1224
+ let sum = 0;
1225
+ for (let p = 0; p < k; p++) {
1226
+ sum += a[i][p] * b[p][j];
1227
+ }
1228
+ row.push(rvNumber(sum));
1229
+ }
1230
+ rows.push(row);
1231
+ }
1232
+ return rvArray(rows);
1233
+ };
1234
+ /**
1235
+ * MDETERM(array) — determinant of a square matrix via Gaussian
1236
+ * elimination with partial pivoting. Non-square or non-numeric input
1237
+ * returns #VALUE!.
1238
+ */
1239
+ export const fnMDETERM = args => {
1240
+ const mat = asNumericMatrix(args[0]);
1241
+ if ("kind" in mat) {
1242
+ return mat;
1243
+ }
1244
+ const n = mat.length;
1245
+ if (n === 0 || mat[0].length !== n) {
1246
+ return ERRORS.VALUE;
1247
+ }
1248
+ // Copy rows so we don't mutate the caller's array.
1249
+ const a = mat.map(r => r.slice());
1250
+ let det = 1;
1251
+ for (let i = 0; i < n; i++) {
1252
+ // Partial pivot: find row with largest |a[r][i]| for r in [i, n).
1253
+ let pivot = i;
1254
+ for (let r = i + 1; r < n; r++) {
1255
+ if (Math.abs(a[r][i]) > Math.abs(a[pivot][i])) {
1256
+ pivot = r;
1257
+ }
1258
+ }
1259
+ if (Math.abs(a[pivot][i]) < 1e-14) {
1260
+ return rvNumber(0);
1261
+ }
1262
+ if (pivot !== i) {
1263
+ const tmp = a[i];
1264
+ a[i] = a[pivot];
1265
+ a[pivot] = tmp;
1266
+ det = -det;
1267
+ }
1268
+ det *= a[i][i];
1269
+ for (let r = i + 1; r < n; r++) {
1270
+ const factor = a[r][i] / a[i][i];
1271
+ for (let c = i; c < n; c++) {
1272
+ a[r][c] -= factor * a[i][c];
1273
+ }
1274
+ }
1275
+ }
1276
+ return rvNumber(det);
1277
+ };
1278
+ /**
1279
+ * MINVERSE(array) — inverse of a square matrix via Gauss-Jordan
1280
+ * elimination. Singular matrices return #NUM!; non-square return
1281
+ * #VALUE!.
1282
+ */
1283
+ export const fnMINVERSE = args => {
1284
+ const mat = asNumericMatrix(args[0]);
1285
+ if ("kind" in mat) {
1286
+ return mat;
1287
+ }
1288
+ const n = mat.length;
1289
+ if (n === 0 || mat[0].length !== n) {
1290
+ return ERRORS.VALUE;
1291
+ }
1292
+ // Build augmented matrix [A | I].
1293
+ const aug = [];
1294
+ for (let i = 0; i < n; i++) {
1295
+ const row = new Array(2 * n).fill(0);
1296
+ for (let j = 0; j < n; j++) {
1297
+ row[j] = mat[i][j];
1298
+ }
1299
+ row[n + i] = 1;
1300
+ aug.push(row);
1301
+ }
1302
+ // Gauss-Jordan elimination with partial pivoting.
1303
+ for (let i = 0; i < n; i++) {
1304
+ let pivot = i;
1305
+ for (let r = i + 1; r < n; r++) {
1306
+ if (Math.abs(aug[r][i]) > Math.abs(aug[pivot][i])) {
1307
+ pivot = r;
1308
+ }
1309
+ }
1310
+ if (Math.abs(aug[pivot][i]) < 1e-14) {
1311
+ return ERRORS.NUM; // singular
1312
+ }
1313
+ if (pivot !== i) {
1314
+ const tmp = aug[i];
1315
+ aug[i] = aug[pivot];
1316
+ aug[pivot] = tmp;
1317
+ }
1318
+ const diag = aug[i][i];
1319
+ for (let c = 0; c < 2 * n; c++) {
1320
+ aug[i][c] /= diag;
1321
+ }
1322
+ for (let r = 0; r < n; r++) {
1323
+ if (r === i) {
1324
+ continue;
1325
+ }
1326
+ const factor = aug[r][i];
1327
+ if (factor === 0) {
1328
+ continue;
1329
+ }
1330
+ for (let c = 0; c < 2 * n; c++) {
1331
+ aug[r][c] -= factor * aug[i][c];
1332
+ }
1333
+ }
1334
+ }
1335
+ // Extract inverse from right half.
1336
+ const rows = [];
1337
+ for (let i = 0; i < n; i++) {
1338
+ const row = [];
1339
+ for (let j = 0; j < n; j++) {
1340
+ row.push(rvNumber(aug[i][n + j]));
1341
+ }
1342
+ rows.push(row);
1343
+ }
1344
+ return rvArray(rows);
1345
+ };
1346
+ /**
1347
+ * MUNIT(dimension) — n×n identity matrix.
1348
+ */
1349
+ export const fnMUNIT = args => {
1350
+ const nV = toNumberRV(topLeft(args[0]));
1351
+ if (isError(nV)) {
1352
+ return nV;
1353
+ }
1354
+ const n = Math.trunc(nV.value);
1355
+ if (n < 1) {
1356
+ return ERRORS.VALUE;
1357
+ }
1358
+ const rows = [];
1359
+ for (let i = 0; i < n; i++) {
1360
+ const row = new Array(n);
1361
+ for (let j = 0; j < n; j++) {
1362
+ row[j] = rvNumber(i === j ? 1 : 0);
1363
+ }
1364
+ rows.push(row);
1365
+ }
1366
+ return rvArray(rows);
1367
+ };
1368
+ // ============================================================================
1369
+ // SERIESSUM
1370
+ // ============================================================================
1371
+ /**
1372
+ * SERIESSUM(x, n, m, coefficients) — returns the sum of a power series
1373
+ * x^n * coef[0] + x^(n+m) * coef[1] + x^(n+2m) * coef[2] + …
1374
+ */
1375
+ export const fnSERIESSUM = args => {
1376
+ const xV = toNumberRV(topLeft(args[0]));
1377
+ if (isError(xV)) {
1378
+ return xV;
1379
+ }
1380
+ const nV = toNumberRV(topLeft(args[1]));
1381
+ if (isError(nV)) {
1382
+ return nV;
1383
+ }
1384
+ const mV = toNumberRV(topLeft(args[2]));
1385
+ if (isError(mV)) {
1386
+ return mV;
1387
+ }
1388
+ const coeffs = flattenNumbers([args[3]]);
1389
+ const err = firstError(coeffs);
1390
+ if (err) {
1391
+ return err;
1392
+ }
1393
+ if (coeffs.length === 0) {
1394
+ return ERRORS.VALUE;
1395
+ }
1396
+ let total = 0;
1397
+ let exponent = nV.value;
1398
+ for (const c of coeffs) {
1399
+ total += c.value * Math.pow(xV.value, exponent);
1400
+ exponent += mV.value;
1401
+ }
1402
+ if (!isFinite(total)) {
1403
+ return ERRORS.NUM;
1404
+ }
1405
+ return rvNumber(total);
1406
+ };