Skip to content

Add MUNIT, MDETERM and MINVERSE functions (HF-401, HF-399, HF-400) - #1817

Open
marcin-kordas-hoc wants to merge 9 commits into
developfrom
feat/hf-matrix-munit-mdeterm-minverse
Open

marcin-kordas-hoc wants to merge 9 commits into
developfrom
feat/hf-matrix-munit-mdeterm-minverse

Conversation

@marcin-kordas-hoc

@marcin-kordas-hoc marcin-kordas-hoc commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Context

Adds the matrix functions MUNIT, MDETERM and MINVERSE to MatrixPlugin, next to MMULT.

  • MUNIT(dimension) returns the identity matrix of that size. Numeric text such as "3" is read as a number, and a fractional dimension is truncated. A dimension below 1, text, an empty text, an empty cell or a dimension above the sheet's row or column limit is #VALUE!, and TRUE() is 1.
  • MDETERM(array) returns the determinant of a square matrix of numbers. A matrix that is not square, or that holds text, an empty cell or a boolean, is #VALUE!, even when it also holds an error. Otherwise an error inside the matrix propagates. A single number is its own determinant.
  • MINVERSE(array) returns the inverse of a square matrix, with the same #VALUE! cases. A singular matrix is #NUM!.
  • Approach: both functions use one LU decomposition with partial pivoting. MDETERM multiplies the pivots, and MINVERSE solves one pair of triangular systems per column. A matrix is singular when the elimination meets a pivot that is exactly 0.
  • Rejected: fraction-free (Bareiss) elimination. It gives an exact 0 for small singular integer matrices, but it multiplies minors. A 3x3 matrix with elements near 1E100 returned #NUM! instead of its determinant (about 6E300), and MINVERSE returned #NUM! for matrices that have an inverse.
  • Rounding: a singular matrix whose elimination does not meet an exact zero pivot gives rounding noise, as in Excel. MDETERM({1,2,3;4,5,6;7,8,9}) returns about 6.66E-16 in both, and MINVERSE of that matrix returns about -4.5E+15 at position (1,1) in both, instead of #NUM!.
  • Translations: every localized name was checked on Microsoft's function page for its locale: the name must appear in both the page title and the syntax line. For MUNIT, esES is M.UNIDAD and ruRU is МЕДИН, taken from those pages. idID keeps the English names.
  • Also in this PR: catalogue entries, the license capability table, a CHANGELOG entry, and MUNIT added to the known limitation on result sizes that must be known at parse time.

How did you test your changes?

  • Paired branch feat/hf-matrix-munit-mdeterm-minverse in hyperformula-tests. It adds unit/interpreter/function-matrix-munit-mdeterm-minverse.spec.ts with 79 tests.
  • The 4 magnitude tests and the 2 localized-name tests fail on the earlier head 2004308b8. The 2 MUNIT size tests (a dimension written as +2, a dimension above the sheet limits) fail on b37ce5db5. The 4 tests for numeric text in MUNIT and for text next to an error fail on 3d8c23fd6. All pass now.
  • 202 test matrices, sizes 2 to 60, compared with exact rational results. 108 of them are non-singular and their determinant is a normal number. The previous elimination returned #NUM! or 0 for 11 of the 108. LU returns #NUM! for 1: its pivots overflow when multiplied, although the determinant is about 3E137. Excel also returns #NUM! for a matrix whose pivot product overflows (table below). The previous elimination also returned #NUM! from MINVERSE for 3 invertible matrices; LU for none.
  • Differential check of the previous head against this one: 2990 formulas over matrices scaled from 1E-300 to 1E300, with 0 blocking lines after 15 accepted ones. All 15 are INDEX(MINVERSE(...)) of {1,2,3;4,5,6;7,8,9} scaled by a power of ten. That matrix is singular, and it now gives #NUM! instead of rounding noise.
  • Full jest run on this head with the paired tests: 521 suites, 6821 passed, 3 skipped.

Compatibility with Excel and Google Sheets

60 formulas were evaluated in Excel Online on one data layout (A1:B2 = {1,2;3,4}, A3 = text); 58 match. Selected rows:

Formula Excel Google Sheets HyperFormula
=INDEX(MUNIT(3),2,2) 1 not checked 1
=ROWS(MUNIT(2.9)) 2 not checked 2
=MUNIT(0) #VALUE! not checked #VALUE!
=MUNIT(TRUE()) 1 not checked 1
=MDETERM(A1:B2) -2 not checked -2
=MDETERM(A1:C2) #VALUE! not checked #VALUE!
=MDETERM(TRUE()) #VALUE! not checked #VALUE!
=INDEX(MINVERSE(A1:B2),2,1) 1.5 not checked 1.5
=MINVERSE(0) #NUM! not checked #NUM!
=MDETERM(A1:B2*2) -8 not checked #VALUE!; -8 with useArrayArithmetic
=MINVERSE(A3:B4) (a range with text) #VALUE! not checked #VALUE! in each cell of the predicted 2x2 result, #SPILL! when that area is occupied

The last two rows are differences. MDETERM(A1:B2*2) needs array arithmetic, which HyperFormula enables only with useArrayArithmetic. MINVERSE sizes its result before it evaluates the argument, so an error result still occupies a 2x2 area.

14 more formulas were measured in Excel Online after the first round; 13 match:

Formula Excel Google Sheets HyperFormula
=MDETERM({1,2,3;4,5,6;7,8,9}) 6.66E-16 not checked 6.66E-16
=INDEX(MINVERSE({1,2,3;4,5,6;7,8,9}),1,1) about -4.5036E+15 not checked about -4.5036E+15
=ROWS(MUNIT("3")) 3 not checked 3
=ROWS(MUNIT(+3)) 3 not checked 3
=MDETERM(A1:C3) with {1,2,0;0,3,1;2,1,1} times 1E100 6E+300 not checked 6E+300
=MDETERM(A1:C3) with the same matrix times 1E-90 6E-270 not checked 6E-270
=MDETERM(A1:B2) with {1E200,0;0,1E200} #NUM! not checked #NUM!
=MDETERM(A1:C3) with {1E200,0,0;0,1E200,0;0,0,1E-200} #NUM! not checked #NUM!
=INDEX(MINVERSE(A1:C3),1,1) with {1E-170,0,0;0,1,0;0,0,1E-170} 1E+170 not checked 1E+170
=INDEX(MINVERSE(A1),1,1) with A1 = 1E-307 1E+307 not checked 1E+307
=INDEX(MINVERSE(1e-307/1000),1,1) #NUM! not checked #NUM!
=MDETERM(A1:B2) with {1,"x";=1/0,4} #VALUE! not checked #VALUE!
=MDETERM(A1:B2) with {TRUE,2;3,4} #VALUE! not checked #VALUE!
=ROWS(MUNIT(A1:A2)) with A1:A2 = 3, 2 2 not checked #VALUE!

The last row is a difference: Excel returns one result per cell of a range of dimensions, and HyperFormula does not. It is listed in docs/guide/list-of-differences.md.

Public API changes

None.

Out of scope and follow-ups

  • The formula parser does not read Cyrillic function names, so no ruRU function name can be typed in a formula; the new tests check the Russian names through getRegisteredFunctionNames. This already affects every ruRU function and is not changed here.
  • MUNIT with a cell reference or a formula as the dimension (=MUNIT(A1)) is #VALUE! unless the result is 1x1, like SEQUENCE; see the known limitations.

Types of changes

  • Breaking change (existing functionality no longer works as before) – agreed with the maintainers
  • New feature or improvement (a non-breaking change that adds functionality)
  • Bug fix (a non-breaking change that fixes an issue)
  • Additional language file, or a change to an existing language file (translations)
  • Change to the documentation

Related issues

  1. HF-399, HF-400, HF-401

Checklist

🤖 Generated with Claude Code

marcin-kordas-hoc and others added 2 commits October 15, 2026 04:53
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
hyperformula-docs f7a20f9 Commit Preview URL

Branch Preview URL
Oct 25 2026, 02:30 AM

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Performance comparison of head (f7a20f9) vs base (d143456)

                                     testName |    base |    head | change
--------------------------------------------------------------------------
                                      Sheet A |   516.1 |  506.75 | -1.81%
                                      Sheet B |  169.24 |  162.83 | -3.79%
                                      Sheet T |  148.25 |  143.72 | -3.06%
                                Column ranges |  489.32 |  487.29 | -0.41%
                                Sorted lookup | 15603.2 | 15604.9 | +0.01%
Sheet A:  change value, add/remove row/column |   16.77 |    16.8 | +0.18%
 Sheet B: change value, add/remove row/column |  150.34 |  146.32 | -2.67%
                   Column ranges - add column |  157.43 |  163.17 | +3.65%
                Column ranges - without batch |  476.57 |  489.51 | +2.72%
                        Column ranges - batch |   117.3 |  120.03 | +2.33%

marcin-kordas-hoc and others added 4 commits October 23, 2026 00:22
…rtial pivoting

The fraction-free elimination multiplied minors together, so a matrix with large or small elements
overflowed or underflowed although its determinant was a number, and MINVERSE returned #NUM! for
matrices that have an inverse. A singular matrix is now one whose elimination meets a pivot that is
exactly 0, and the list of differences no longer claims an exact elimination.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… and ruRU

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…st MUNIT among such functions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread hyperformula/src/interpreter/plugin/MatrixPlugin.ts
Comment thread hyperformula/src/interpreter/plugin/MatrixPlugin.ts
marcin-kordas-hoc and others added 3 commits October 23, 2026 08:27
…sion at parse time

A dimension above the row or column limit of a sheet is now #VALUE!, like SEQUENCE, instead of
allocating the matrix (a dimension of 20000 crashed the process). A dimension written as +3 or -3
is read when the result size is predicted, so =MUNIT(+3) spills a 3x3 matrix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n MDETERM and MINVERSE

Excel returns a 3x3 matrix for MUNIT("3") and #VALUE! for a matrix that holds both text and an
error, whichever comes first. MUNIT now takes its dimension as a number, and the result size of a
numeric text literal is known at parse time.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Excel lifts MUNIT over a range and returns one result per cell; HyperFormula returns #VALUE!.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f7a20f9. Configure here.

if (node.type === AstNodeType.STRING) {
const value = Number(node.value)
return node.value.trim() !== '' && Number.isFinite(value) ? value : undefined
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String size parse disagrees with eval

Medium Severity

numberLiteral now accepts quoted dimensions via JavaScript Number(), but evaluation coerces the same argument with locale-aware NUMBER rules. Those parsers disagree, so munitArraySize can reserve a multi-cell spill for a #VALUE! result, or stay 1×1 when evaluation would build a larger identity matrix.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f7a20f9. Configure here.

@codecov

codecov Bot commented Oct 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.30%. Comparing base (d143456) to head (f7a20f9).

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #1817      +/-   ##
===========================================
+ Coverage    97.28%   97.30%   +0.01%     
===========================================
  Files          207      207              
  Lines        16218    16337     +119     
  Branches      3556     3620      +64     
===========================================
+ Hits         15777    15896     +119     
  Misses         433      433              
  Partials         8        8              
Files with missing lines Coverage Δ
hyperformula/src/error-message.ts 100.00% <100.00%> (ø)
hyperformula/src/i18n/languages/csCZ.ts 100.00% <ø> (ø)
hyperformula/src/i18n/languages/daDK.ts 100.00% <ø> (ø)
hyperformula/src/i18n/languages/deDE.ts 100.00% <ø> (ø)
hyperformula/src/i18n/languages/enGB.ts 100.00% <ø> (ø)
hyperformula/src/i18n/languages/esES.ts 100.00% <ø> (ø)
hyperformula/src/i18n/languages/fiFI.ts 100.00% <ø> (ø)
hyperformula/src/i18n/languages/frFR.ts 100.00% <ø> (ø)
hyperformula/src/i18n/languages/huHU.ts 100.00% <ø> (ø)
hyperformula/src/i18n/languages/idID.ts 100.00% <ø> (ø)
... and 11 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant