sc/inc/scmatrix.hxx | 5 sc/source/core/tool/compiler.cxx | 225 ++++++++++++++++++++++++++----------- sc/source/core/tool/interpr5.cxx | 109 +++--------------- sc/source/core/tool/scmatrix.cxx | 231 ++++++++++++++++++++++++++++++++++++--- 4 files changed, 400 insertions(+), 170 deletions(-)
New commits: commit 9713a4aae980a52d08d38f5b12424aa5e33f9a75 Author: Noel Grandin <[email protected]> AuthorDate: Wed Dec 28 17:30:12 2022 +0200 Commit: Noel Grandin <[email protected]> CommitDate: Fri Dec 30 18:59:48 2022 +0000 optimise SUMPRODUCT(IF..) where we have entire column/row ranges following the same pattern as for SUM, set those ranges to shrink to available data. Takes it from 3s to <500ms on my test spreadsheet. Change-Id: I53ada996393a063b12ef7dd0f9bff40c90ecc8be Reviewed-on: https://gerrit.libreoffice.org/c/core/+/144850 Tested-by: Jenkins Reviewed-by: Noel Grandin <[email protected]> diff --git a/sc/source/core/tool/compiler.cxx b/sc/source/core/tool/compiler.cxx index e55a7e923c19..f97bff3053dd 100644 --- a/sc/source/core/tool/compiler.cxx +++ b/sc/source/core/tool/compiler.cxx @@ -6465,89 +6465,178 @@ void ScCompiler::AnnotateTrimOnDoubleRefs() // OpCode of the "root" operator (which is already in RPN array). OpCode eOpCode = (*(pCode - 1))->GetOpCode(); // eOpCode can be some operator which does not change with operands with or contains zero values. - if (eOpCode != ocSum) - return; + if (eOpCode == ocSum) + { + FormulaToken** ppTok = pCode - 2; // exclude the root operator. + // The following loop runs till a "pattern" is found or there is a mismatch + // and marks the push DoubleRef arguments as trimmable when there is a match. + // The pattern is + // SUM(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>) + // such that one of the operands of ocEqual is a double-ref. + // Examples of formula that matches this are: + // SUM(IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2) + // SUM((IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2)*$H$2*5/$G$3) + // SUM(IF(E:E=16,F:F)*$H$1*100) + bool bTillClose = true; + bool bCloseTillIf = false; + sal_Int16 nToksTillIf = 0; + constexpr sal_Int16 MAXDIST_IF = 15; + while (*ppTok) + { + FormulaToken* pTok = *ppTok; + OpCode eCurrOp = pTok->GetOpCode(); + ++nToksTillIf; + + // TODO : Is there a better way to handle this ? + // ocIf is too far off from the sum opcode. + if (nToksTillIf > MAXDIST_IF) + return; - FormulaToken** ppTok = pCode - 2; // exclude the root operator. - // The following loop runs till a "pattern" is found or there is a mismatch - // and marks the push DoubleRef arguments as trimmable when there is a match. - // The pattern is - // SUM(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>) - // such that one of the operands of ocEqual is a double-ref. - // Examples of formula that matches this are: - // SUM(IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2) - // SUM((IF(D:D=$A$1,F:F)*$H$1*2.3/$G$2)*$H$2*5/$G$3) - // SUM(IF(E:E=16,F:F)*$H$1*100) - bool bTillClose = true; - bool bCloseTillIf = false; - sal_Int16 nToksTillIf = 0; - constexpr sal_Int16 MAXDIST_IF = 15; - while (*ppTok) - { - FormulaToken* pTok = *ppTok; - OpCode eCurrOp = pTok->GetOpCode(); - ++nToksTillIf; - - // TODO : Is there a better way to handle this ? - // ocIf is too far off from the sum opcode. - if (nToksTillIf > MAXDIST_IF) - return; + switch (eCurrOp) + { + case ocDiv: + case ocMul: + if (!bTillClose) + return; + break; + case ocPush: - switch (eCurrOp) - { - case ocDiv: - case ocMul: - if (!bTillClose) - return; - break; - case ocPush: + break; + case ocClose: + if (bTillClose) + { + bTillClose = false; + bCloseTillIf = true; + } + else + return; + break; + case ocIf: + { + if (!bCloseTillIf) + return; - break; - case ocClose: - if (bTillClose) - { - bTillClose = false; - bCloseTillIf = true; - } - else + if (!pTok->IsInForceArray()) + return; + + const short nJumpCount = pTok->GetJump()[0]; + if (nJumpCount != 2) // Should have THEN but no ELSE. + return; + + OpCode eCompOp = (*(ppTok - 1))->GetOpCode(); + if (eCompOp != ocEqual) + return; + + FormulaToken* pLHS = *(ppTok - 2); + FormulaToken* pRHS = *(ppTok - 3); + if (((pLHS->GetType() == svSingleRef || pLHS->GetType() == svDouble) && pRHS->GetType() == svDoubleRef) || + ((pRHS->GetType() == svSingleRef || pRHS->GetType() == svDouble) && pLHS->GetType() == svDoubleRef)) + { + if (pLHS->GetType() == svDoubleRef) + pLHS->GetDoubleRef()->SetTrimToData(true); + else + pRHS->GetDoubleRef()->SetTrimToData(true); + return; + } + } + break; + default: return; - break; - case ocIf: - { - if (!bCloseTillIf) - return; + } + --ppTok; + } + } + else if (eOpCode == ocSumProduct) + { + FormulaToken** ppTok = pCode - 2; // exclude the root operator. + // The following loop runs till a "pattern" is found or there is a mismatch + // and marks the push DoubleRef arguments as trimmable when there is a match. + // The pattern is + // SUMPRODUCT(IF(<reference|double>=<reference|double>, <then-clause>)<a some operands with operators / or *>) + // such that one of the operands of ocEqual is a double-ref. + // Examples of formula that matches this are: + // SUMPRODUCT(IF($A:$A=$L12;$D:$D*G:G)) + bool bTillClose = true; + bool bCloseTillIf = false; + sal_Int16 nToksTillIf = 0; + constexpr sal_Int16 MAXDIST_IF = 15; + while (*ppTok) + { + FormulaToken* pTok = *ppTok; + OpCode eCurrOp = pTok->GetOpCode(); + ++nToksTillIf; + + // TODO : Is there a better way to handle this ? + // ocIf is too far off from the sum opcode. + if (nToksTillIf > MAXDIST_IF) + return; - if (!pTok->IsInForceArray()) + switch (eCurrOp) + { + case ocDiv: + case ocMul: + { + if (!pTok->IsInForceArray()) + break; + FormulaToken* pLHS = *(ppTok - 1); + FormulaToken* pRHS = *(ppTok - 2); + StackVar lhsType = pLHS->GetType(); + StackVar rhsType = pRHS->GetType(); + if (lhsType == svDoubleRef && rhsType == svDoubleRef) + { + pLHS->GetDoubleRef()->SetTrimToData(true); + pRHS->GetDoubleRef()->SetTrimToData(true); + } + } + break; + case ocPush: + break; + case ocClose: + if (bTillClose) + { + bTillClose = false; + bCloseTillIf = true; + } + else return; + break; + case ocIf: + { + if (!bCloseTillIf) + return; - const short nJumpCount = pTok->GetJump()[0]; - if (nJumpCount != 2) // Should have THEN but no ELSE. - return; + if (!pTok->IsInForceArray()) + return; - OpCode eCompOp = (*(ppTok - 1))->GetOpCode(); - if (eCompOp != ocEqual) - return; + const short nJumpCount = pTok->GetJump()[0]; + if (nJumpCount != 2) // Should have THEN but no ELSE. + return; - FormulaToken* pLHS = *(ppTok - 2); - FormulaToken* pRHS = *(ppTok - 3); - if (((pLHS->GetType() == svSingleRef || pLHS->GetType() == svDouble) && pRHS->GetType() == svDoubleRef) || - ((pRHS->GetType() == svSingleRef || pRHS->GetType() == svDouble) && pLHS->GetType() == svDoubleRef)) - { - if (pLHS->GetType() == svDoubleRef) + OpCode eCompOp = (*(ppTok - 1))->GetOpCode(); + if (eCompOp != ocEqual) + return; + + FormulaToken* pLHS = *(ppTok - 2); + FormulaToken* pRHS = *(ppTok - 3); + StackVar lhsType = pLHS->GetType(); + StackVar rhsType = pRHS->GetType(); + if (lhsType == svDoubleRef && (rhsType == svSingleRef || rhsType == svDouble)) + { pLHS->GetDoubleRef()->SetTrimToData(true); - else + } + if ((lhsType == svSingleRef || lhsType == svDouble) && rhsType == svDoubleRef) + { pRHS->GetDoubleRef()->SetTrimToData(true); + } return; } - } - break; - default: - return; + break; + default: + return; + } + --ppTok; } - --ppTok; } - - return; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ commit 96f162d02adee9b4edbb440896be90a64523c119 Author: Noel Grandin <[email protected]> AuthorDate: Thu Dec 22 18:41:19 2022 +0200 Commit: Noel Grandin <[email protected]> CommitDate: Fri Dec 30 18:59:34 2022 +0000 optimise SUMPRODUCT(IF..) a little Move the AddSub calculation inside ScMatrix so we can use an iterator to walk the matrix and avoid lookup cost for each element. Shaves 50% off the time spent here in my test sheet. Change-Id: I171d7dd4ae86419a563342a4120d14106e8d71db Reviewed-on: https://gerrit.libreoffice.org/c/core/+/144826 Tested-by: Jenkins Reviewed-by: Noel Grandin <[email protected]> diff --git a/sc/inc/scmatrix.hxx b/sc/inc/scmatrix.hxx index fdb870f40a1d..8eb4ae7fde76 100644 --- a/sc/inc/scmatrix.hxx +++ b/sc/inc/scmatrix.hxx @@ -118,6 +118,7 @@ public: typedef std::function<void(size_t, size_t, bool)> BoolOpFunction; typedef std::function<void(size_t, size_t, svl::SharedString)> StringOpFunction; typedef std::function<void(size_t, size_t)> EmptyOpFunction; + typedef std::function<double(double, double)> CalculateOpFunction; /** * When adding all numerical matrix elements for a scalar result such as @@ -410,6 +411,10 @@ public: void MatConcat(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrixRef& xMat1, const ScMatrixRef& xMat2, SvNumberFormatter& rFormatter, svl::SharedStringPool& rPool) ; + /** Apply binary operation to values from two input matrices, storing result into this matrix. */ + void ExecuteBinaryOp(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrix& rInputMat1, const ScMatrix& rInputMat2, + ScInterpreter* pInterpreter, CalculateOpFunction op); + #if DEBUG_MATRIX void Dump() const; #endif diff --git a/sc/source/core/tool/interpr5.cxx b/sc/source/core/tool/interpr5.cxx index cdb07fa9ca22..b27e27c5e188 100644 --- a/sc/source/core/tool/interpr5.cxx +++ b/sc/source/core/tool/interpr5.cxx @@ -47,45 +47,30 @@ using namespace formula; namespace { -struct MatrixAdd +double MatrixAdd(const double& lhs, const double& rhs) { - double operator() (const double& lhs, const double& rhs) const - { - return ::rtl::math::approxAdd( lhs,rhs); - } -}; + return ::rtl::math::approxAdd( lhs,rhs); +} -struct MatrixSub +double MatrixSub(const double& lhs, const double& rhs) { - double operator() (const double& lhs, const double& rhs) const - { - return ::rtl::math::approxSub( lhs,rhs); - } -}; + return ::rtl::math::approxSub( lhs,rhs); +} -struct MatrixMul +double MatrixMul(const double& lhs, const double& rhs) { - double operator() (const double& lhs, const double& rhs) const - { - return lhs * rhs; - } -}; + return lhs * rhs; +} -struct MatrixDiv +double MatrixDiv(const double& lhs, const double& rhs) { - double operator() (const double& lhs, const double& rhs) const - { - return ScInterpreter::div( lhs,rhs); - } -}; + return ScInterpreter::div( lhs,rhs); +} -struct MatrixPow +double MatrixPow(const double& lhs, const double& rhs) { - double operator() (const double& lhs, const double& rhs) const - { - return ::pow( lhs,rhs); - } -}; + return ::pow( lhs,rhs); +} // Multiply n x m Mat A with m x l Mat B to n x l Mat R void lcl_MFastMult(const ScMatrixRef& pA, const ScMatrixRef& pB, const ScMatrixRef& pR, @@ -1164,66 +1149,18 @@ static SCSIZE lcl_GetMinExtent( SCSIZE n1, SCSIZE n2 ) return n2; } -template<class Function> static ScMatrixRef lcl_MatrixCalculation( - const ScMatrix& rMat1, const ScMatrix& rMat2, ScInterpreter* pInterpreter) + const ScMatrix& rMat1, const ScMatrix& rMat2, ScInterpreter* pInterpreter, ScMatrix::CalculateOpFunction Op) { - static const Function Op; - SCSIZE nC1, nC2, nMinC; SCSIZE nR1, nR2, nMinR; - SCSIZE i, j; 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); if (xResMat) - { - for (i = 0; i < nMinC; i++) - { - for (j = 0; j < nMinR; j++) - { - bool bVal1 = rMat1.IsValueOrEmpty(i,j); - bool bVal2 = rMat2.IsValueOrEmpty(i,j); - FormulaError nErr; - if (bVal1 && bVal2) - { - double d = Op(rMat1.GetDouble(i,j), rMat2.GetDouble(i,j)); - xResMat->PutDouble( d, i, j); - } - else if (((nErr = rMat1.GetErrorIfNotString(i,j)) != FormulaError::NONE) || - ((nErr = rMat2.GetErrorIfNotString(i,j)) != FormulaError::NONE)) - { - xResMat->PutError( nErr, i, j); - } - else if ((!bVal1 && rMat1.IsStringOrEmpty(i,j)) || (!bVal2 && rMat2.IsStringOrEmpty(i,j))) - { - FormulaError nError1 = FormulaError::NONE; - SvNumFormatType nFmt1 = SvNumFormatType::ALL; - double fVal1 = (bVal1 ? rMat1.GetDouble(i,j) : - pInterpreter->ConvertStringToValue( rMat1.GetString(i,j).getString(), nError1, nFmt1)); - - FormulaError nError2 = FormulaError::NONE; - SvNumFormatType nFmt2 = SvNumFormatType::ALL; - double fVal2 = (bVal2 ? rMat2.GetDouble(i,j) : - pInterpreter->ConvertStringToValue( rMat2.GetString(i,j).getString(), nError2, nFmt2)); - - if (nError1 != FormulaError::NONE) - xResMat->PutError( nError1, i, j); - else if (nError2 != FormulaError::NONE) - xResMat->PutError( nError2, i, j); - else - { - double d = Op( fVal1, fVal2); - xResMat->PutDouble( d, i, j); - } - } - else - xResMat->PutError( FormulaError::NoValue, i, j); - } - } - } + xResMat->ExecuteBinaryOp(nMinC, nMinR, rMat1, rMat2, pInterpreter, Op); return xResMat; } @@ -1337,11 +1274,11 @@ void ScInterpreter::CalculateAddSub(bool _bSub) ScMatrixRef pResMat; if ( _bSub ) { - pResMat = lcl_MatrixCalculation<MatrixSub>( *pMat1, *pMat2, this); + pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixSub); } else { - pResMat = lcl_MatrixCalculation<MatrixAdd>( *pMat1, *pMat2, this); + pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixAdd); } if (!pResMat) @@ -1537,7 +1474,7 @@ void ScInterpreter::ScMul() } if (pMat1 && pMat2) { - ScMatrixRef pResMat = lcl_MatrixCalculation<MatrixMul>( *pMat1, *pMat2, this); + ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixMul); if (!pResMat) PushNoValue(); else @@ -1609,7 +1546,7 @@ void ScInterpreter::ScDiv() } if (pMat1 && pMat2) { - ScMatrixRef pResMat = lcl_MatrixCalculation<MatrixDiv>( *pMat1, *pMat2, this); + ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixDiv); if (!pResMat) PushNoValue(); else @@ -1676,7 +1613,7 @@ void ScInterpreter::ScPow() fVal1 = GetDouble(); if (pMat1 && pMat2) { - ScMatrixRef pResMat = lcl_MatrixCalculation<MatrixPow>( *pMat1, *pMat2, this); + ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixPow); if (!pResMat) PushNoValue(); else @@ -1847,7 +1784,7 @@ void ScInterpreter::ScSumXMY2() PushNoValue(); return; } // if (nC1 != nC2 || nR1 != nR2) - ScMatrixRef pResMat = lcl_MatrixCalculation<MatrixSub>( *pMat1, *pMat2, this); + ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixSub); if (!pResMat) { PushNoValue(); diff --git a/sc/source/core/tool/scmatrix.cxx b/sc/source/core/tool/scmatrix.cxx index 3c0aa14966db..f5d04bdb3fb9 100644 --- a/sc/source/core/tool/scmatrix.cxx +++ b/sc/source/core/tool/scmatrix.cxx @@ -335,6 +335,16 @@ public: void MatConcat(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrixRef& xMat1, const ScMatrixRef& xMat2, SvNumberFormatter& rFormatter, svl::SharedStringPool& rPool); + void ExecuteBinaryOp(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrix& rInputMat1, const ScMatrix& rInputMat2, + ScInterpreter* pInterpreter, ScMatrix::CalculateOpFunction op); + bool IsValueOrEmpty( const MatrixImplType::const_position_type & rPos ) const; + double GetDouble( const MatrixImplType::const_position_type & rPos) const; + FormulaError GetErrorIfNotString( const MatrixImplType::const_position_type & rPos ) const; + bool IsValue( const MatrixImplType::const_position_type & rPos ) const; + FormulaError GetError(const MatrixImplType::const_position_type & rPos) const; + bool IsStringOrEmpty(const MatrixImplType::const_position_type & rPos) const; + svl::SharedString GetString(const MatrixImplType::const_position_type& rPos) const; + #if DEBUG_MATRIX void Dump() const; #endif @@ -644,22 +654,7 @@ svl::SharedString ScMatrixImpl::GetString(SCSIZE nC, SCSIZE nR) const { if (ValidColRowOrReplicated( nC, nR )) { - double fErr = 0.0; - MatrixImplType::const_position_type aPos = maMat.position(nR, nC); - switch (maMat.get_type(aPos)) - { - case mdds::mtm::element_string: - return maMat.get_string(aPos); - case mdds::mtm::element_empty: - return svl::SharedString::getEmptyString(); - case mdds::mtm::element_numeric: - case mdds::mtm::element_boolean: - fErr = maMat.get_numeric(aPos); - [[fallthrough]]; - default: - OSL_FAIL("ScMatrixImpl::GetString: access error, no string"); - } - SetErrorAtInterpreter(GetDoubleErrorValue(fErr)); + return GetString(maMat.position(nR, nC)); } else { @@ -668,6 +663,26 @@ svl::SharedString ScMatrixImpl::GetString(SCSIZE nC, SCSIZE nR) const return svl::SharedString::getEmptyString(); } +svl::SharedString ScMatrixImpl::GetString(const MatrixImplType::const_position_type& rPos) const +{ + double fErr = 0.0; + switch (maMat.get_type(rPos)) + { + case mdds::mtm::element_string: + return maMat.get_string(rPos); + case mdds::mtm::element_empty: + return svl::SharedString::getEmptyString(); + case mdds::mtm::element_numeric: + case mdds::mtm::element_boolean: + fErr = maMat.get_numeric(rPos); + [[fallthrough]]; + default: + OSL_FAIL("ScMatrixImpl::GetString: access error, no string"); + } + SetErrorAtInterpreter(GetDoubleErrorValue(fErr)); + return svl::SharedString::getEmptyString(); +} + svl::SharedString ScMatrixImpl::GetString( SCSIZE nIndex) const { SCSIZE nC, nR; @@ -2788,6 +2803,185 @@ void ScMatrixImpl::MatConcat(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrixRef& } } +bool ScMatrixImpl::IsValueOrEmpty( const MatrixImplType::const_position_type & rPos ) const +{ + switch (maMat.get_type(rPos)) + { + case mdds::mtm::element_boolean: + case mdds::mtm::element_numeric: + case mdds::mtm::element_empty: + return true; + default: + ; + } + return false; +} + +double ScMatrixImpl::GetDouble(const MatrixImplType::const_position_type & rPos) const +{ + double fVal = maMat.get_numeric(rPos); + if ( pErrorInterpreter ) + { + FormulaError nError = GetDoubleErrorValue(fVal); + if ( nError != FormulaError::NONE ) + SetErrorAtInterpreter( nError); + } + return fVal; +} + +FormulaError ScMatrixImpl::GetErrorIfNotString( const MatrixImplType::const_position_type & rPos ) const +{ return IsValue(rPos) ? GetError(rPos) : FormulaError::NONE; } + +bool ScMatrixImpl::IsValue( const MatrixImplType::const_position_type & rPos ) const +{ + switch (maMat.get_type(rPos)) + { + case mdds::mtm::element_boolean: + case mdds::mtm::element_numeric: + return true; + default: + ; + } + return false; +} + +FormulaError ScMatrixImpl::GetError(const MatrixImplType::const_position_type & rPos) const +{ + double fVal = maMat.get_numeric(rPos); + return GetDoubleErrorValue(fVal); +} + +bool ScMatrixImpl::IsStringOrEmpty(const MatrixImplType::const_position_type & rPos) const +{ + switch (maMat.get_type(rPos)) + { + case mdds::mtm::element_empty: + case mdds::mtm::element_string: + return true; + default: + ; + } + return false; +} + +void ScMatrixImpl::ExecuteBinaryOp(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrix& rInputMat1, const ScMatrix& rInputMat2, + ScInterpreter* pInterpreter, ScMatrix::CalculateOpFunction Op) +{ + // Check output matrix size, otherwise output iterator logic will be wrong. + assert(maMat.size().row == nMaxRow && maMat.size().column == nMaxCol + && "the caller code should have sized the output matrix to the passed dimensions"); + auto & rMatImpl1 = *rInputMat1.pImpl; + auto & rMatImpl2 = *rInputMat2.pImpl; + // Check if we can do fast-path, where we have no replication or mis-matched matrix sizes. + if (rMatImpl1.maMat.size() == rMatImpl2.maMat.size() + && rMatImpl1.maMat.size() == maMat.size()) + { + MatrixImplType::position_type aOutPos = maMat.position(0, 0); + MatrixImplType::const_position_type aPos1 = rMatImpl1.maMat.position(0, 0); + MatrixImplType::const_position_type aPos2 = rMatImpl2.maMat.position(0, 0); + for (SCSIZE i = 0; i < nMaxCol; i++) + { + for (SCSIZE j = 0; j < nMaxRow; j++) + { + bool bVal1 = rMatImpl1.IsValueOrEmpty(aPos1); + bool bVal2 = rMatImpl2.IsValueOrEmpty(aPos2); + FormulaError nErr; + if (bVal1 && bVal2) + { + double d = Op(rMatImpl1.GetDouble(aPos1), rMatImpl2.GetDouble(aPos2)); + aOutPos = maMat.set(aOutPos, d); + } + else if (((nErr = rMatImpl1.GetErrorIfNotString(aPos1)) != FormulaError::NONE) || + ((nErr = rMatImpl2.GetErrorIfNotString(aPos2)) != FormulaError::NONE)) + { + aOutPos = maMat.set(aOutPos, CreateDoubleError(nErr)); + } + else if ((!bVal1 && rMatImpl1.IsStringOrEmpty(aPos1)) || + (!bVal2 && rMatImpl2.IsStringOrEmpty(aPos2))) + { + FormulaError nError1 = FormulaError::NONE; + SvNumFormatType nFmt1 = SvNumFormatType::ALL; + double fVal1 = (bVal1 ? rMatImpl1.GetDouble(aPos1) : + pInterpreter->ConvertStringToValue( rMatImpl1.GetString(aPos1).getString(), nError1, nFmt1)); + + FormulaError nError2 = FormulaError::NONE; + SvNumFormatType nFmt2 = SvNumFormatType::ALL; + double fVal2 = (bVal2 ? rMatImpl2.GetDouble(aPos2) : + pInterpreter->ConvertStringToValue( rMatImpl2.GetString(aPos2).getString(), nError2, nFmt2)); + + if (nError1 != FormulaError::NONE) + aOutPos = maMat.set(aOutPos, CreateDoubleError(nError1)); + else if (nError2 != FormulaError::NONE) + aOutPos = maMat.set(aOutPos, CreateDoubleError(nError2)); + else + { + double d = Op( fVal1, fVal2); + aOutPos = maMat.set(aOutPos, d); + } + } + else + aOutPos = maMat.set(aOutPos, CreateDoubleError(FormulaError::NoValue)); + aPos1 = MatrixImplType::next_position(aPos1); + aPos2 = MatrixImplType::next_position(aPos2); + aOutPos = MatrixImplType::next_position(aOutPos); + } + } + } + else + { + // Noting that this block is very hard to optimise to use iterators, because various dodgy + // array function usage relies on the semantics of some of the methods we call here. + // (see unit test testDubiousArrayFormulasFODS). + // These methods are inconsistent in their usage of ValidColRowReplicated() vs. ValidColRowOrReplicated() + // which leads to some very odd results. + MatrixImplType::position_type aOutPos = maMat.position(0, 0); + for (SCSIZE i = 0; i < nMaxCol; i++) + { + for (SCSIZE j = 0; j < nMaxRow; j++) + { + bool bVal1 = rInputMat1.IsValueOrEmpty(i,j); + bool bVal2 = rInputMat2.IsValueOrEmpty(i,j); + FormulaError nErr; + if (bVal1 && bVal2) + { + double d = Op(rInputMat1.GetDouble(i,j), rInputMat2.GetDouble(i,j)); + aOutPos = maMat.set(aOutPos, d); + } + else if (((nErr = rInputMat1.GetErrorIfNotString(i,j)) != FormulaError::NONE) || + ((nErr = rInputMat2.GetErrorIfNotString(i,j)) != FormulaError::NONE)) + { + aOutPos = maMat.set(aOutPos, CreateDoubleError(nErr)); + } + else if ((!bVal1 && rInputMat1.IsStringOrEmpty(i,j)) || (!bVal2 && rInputMat2.IsStringOrEmpty(i,j))) + { + FormulaError nError1 = FormulaError::NONE; + SvNumFormatType nFmt1 = SvNumFormatType::ALL; + double fVal1 = (bVal1 ? rInputMat1.GetDouble(i,j) : + pInterpreter->ConvertStringToValue( rInputMat1.GetString(i,j).getString(), nError1, nFmt1)); + + FormulaError nError2 = FormulaError::NONE; + SvNumFormatType nFmt2 = SvNumFormatType::ALL; + double fVal2 = (bVal2 ? rInputMat2.GetDouble(i,j) : + pInterpreter->ConvertStringToValue( rInputMat2.GetString(i,j).getString(), nError2, nFmt2)); + + if (nError1 != FormulaError::NONE) + aOutPos = maMat.set(aOutPos, CreateDoubleError(nError1)); + else if (nError2 != FormulaError::NONE) + aOutPos = maMat.set(aOutPos, CreateDoubleError(nError2)); + else + { + double d = Op( fVal1, fVal2); + aOutPos = maMat.set(aOutPos, d); + } + } + else + aOutPos = maMat.set(aOutPos, CreateDoubleError(FormulaError::NoValue)); + aOutPos = MatrixImplType::next_position(aOutPos); + } + } + } +} + void ScMatrix::IncRef() const { ++nRefCnt; @@ -3418,4 +3612,9 @@ void ScMatrix::MatConcat(SCSIZE nMaxCol, SCSIZE nMaxRow, pImpl->MatConcat(nMaxCol, nMaxRow, xMat1, xMat2, rFormatter, rPool); } +void ScMatrix::ExecuteBinaryOp(SCSIZE nMaxCol, SCSIZE nMaxRow, const ScMatrix& rInputMat1, const ScMatrix& rInputMat2, + ScInterpreter* pInterpreter, CalculateOpFunction op) +{ + pImpl->ExecuteBinaryOp(nMaxCol, nMaxRow, rInputMat1, rInputMat2, pInterpreter, op); +} /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
