sc: extend mismatched-extent matrix arithmetic to the longer side

Element-wise matrix arithmetic between non-broadcast operands
of unequal length used to clip the result to the shorter side
and lose the trailing slots. The result should follow the
longer length, with #N/A in the slots past the shorter
operand.

Pair non-broadcast operands to the larger extent in
lcl_GetExtent (renamed from lcl_GetMinExtent), and switch the
per-cell out-of-bounds error in ExecuteBinaryOp from #VALUE!
to #N/A so the visible result matches the spec.

testMatrixArithmeticMismatchedExtent multiplies A1:A4 by the
column vector {1|2|3} and checks that the fourth row holds

Signed-off-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.co.uk>
Change-Id: I34eb5b14c77f8d414677c8ebeaf7e331c413c2a3
Reviewed-on: https://gerrit.collaboraoffice.com/c/online/+/5651
Tested-by: Tomaž Vajngerl <tomaz.vajngerl@collabora.com>
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
(cherry picked from commit b4957ed105bcaa4a73666a6a5e119d523284b0c9)
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/207872
Tested-by: Jenkins
Reviewed-by: Xisco Fauli <xiscofauli@libreoffice.org>
This commit is contained in:
Tomaž Vajngerl
2026-07-09 17:09:31 +02:00
committed by Xisco Fauli
parent 46394856a9
commit 47c226418e
3 changed files with 46 additions and 19 deletions
+25
View File
@@ -2182,6 +2182,31 @@ CPPUNIT_TEST_FIXTURE(TestFormula2, testExternalRefUnresolved)
#endif
}
CPPUNIT_TEST_FIXTURE(TestFormula2, testMatrixArithmeticMismatchedExtent)
{
// Range arithmetic between operands of unequal non-broadcast
// lengths extends the result to the longer length and fills the
// slots past the shorter operand with #N/A.
sc::AutoCalcSwitch aACSwitch(*m_pDoc, true);
m_pDoc->InsertTab(0, u"Test"_ustr);
m_pDoc->SetValue(ScAddress(0, 0, 0), 2.0);
m_pDoc->SetValue(ScAddress(0, 1, 0), 4.0);
m_pDoc->SetValue(ScAddress(0, 2, 0), 7.0);
m_pDoc->SetValue(ScAddress(0, 3, 0), 7.0);
ScMarkData aMark(m_pDoc->GetSheetLimits());
aMark.SelectOneTable(0);
m_pDoc->InsertMatrixFormula(1, 0, 1, 3, aMark, u"=A1:A4 * {1|2|3}"_ustr);
CPPUNIT_ASSERT_EQUAL(2.0, m_pDoc->GetValue(ScAddress(1, 0, 0)));
CPPUNIT_ASSERT_EQUAL(8.0, m_pDoc->GetValue(ScAddress(1, 1, 0)));
CPPUNIT_ASSERT_EQUAL(21.0, m_pDoc->GetValue(ScAddress(1, 2, 0)));
CPPUNIT_ASSERT_EQUAL(FormulaError::NotAvailable, m_pDoc->GetErrCode(ScAddress(1, 3, 0)));
m_pDoc->DeleteTab(0);
}
CPPUNIT_TEST_FIXTURE(TestFormula2, testMatrixOp)
{
m_pDoc->InsertTab(0, u"Test"_ustr);
+17 -18
View File
@@ -1218,17 +1218,16 @@ void ScInterpreter::ScMatTrans()
PushIllegalParameter();
}
/** Minimum extent of one result matrix dimension.
For a row or column vector to be replicated the larger matrix dimension is
returned, else the smaller dimension.
*/
static SCSIZE lcl_GetMinExtent( SCSIZE n1, SCSIZE n2 )
// Pair non-broadcast operands to the larger extent so the longer
// side keeps its data. A single-cell side broadcasts to the other
// side's length.
static SCSIZE lcl_GetExtent( SCSIZE n1, SCSIZE n2 )
{
if (n1 == 1)
return n2;
else if (n2 == 1)
return n1;
else if (n1 < n2)
else if (n1 > n2)
return n1;
else
return n2;
@@ -1237,30 +1236,30 @@ static SCSIZE lcl_GetMinExtent( SCSIZE n1, SCSIZE n2 )
static ScMatrixRef lcl_MatrixCalculation(
const ScMatrix& rMat1, const ScMatrix& rMat2, ScInterpreter* pInterpreter, const ScMatrix::CalculateOpFunction& Op)
{
SCSIZE nC1, nC2, nMinC;
SCSIZE nR1, nR2, nMinR;
SCSIZE nC1, nC2, nExtentC;
SCSIZE nR1, nR2, nExtentR;
rMat1.GetDimensions(nC1, nR1);
rMat2.GetDimensions(nC2, nR2);
nMinC = lcl_GetMinExtent( nC1, nC2);
nMinR = lcl_GetMinExtent( nR1, nR2);
ScMatrixRef xResMat = pInterpreter->GetNewMat(nMinC, nMinR, /*bEmpty*/true);
nExtentC = lcl_GetExtent( nC1, nC2);
nExtentR = lcl_GetExtent( nR1, nR2);
ScMatrixRef xResMat = pInterpreter->GetNewMat(nExtentC, nExtentR, /*bEmpty*/true);
if (xResMat)
xResMat->ExecuteBinaryOp(nMinC, nMinR, rMat1, rMat2, pInterpreter, Op);
xResMat->ExecuteBinaryOp(nExtentC, nExtentR, rMat1, rMat2, pInterpreter, Op);
return xResMat;
}
ScMatrixRef ScInterpreter::MatConcat(const ScMatrixRef& pMat1, const ScMatrixRef& pMat2)
{
SCSIZE nC1, nC2, nMinC;
SCSIZE nR1, nR2, nMinR;
SCSIZE nC1, nC2, nExtentC;
SCSIZE nR1, nR2, nExtentR;
pMat1->GetDimensions(nC1, nR1);
pMat2->GetDimensions(nC2, nR2);
nMinC = lcl_GetMinExtent( nC1, nC2);
nMinR = lcl_GetMinExtent( nR1, nR2);
ScMatrixRef xResMat = GetNewMat(nMinC, nMinR, /*bEmpty*/true);
nExtentC = lcl_GetExtent( nC1, nC2);
nExtentR = lcl_GetExtent( nR1, nR2);
ScMatrixRef xResMat = GetNewMat(nExtentC, nExtentR, /*bEmpty*/true);
if (xResMat)
{
xResMat->MatConcat(nMinC, nMinR, pMat1, pMat2, mrContext, mrDoc.GetSharedStringPool());
xResMat->MatConcat(nExtentC, nExtentR, pMat1, pMat2, mrContext, mrDoc.GetSharedStringPool());
}
return xResMat;
}
+4 -1
View File
@@ -3163,7 +3163,10 @@ void ScMatrixImpl::ExecuteBinaryOp(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatri
}
}
else
aOutPos = maMat.set(aOutPos, CreateDoubleError(FormulaError::NoValue));
// Out of bounds against the shorter operand.
// Mismatched-extent arithmetic fills these slots
// with #N/A so the longer side still has a result.
aOutPos = maMat.set(aOutPos, CreateDoubleError(FormulaError::NotAvailable));
aOutPos = MatrixImplType::next_position(aOutPos);
}
}