/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #ifdef DEBUG_SC_LUP_DECOMPOSITION #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include //Application:: #include using ::std::vector; using namespace formula; namespace { struct MatrixAdd { double operator() (const double& lhs, const double& rhs) const { return ::rtl::math::approxAdd( lhs,rhs); } }; struct MatrixSub { double operator() (const double& lhs, const double& rhs) const { return ::rtl::math::approxSub( lhs,rhs); } }; struct MatrixMul { double operator() (const double& lhs, const double& rhs) const { return lhs * rhs; } }; struct MatrixDiv { double operator() (const double& lhs, const double& rhs) const { return ScInterpreter::div( lhs,rhs); } }; struct MatrixPow { double operator() (const double& lhs, const double& rhs) const { 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, SCSIZE n, SCSIZE m, SCSIZE l) { for (SCSIZE row = 0; row < n; row++) { for (SCSIZE col = 0; col < l; col++) { // result element(col, row) =sum[ (row of A) * (column of B)] KahanSum fSum = 0.0; for (SCSIZE k = 0; k < m; k++) fSum += pA->GetDouble(k,row) * pB->GetDouble(col,k); pR->PutDouble(fSum.get(), col, row); } } } } double ScInterpreter::ScGetGCD(double fx, double fy) { // By ODFF definition GCD(0,a) => a. This is also vital for the code in // ScGCD() to work correctly with a preset fy=0.0 if (fy == 0.0) return fx; else if (fx == 0.0) return fy; else { double fz = fmod(fx, fy); while (fz > 0.0) { fx = fy; fy = fz; fz = fmod(fx, fy); } return fy; } } void ScInterpreter::ScGCD() { short nParamCount = GetByte(); if ( !MustHaveParamCountMin( nParamCount, 1 ) ) return; double fx, fy = 0.0; ScRange aRange; size_t nRefInList = 0; while (nGlobalError == FormulaError::NONE && nParamCount-- > 0) { switch (GetStackType()) { case svDouble : case svString: case svSingleRef: { fx = ::rtl::math::approxFloor( GetDouble()); if (fx < 0.0) { PushIllegalArgument(); return; } fy = ScGetGCD(fx, fy); } break; case svDoubleRef : case svRefList : { FormulaError nErr = FormulaError::NONE; PopDoubleRef( aRange, nParamCount, nRefInList); double nCellVal; ScValueIterator aValIter( mrContext, mrDoc, aRange, mnSubTotalFlags ); if (aValIter.GetFirst(nCellVal, nErr)) { do { fx = ::rtl::math::approxFloor( nCellVal); if (fx < 0.0) { PushIllegalArgument(); return; } fy = ScGetGCD(fx, fy); } while (nErr == FormulaError::NONE && aValIter.GetNext(nCellVal, nErr)); } SetError(nErr); } break; case svMatrix : case svExternalSingleRef: case svExternalDoubleRef: { ScMatrixRef pMat = GetMatrix(); if (pMat) { SCSIZE nC, nR; pMat->GetDimensions(nC, nR); if (nC == 0 || nR == 0) SetError(FormulaError::IllegalArgument); else { double nVal = pMat->GetGcd(); fy = ScGetGCD(nVal,fy); } } } break; default : SetError(FormulaError::IllegalParameter); break; } } PushDouble(fy); } void ScInterpreter:: ScLCM() { short nParamCount = GetByte(); if ( !MustHaveParamCountMin( nParamCount, 1 ) ) return; double fx, fy = 1.0; ScRange aRange; size_t nRefInList = 0; while (nGlobalError == FormulaError::NONE && nParamCount-- > 0) { switch (GetStackType()) { case svDouble : case svString: case svSingleRef: { fx = ::rtl::math::approxFloor( GetDouble()); if (fx < 0.0) { PushIllegalArgument(); return; } if (fx == 0.0 || fy == 0.0) fy = 0.0; else fy = fx * fy / ScGetGCD(fx, fy); } break; case svDoubleRef : case svRefList : { FormulaError nErr = FormulaError::NONE; PopDoubleRef( aRange, nParamCount, nRefInList); double nCellVal; ScValueIterator aValIter( mrContext, mrDoc, aRange, mnSubTotalFlags ); if (aValIter.GetFirst(nCellVal, nErr)) { do { fx = ::rtl::math::approxFloor( nCellVal); if (fx < 0.0) { PushIllegalArgument(); return; } if (fx == 0.0 || fy == 0.0) fy = 0.0; else fy = fx * fy / ScGetGCD(fx, fy); } while (nErr == FormulaError::NONE && aValIter.GetNext(nCellVal, nErr)); } SetError(nErr); } break; case svMatrix : case svExternalSingleRef: case svExternalDoubleRef: { ScMatrixRef pMat = GetMatrix(); if (pMat) { SCSIZE nC, nR; pMat->GetDimensions(nC, nR); if (nC == 0 || nR == 0) SetError(FormulaError::IllegalArgument); else { double nVal = pMat->GetLcm(); fy = (nVal * fy ) / ScGetGCD(nVal, fy); } } } break; default : SetError(FormulaError::IllegalParameter); break; } } PushDouble(fy); } void ScInterpreter::MakeMatNew(ScMatrixRef& rMat, SCSIZE nC, SCSIZE nR) { rMat->SetErrorInterpreter( this); // A temporary matrix is mutable and ScMatrix::CloneIfConst() returns the // very matrix. rMat->SetMutable(); SCSIZE nCols, nRows; rMat->GetDimensions( nCols, nRows); if ( nCols != nC || nRows != nR ) { // arbitrary limit of elements exceeded SetError( FormulaError::MatrixSize); rMat.reset(); } } ScMatrixRef ScInterpreter::GetNewMat(SCSIZE nC, SCSIZE nR, bool bEmpty) { ScMatrixRef pMat; if (bEmpty) pMat = new ScMatrix(nC, nR); else pMat = new ScMatrix(nC, nR, 0.0); MakeMatNew(pMat, nC, nR); return pMat; } ScMatrixRef ScInterpreter::GetNewMat(SCSIZE nC, SCSIZE nR, const std::vector& rValues) { ScMatrixRef pMat(new ScMatrix(nC, nR, rValues)); MakeMatNew(pMat, nC, nR); return pMat; } ScMatrixRef ScInterpreter::CreateMatrixFromDoubleRef( const FormulaToken* pToken, SCCOL nCol1, SCROW nRow1, SCTAB nTab1, SCCOL nCol2, SCROW nRow2, SCTAB nTab2 ) { if (nTab1 != nTab2 || nGlobalError != FormulaError::NONE) { // Not a 2D matrix. SetError(FormulaError::IllegalParameter); return nullptr; } if (nTab1 == nTab2 && pToken) { const ScComplexRefData& rCRef = *pToken->GetDoubleRef(); if (rCRef.IsTrimToData()) { // Clamp the size of the matrix area to rows which actually contain data. // For e.g. SUM(IF over an entire column, this can make a big difference, // But lets not trim the empty space from the top or left as this matters // at least in matrix formulas involving IF(). // Refer ScCompiler::AnnotateTrimOnDoubleRefs() where double-refs are // flagged for trimming. SCCOL nTempCol = nCol1; SCROW nTempRow = nRow1; mrDoc.ShrinkToDataArea(nTab1, nTempCol, nTempRow, nCol2, nRow2); } } SCSIZE nMatCols = static_cast(nCol2 - nCol1 + 1); SCSIZE nMatRows = static_cast(nRow2 - nRow1 + 1); if (!ScMatrix::IsSizeAllocatable( nMatCols, nMatRows)) { SetError(FormulaError::MatrixSize); return nullptr; } ScTokenMatrixMap::const_iterator aIter; if (pToken && ((aIter = maTokenMatrixMap.find( pToken)) != maTokenMatrixMap.end())) { /* XXX casting const away here is ugly; ScMatrixToken (to which the * result of this function usually is assigned) should not be forced to * carry a ScConstMatrixRef though. * TODO: a matrix already stored in pTokenMatrixMap should be * read-only and have a copy-on-write mechanism. Previously all tokens * were modifiable so we're already better than before ... */ return const_cast((*aIter).second.get())->GetMatrix(); } ScMatrixRef pMat = GetNewMat( nMatCols, nMatRows, true); if (!pMat || nGlobalError != FormulaError::NONE) return nullptr; if (!bCalcAsShown) { // Use fast array fill. mrDoc.FillMatrix(*pMat, nTab1, nCol1, nRow1, nCol2, nRow2); } else { // Use slower ScCellIterator to round values. // TODO: this probably could use CellBucket for faster storage, see // sc/source/core/data/column2.cxx and FillMatrixHandler, and then be // moved to a function on its own, and/or squeeze the rounding into a // similar FillMatrixHandler that would need to keep track of the cell // position then. // Set position where the next entry is expected. SCROW nNextRow = nRow1; SCCOL nNextCol = nCol1; // Set last position as if there was a previous entry. SCROW nThisRow = nRow2; SCCOL nThisCol = nCol1 - 1; ScCellIterator aCellIter( mrDoc, ScRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2)); for (bool bHas = aCellIter.first(); bHas; bHas = aCellIter.next()) { nThisCol = aCellIter.GetPos().Col(); nThisRow = aCellIter.GetPos().Row(); if (nThisCol != nNextCol || nThisRow != nNextRow) { // Fill empty between iterator's positions. for ( ; nNextCol <= nThisCol; ++nNextCol) { const SCSIZE nC = nNextCol - nCol1; const SCSIZE nMatStopRow = ((nNextCol < nThisCol) ? nMatRows : nThisRow - nRow1); for (SCSIZE nR = nNextRow - nRow1; nR < nMatStopRow; ++nR) { pMat->PutEmpty( nC, nR); } nNextRow = nRow1; } } if (nThisRow == nRow2) { nNextCol = nThisCol + 1; nNextRow = nRow1; } else { nNextCol = nThisCol; nNextRow = nThisRow + 1; } const SCSIZE nMatCol = static_cast(nThisCol - nCol1); const SCSIZE nMatRow = static_cast(nThisRow - nRow1); ScRefCellValue aCell( aCellIter.getRefCellValue()); if (aCellIter.isEmpty() || aCell.hasEmptyValue()) { pMat->PutEmpty( nMatCol, nMatRow); } else if (aCell.hasError()) { pMat->PutError( aCell.mpFormula->GetErrCode(), nMatCol, nMatRow); } else if (aCell.hasNumeric()) { double fVal = aCell.getValue(); // CELLTYPE_FORMULA already stores the rounded value. if (aCell.meType == CELLTYPE_VALUE) { // TODO: this could be moved to ScCellIterator to take // advantage of the faster ScAttrArray_IterGetNumberFormat. const ScAddress aAdr( nThisCol, nThisRow, nTab1); const sal_uInt32 nNumFormat = mrDoc.GetNumberFormat( mrContext, aAdr); fVal = mrDoc.RoundValueAsShown( fVal, nNumFormat, &mrContext); } pMat->PutDouble( fVal, nMatCol, nMatRow); } else if (aCell.hasString()) { pMat->PutString( mrStrPool.intern( aCell.getString(&mrDoc)), nMatCol, nMatRow); } else { assert(!"aCell.what?"); pMat->PutEmpty( nMatCol, nMatRow); } } // Fill empty if iterator's last position wasn't the end. if (nThisCol != nCol2 || nThisRow != nRow2) { for ( ; nNextCol <= nCol2; ++nNextCol) { SCSIZE nC = nNextCol - nCol1; for (SCSIZE nR = nNextRow - nRow1; nR < nMatRows; ++nR) { pMat->PutEmpty( nC, nR); } nNextRow = nRow1; } } } if (pToken) maTokenMatrixMap.emplace(pToken, new ScMatrixToken( pMat)); return pMat; } ScMatrixRef ScInterpreter::GetMatrix() { ScMatrixRef pMat = nullptr; switch (GetRawStackType()) { case svSingleRef : { ScAddress aAdr; PopSingleRef( aAdr ); pMat = GetNewMat(1, 1); if (pMat) { ScRefCellValue aCell(mrDoc, aAdr); if (aCell.hasEmptyValue()) pMat->PutEmpty(0, 0); else if (aCell.hasNumeric()) pMat->PutDouble(GetCellValue(aAdr, aCell), 0); else { svl::SharedString aStr; GetCellString(aStr, aCell); pMat->PutString(aStr, 0); } } } break; case svDoubleRef: { SCCOL nCol1, nCol2; SCROW nRow1, nRow2; SCTAB nTab1, nTab2; const formula::FormulaToken* p = sp ? pStack[sp-1] : nullptr; PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); pMat = CreateMatrixFromDoubleRef( p, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); } break; case svMatrix: pMat = PopMatrix(); break; case svError : case svMissing : case svDouble : { double fVal = GetDouble(); pMat = GetNewMat( 1, 1); if ( pMat ) { if ( nGlobalError != FormulaError::NONE ) { fVal = CreateDoubleError( nGlobalError); nGlobalError = FormulaError::NONE; } pMat->PutDouble( fVal, 0); } } break; case svString : { svl::SharedString aStr = GetString(); pMat = GetNewMat( 1, 1); if ( pMat ) { if ( nGlobalError != FormulaError::NONE ) { double fVal = CreateDoubleError( nGlobalError); pMat->PutDouble( fVal, 0); nGlobalError = FormulaError::NONE; } else pMat->PutString(aStr, 0); } } break; case svExternalSingleRef: { ScExternalRefCache::TokenRef pToken; PopExternalSingleRef(pToken); pMat = GetNewMat( 1, 1, true); if (!pMat) break; if (nGlobalError != FormulaError::NONE) { pMat->PutError( nGlobalError, 0, 0); nGlobalError = FormulaError::NONE; break; } switch (pToken->GetType()) { case svError: pMat->PutError( pToken->GetError(), 0, 0); break; case svDouble: pMat->PutDouble( pToken->GetDouble(), 0, 0); break; case svString: pMat->PutString( pToken->GetString(), 0, 0); break; default: ; // nothing, empty element matrix } } break; case svExternalDoubleRef: PopExternalDoubleRef(pMat); break; default: PopError(); SetError( FormulaError::IllegalArgument); break; } return pMat; } ScMatrixRef ScInterpreter::GetMatrix( short & rParam, size_t & rRefInList ) { switch (GetRawStackType()) { case svRefList: { ScRange aRange( ScAddress::INITIALIZE_INVALID ); PopDoubleRef( aRange, rParam, rRefInList); if (nGlobalError != FormulaError::NONE) return nullptr; SCCOL nCol1, nCol2; SCROW nRow1, nRow2; SCTAB nTab1, nTab2; aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); return CreateMatrixFromDoubleRef( nullptr, nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); } break; default: return GetMatrix(); } } sc::RangeMatrix ScInterpreter::GetRangeMatrix() { sc::RangeMatrix aRet; switch (GetRawStackType()) { case svMatrix: aRet = PopRangeMatrix(); break; default: aRet.mpMat = GetMatrix(); } return aRet; } void ScInterpreter::ScMatValue() { if ( !MustHaveParamCount( GetByte(), 3 ) ) return; // 0 to count-1 // Theoretically we could have GetSize() instead of GetUInt32(), but // really, practically ... SCSIZE nR = static_cast(GetUInt32()); SCSIZE nC = static_cast(GetUInt32()); if (nGlobalError != FormulaError::NONE) { PushError( nGlobalError); return; } switch (GetStackType()) { case svSingleRef : { ScAddress aAdr; PopSingleRef( aAdr ); ScRefCellValue aCell(mrDoc, aAdr); if (aCell.meType == CELLTYPE_FORMULA) { FormulaError nErrCode = aCell.mpFormula->GetErrCode(); if (nErrCode != FormulaError::NONE) PushError( nErrCode); else { const ScMatrix* pMat = aCell.mpFormula->GetMatrix(); CalculateMatrixValue(pMat,nC,nR); } } else PushIllegalParameter(); } break; case svDoubleRef : { SCCOL nCol1; SCROW nRow1; SCTAB nTab1; SCCOL nCol2; SCROW nRow2; SCTAB nTab2; PopDoubleRef(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); if (nCol2 - nCol1 >= static_cast(nR) && nRow2 - nRow1 >= static_cast(nC) && nTab1 == nTab2) { ScAddress aAdr( sal::static_int_cast( nCol1 + nR ), sal::static_int_cast( nRow1 + nC ), nTab1 ); ScRefCellValue aCell(mrDoc, aAdr); if (aCell.hasNumeric()) PushDouble(GetCellValue(aAdr, aCell)); else { svl::SharedString aStr; GetCellString(aStr, aCell); PushString(aStr); } } else PushNoValue(); } break; case svMatrix: { ScMatrixRef pMat = PopMatrix(); CalculateMatrixValue(pMat.get(),nC,nR); } break; default: PopError(); PushIllegalParameter(); break; } } void ScInterpreter::CalculateMatrixValue(const ScMatrix* pMat,SCSIZE nC,SCSIZE nR) { if (pMat) { SCSIZE nCl, nRw; pMat->GetDimensions(nCl, nRw); if (nC < nCl && nR < nRw) { const ScMatrixValue nMatVal = pMat->Get( nC, nR); ScMatValType nMatValType = nMatVal.nType; if (ScMatrix::IsNonValueType( nMatValType)) PushString( nMatVal.GetString() ); else PushDouble(nMatVal.fVal); // also handles DoubleError } else PushNoValue(); } else PushNoValue(); } void ScInterpreter::ScEMat() { if ( !MustHaveParamCount( GetByte(), 1 ) ) return; SCSIZE nDim = static_cast(GetUInt32()); if (nGlobalError != FormulaError::NONE || nDim == 0) PushIllegalArgument(); else if (!ScMatrix::IsSizeAllocatable( nDim, nDim)) PushError( FormulaError::MatrixSize); else { ScMatrixRef pRMat = GetNewMat(nDim, nDim, /*bEmpty*/true); if (pRMat) { MEMat(pRMat, nDim); PushMatrix(pRMat); } else PushIllegalArgument(); } } void ScInterpreter::MEMat(const ScMatrixRef& mM, SCSIZE n) { mM->FillDouble(0.0, 0, 0, n-1, n-1); for (SCSIZE i = 0; i < n; i++) mM->PutDouble(1.0, i, i); } /* Matrix LUP decomposition according to the pseudocode of "Introduction to * Algorithms" by Cormen, Leiserson, Rivest, Stein. * * Added scaling for numeric stability. * * Given an n x n nonsingular matrix A, find a permutation matrix P, a unit * lower-triangular matrix L, and an upper-triangular matrix U such that PA=LU. * Compute L and U "in place" in the matrix A, the original content is * destroyed. Note that the diagonal elements of the U triangular matrix * replace the diagonal elements of the L-unit matrix (that are each ==1). The * permutation matrix P is an array, where P[i]=j means that the i-th row of P * contains a 1 in column j. Additionally keep track of the number of * permutations (row exchanges). * * Returns 0 if a singular matrix is encountered, else +1 if an even number of * permutations occurred, or -1 if odd, which is the sign of the determinant. * This may be used to calculate the determinant by multiplying the sign with * the product of the diagonal elements of the LU matrix. */ static int lcl_LUP_decompose( ScMatrix* mA, const SCSIZE n, ::std::vector< SCSIZE> & P ) { int nSign = 1; // Find scale of each row. ::std::vector< double> aScale(n); for (SCSIZE i=0; i < n; ++i) { double fMax = 0.0; for (SCSIZE j=0; j < n; ++j) { double fTmp = fabs( mA->GetDouble( j, i)); if (fMax < fTmp) fMax = fTmp; } if (fMax == 0.0) return 0; // singular matrix aScale[i] = 1.0 / fMax; } // Represent identity permutation, P[i]=i for (SCSIZE i=0; i < n; ++i) P[i] = i; // "Recursion" on the diagonal. SCSIZE l = n - 1; for (SCSIZE k=0; k < l; ++k) { // Implicit pivoting. With the scale found for a row, compare values of // a column and pick largest. double fMax = 0.0; double fScale = aScale[k]; SCSIZE kp = k; for (SCSIZE i = k; i < n; ++i) { double fTmp = fScale * fabs( mA->GetDouble( k, i)); if (fMax < fTmp) { fMax = fTmp; kp = i; } } if (fMax == 0.0) return 0; // singular matrix // Swap rows. The pivot element will be at mA[k,kp] (row,col notation) if (k != kp) { // permutations SCSIZE nTmp = P[k]; P[k] = P[kp]; P[kp] = nTmp; nSign = -nSign; // scales double fTmp = aScale[k]; aScale[k] = aScale[kp]; aScale[kp] = fTmp; // elements for (SCSIZE i=0; i < n; ++i) { double fMatTmp = mA->GetDouble( i, k); mA->PutDouble( mA->GetDouble( i, kp), i, k); mA->PutDouble( fMatTmp, i, kp); } } // Compute Schur complement. for (SCSIZE i = k+1; i < n; ++i) { double fNum = mA->GetDouble( k, i); double fDen = mA->GetDouble( k, k); mA->PutDouble( fNum/fDen, k, i); for (SCSIZE j = k+1; j < n; ++j) mA->PutDouble( ( mA->GetDouble( j, i) * fDen - fNum * mA->GetDouble( j, k) ) / fDen, j, i); } } #ifdef DEBUG_SC_LUP_DECOMPOSITION fprintf( stderr, "\n%s\n", "lcl_LUP_decompose(): LU"); for (SCSIZE i=0; i < n; ++i) { for (SCSIZE j=0; j < n; ++j) fprintf( stderr, "%8.2g ", mA->GetDouble( j, i)); fprintf( stderr, "\n%s\n", ""); } fprintf( stderr, "\n%s\n", "lcl_LUP_decompose(): P"); for (SCSIZE j=0; j < n; ++j) fprintf( stderr, "%5u ", (unsigned)P[j]); fprintf( stderr, "\n%s\n", ""); #endif bool bSingular=false; for (SCSIZE i=0; iGetDouble(i,i)) == 0.0; if (bSingular) nSign = 0; return nSign; } /* Solve a LUP decomposed equation Ax=b. LU is a combined matrix of L and U * triangulars and P the permutation vector as obtained from * lcl_LUP_decompose(). B is the right-hand side input vector, X is used to * return the solution vector. */ static void lcl_LUP_solve( const ScMatrix* mLU, const SCSIZE n, const ::std::vector< SCSIZE> & P, const ::std::vector< double> & B, ::std::vector< double> & X ) { SCSIZE nFirst = SCSIZE_MAX; // Ax=b => PAx=Pb, with decomposition LUx=Pb. // Define y=Ux and solve for y in Ly=Pb using forward substitution. for (SCSIZE i=0; i < n; ++i) { KahanSum fSum = B[P[i]]; // Matrix inversion comes with a lot of zeros in the B vectors, we // don't have to do all the computing with results multiplied by zero. // Until then, simply lookout for the position of the first nonzero // value. if (nFirst != SCSIZE_MAX) { for (SCSIZE j = nFirst; j < i; ++j) fSum -= mLU->GetDouble( j, i) * X[j]; // X[j] === y[j] } else if (fSum != 0) nFirst = i; X[i] = fSum.get(); // X[i] === y[i] } // Solve for x in Ux=y using back substitution. for (SCSIZE i = n; i--; ) { KahanSum fSum = X[i]; // X[i] === y[i] for (SCSIZE j = i+1; j < n; ++j) fSum -= mLU->GetDouble( j, i) * X[j]; // X[j] === x[j] X[i] = fSum.get() / mLU->GetDouble( i, i); // X[i] === x[i] } #ifdef DEBUG_SC_LUP_DECOMPOSITION fprintf( stderr, "\n%s\n", "lcl_LUP_solve():"); for (SCSIZE i=0; i < n; ++i) fprintf( stderr, "%8.2g ", X[i]); fprintf( stderr, "%s\n", ""); #endif } void ScInterpreter::ScMatDet() { if ( !MustHaveParamCount( GetByte(), 1 ) ) return; ScMatrixRef pMat = GetMatrix(); if (!pMat) { PushIllegalParameter(); return; } if ( !pMat->IsNumeric() ) { PushNoValue(); return; } SCSIZE nC, nR; pMat->GetDimensions(nC, nR); if ( nC != nR || nC == 0 ) PushIllegalArgument(); else if (!ScMatrix::IsSizeAllocatable( nC, nR)) PushError( FormulaError::MatrixSize); else { // LUP decomposition is done inplace, use copy. ScMatrixRef xLU = pMat->Clone(); if (!xLU) PushError( FormulaError::CodeOverflow); else { ::std::vector< SCSIZE> P(nR); int nDetSign = lcl_LUP_decompose( xLU.get(), nR, P); if (!nDetSign) PushInt(0); // singular matrix else { // In an LU matrix the determinant is simply the product of // all diagonal elements. double fDet = nDetSign; for (SCSIZE i=0; i < nR; ++i) fDet *= xLU->GetDouble( i, i); PushDouble( fDet); } } } } void ScInterpreter::ScMatInv() { if ( !MustHaveParamCount( GetByte(), 1 ) ) return; ScMatrixRef pMat = GetMatrix(); if (!pMat) { PushIllegalParameter(); return; } if ( !pMat->IsNumeric() ) { PushNoValue(); return; } SCSIZE nC, nR; pMat->GetDimensions(nC, nR); if (ScCalcConfig::isOpenCLEnabled()) { sc::FormulaGroupInterpreter *pInterpreter = sc::FormulaGroupInterpreter::getStatic(); if (pInterpreter != nullptr) { ScMatrixRef xResMat = pInterpreter->inverseMatrix(*pMat); if (xResMat) { PushMatrix(xResMat); return; } } } if ( nC != nR || nC == 0 ) PushIllegalArgument(); else if (!ScMatrix::IsSizeAllocatable( nC, nR)) PushError( FormulaError::MatrixSize); else { // LUP decomposition is done inplace, use copy. ScMatrixRef xLU = pMat->Clone(); // The result matrix. ScMatrixRef xY = GetNewMat( nR, nR, /*bEmpty*/true ); if (!xLU || !xY) PushError( FormulaError::CodeOverflow); else { ::std::vector< SCSIZE> P(nR); int nDetSign = lcl_LUP_decompose( xLU.get(), nR, P); if (!nDetSign) PushIllegalArgument(); else { // Solve equation for each column. ::std::vector< double> B(nR); ::std::vector< double> X(nR); for (SCSIZE j=0; j < nR; ++j) { for (SCSIZE i=0; i < nR; ++i) B[i] = 0.0; B[j] = 1.0; lcl_LUP_solve( xLU.get(), nR, P, B, X); for (SCSIZE i=0; i < nR; ++i) xY->PutDouble( X[i], j, i); } #ifdef DEBUG_SC_LUP_DECOMPOSITION /* Possible checks for ill-condition: * 1. Scale matrix, invert scaled matrix. If there are * elements of the inverted matrix that are several * orders of magnitude greater than 1 => * ill-conditioned. * Just how much is "several orders"? * 2. Invert the inverted matrix and assess whether the * result is sufficiently close to the original matrix. * If not => ill-conditioned. * Just what is sufficient? * 3. Multiplying the inverse by the original matrix should * produce a result sufficiently close to the identity * matrix. * Just what is sufficient? * * The following is #3. */ const double fInvEpsilon = 1.0E-7; ScMatrixRef xR = GetNewMat( nR, nR); if (xR) { ScMatrix* pR = xR.get(); lcl_MFastMult( pMat, xY.get(), pR, nR, nR, nR); fprintf( stderr, "\n%s\n", "ScMatInv(): mult-identity"); for (SCSIZE i=0; i < nR; ++i) { for (SCSIZE j=0; j < nR; ++j) { double fTmp = pR->GetDouble( j, i); fprintf( stderr, "%8.2g ", fTmp); if (fabs( fTmp - (i == j)) > fInvEpsilon) SetError( FormulaError::IllegalArgument); } fprintf( stderr, "\n%s\n", ""); } } #endif if (nGlobalError != FormulaError::NONE) PushError( nGlobalError); else PushMatrix( xY); } } } } void ScInterpreter::ScMatMult() { if ( !MustHaveParamCount( GetByte(), 2 ) ) return; ScMatrixRef pMat2 = GetMatrix(); ScMatrixRef pMat1 = GetMatrix(); ScMatrixRef pRMat; if (pMat1 && pMat2) { if ( pMat1->IsNumeric() && pMat2->IsNumeric() ) { SCSIZE nC1, nC2; SCSIZE nR1, nR2; pMat1->GetDimensions(nC1, nR1); pMat2->GetDimensions(nC2, nR2); if (nC1 != nR2) PushIllegalArgument(); else { pRMat = GetNewMat(nC2, nR1, /*bEmpty*/true); if (pRMat) { KahanSum fSum; for (SCSIZE i = 0; i < nR1; i++) { for (SCSIZE j = 0; j < nC2; j++) { fSum = 0.0; for (SCSIZE k = 0; k < nC1; k++) { fSum += pMat1->GetDouble(k,i)*pMat2->GetDouble(j,k); } pRMat->PutDouble(fSum.get(), j, i); } } PushMatrix(pRMat); } else PushIllegalArgument(); } } else PushNoValue(); } else PushIllegalParameter(); } void ScInterpreter::ScMatTrans() { if ( !MustHaveParamCount( GetByte(), 1 ) ) return; ScMatrixRef pMat = GetMatrix(); ScMatrixRef pRMat; if (pMat) { SCSIZE nC, nR; pMat->GetDimensions(nC, nR); pRMat = GetNewMat(nR, nC, /*bEmpty*/true); if ( pRMat ) { pMat->MatTrans(*pRMat); PushMatrix(pRMat); } else PushIllegalArgument(); } else 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 ) { if (n1 == 1) return n2; else if (n2 == 1) return n1; else if (n1 < n2) return n1; else return n2; } template static ScMatrixRef lcl_MatrixCalculation( const ScMatrix& rMat1, const ScMatrix& rMat2, ScInterpreter* pInterpreter) { 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); } } } return xResMat; } ScMatrixRef ScInterpreter::MatConcat(const ScMatrixRef& pMat1, const ScMatrixRef& pMat2) { SCSIZE nC1, nC2, nMinC; SCSIZE nR1, nR2, nMinR; 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); if (xResMat) { xResMat->MatConcat(nMinC, nMinR, pMat1, pMat2, *pFormatter, mrDoc.GetSharedStringPool()); } return xResMat; } // for DATE, TIME, DATETIME, DURATION static void lcl_GetDiffDateTimeFmtType( SvNumFormatType& nFuncFmt, SvNumFormatType nFmt1, SvNumFormatType nFmt2 ) { if ( nFmt1 == SvNumFormatType::UNDEFINED && nFmt2 == SvNumFormatType::UNDEFINED ) return; if ( nFmt1 == nFmt2 ) { if ( nFmt1 == SvNumFormatType::TIME || nFmt1 == SvNumFormatType::DATETIME || nFmt1 == SvNumFormatType::DURATION ) nFuncFmt = SvNumFormatType::DURATION; // times result in time duration // else: nothing special, number (date - date := days) } else if ( nFmt1 == SvNumFormatType::UNDEFINED ) nFuncFmt = nFmt2; // e.g. date + days := date else if ( nFmt2 == SvNumFormatType::UNDEFINED ) nFuncFmt = nFmt1; else { if ( nFmt1 == SvNumFormatType::DATE || nFmt2 == SvNumFormatType::DATE || nFmt1 == SvNumFormatType::DATETIME || nFmt2 == SvNumFormatType::DATETIME ) { if ( nFmt1 == SvNumFormatType::TIME || nFmt2 == SvNumFormatType::TIME ) nFuncFmt = SvNumFormatType::DATETIME; // date + time } } } void ScInterpreter::ScAdd() { CalculateAddSub(false); } void ScInterpreter::CalculateAddSub(bool _bSub) { ScMatrixRef pMat1 = nullptr; ScMatrixRef pMat2 = nullptr; double fVal1 = 0.0, fVal2 = 0.0; SvNumFormatType nFmt1, nFmt2; nFmt1 = nFmt2 = SvNumFormatType::UNDEFINED; SvNumFormatType nFmtCurrencyType = nCurFmtType; sal_uLong nFmtCurrencyIndex = nCurFmtIndex; SvNumFormatType nFmtPercentType = nCurFmtType; if ( GetStackType() == svMatrix ) pMat2 = GetMatrix(); else { fVal2 = GetDouble(); switch ( nCurFmtType ) { case SvNumFormatType::DATE : case SvNumFormatType::TIME : case SvNumFormatType::DATETIME : case SvNumFormatType::DURATION : nFmt2 = nCurFmtType; break; case SvNumFormatType::CURRENCY : nFmtCurrencyType = nCurFmtType; nFmtCurrencyIndex = nCurFmtIndex; break; case SvNumFormatType::PERCENT : nFmtPercentType = SvNumFormatType::PERCENT; break; default: break; } } if ( GetStackType() == svMatrix ) pMat1 = GetMatrix(); else { fVal1 = GetDouble(); switch ( nCurFmtType ) { case SvNumFormatType::DATE : case SvNumFormatType::TIME : case SvNumFormatType::DATETIME : case SvNumFormatType::DURATION : nFmt1 = nCurFmtType; break; case SvNumFormatType::CURRENCY : nFmtCurrencyType = nCurFmtType; nFmtCurrencyIndex = nCurFmtIndex; break; case SvNumFormatType::PERCENT : nFmtPercentType = SvNumFormatType::PERCENT; break; default: break; } } if (pMat1 && pMat2) { ScMatrixRef pResMat; if ( _bSub ) { pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this); } else { pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this); } if (!pResMat) PushNoValue(); else PushMatrix(pResMat); } else if (pMat1 || pMat2) { double fVal; bool bFlag; ScMatrixRef pMat = pMat1; if (!pMat) { fVal = fVal1; pMat = pMat2; bFlag = true; // double - Matrix } else { fVal = fVal2; bFlag = false; // Matrix - double } SCSIZE nC, nR; pMat->GetDimensions(nC, nR); ScMatrixRef pResMat = GetNewMat(nC, nR, true); if (pResMat) { if (_bSub) { pMat->SubOp( bFlag, fVal, *pResMat); } else { pMat->AddOp( fVal, *pResMat); } PushMatrix(pResMat); } else PushIllegalArgument(); } else { // Determine nFuncFmtType type before PushDouble(). if ( nFmtCurrencyType == SvNumFormatType::CURRENCY ) { nFuncFmtType = nFmtCurrencyType; nFuncFmtIndex = nFmtCurrencyIndex; } else { lcl_GetDiffDateTimeFmtType( nFuncFmtType, nFmt1, nFmt2 ); if (nFmtPercentType == SvNumFormatType::PERCENT && nFuncFmtType == SvNumFormatType::NUMBER) nFuncFmtType = SvNumFormatType::PERCENT; } if ( _bSub ) PushDouble( ::rtl::math::approxSub( fVal1, fVal2 ) ); else PushDouble( ::rtl::math::approxAdd( fVal1, fVal2 ) ); } } void ScInterpreter::ScAmpersand() { ScMatrixRef pMat1 = nullptr; ScMatrixRef pMat2 = nullptr; OUString sStr1, sStr2; if ( GetStackType() == svMatrix ) pMat2 = GetMatrix(); else sStr2 = GetString().getString(); if ( GetStackType() == svMatrix ) pMat1 = GetMatrix(); else sStr1 = GetString().getString(); if (pMat1 && pMat2) { ScMatrixRef pResMat = MatConcat(pMat1, pMat2); if (!pResMat) PushNoValue(); else PushMatrix(pResMat); } else if (pMat1 || pMat2) { OUString sStr; bool bFlag; ScMatrixRef pMat = pMat1; if (!pMat) { sStr = sStr1; pMat = pMat2; bFlag = true; // double - Matrix } else { sStr = sStr2; bFlag = false; // Matrix - double } SCSIZE nC, nR; pMat->GetDimensions(nC, nR); ScMatrixRef pResMat = GetNewMat(nC, nR, /*bEmpty*/true); if (pResMat) { if (nGlobalError != FormulaError::NONE) { for (SCSIZE i = 0; i < nC; ++i) for (SCSIZE j = 0; j < nR; ++j) pResMat->PutError( nGlobalError, i, j); } else if (bFlag) { for (SCSIZE i = 0; i < nC; ++i) for (SCSIZE j = 0; j < nR; ++j) { FormulaError nErr = pMat->GetErrorIfNotString( i, j); if (nErr != FormulaError::NONE) pResMat->PutError( nErr, i, j); else { OUString aTmp = sStr + pMat->GetString(*pFormatter, i, j).getString(); pResMat->PutString(mrStrPool.intern(aTmp), i, j); } } } else { for (SCSIZE i = 0; i < nC; ++i) for (SCSIZE j = 0; j < nR; ++j) { FormulaError nErr = pMat->GetErrorIfNotString( i, j); if (nErr != FormulaError::NONE) pResMat->PutError( nErr, i, j); else { OUString aTmp = pMat->GetString(*pFormatter, i, j).getString() + sStr; pResMat->PutString(mrStrPool.intern(aTmp), i, j); } } } PushMatrix(pResMat); } else PushIllegalArgument(); } else { if ( CheckStringResultLen( sStr1, sStr2.getLength() ) ) sStr1 += sStr2; PushString(sStr1); } } void ScInterpreter::ScSub() { CalculateAddSub(true); } void ScInterpreter::ScMul() { ScMatrixRef pMat1 = nullptr; ScMatrixRef pMat2 = nullptr; double fVal1 = 0.0, fVal2 = 0.0; SvNumFormatType nFmtCurrencyType = nCurFmtType; sal_uLong nFmtCurrencyIndex = nCurFmtIndex; if ( GetStackType() == svMatrix ) pMat2 = GetMatrix(); else { fVal2 = GetDouble(); switch ( nCurFmtType ) { case SvNumFormatType::CURRENCY : nFmtCurrencyType = nCurFmtType; nFmtCurrencyIndex = nCurFmtIndex; break; default: break; } } if ( GetStackType() == svMatrix ) pMat1 = GetMatrix(); else { fVal1 = GetDouble(); switch ( nCurFmtType ) { case SvNumFormatType::CURRENCY : nFmtCurrencyType = nCurFmtType; nFmtCurrencyIndex = nCurFmtIndex; break; default: break; } } if (pMat1 && pMat2) { ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this); if (!pResMat) PushNoValue(); else PushMatrix(pResMat); } else if (pMat1 || pMat2) { double fVal; ScMatrixRef pMat = pMat1; if (!pMat) { fVal = fVal1; pMat = pMat2; } else fVal = fVal2; SCSIZE nC, nR; pMat->GetDimensions(nC, nR); ScMatrixRef pResMat = GetNewMat(nC, nR, /*bEmpty*/true); if (pResMat) { pMat->MulOp( fVal, *pResMat); PushMatrix(pResMat); } else PushIllegalArgument(); } else { // Determine nFuncFmtType type before PushDouble(). if ( nFmtCurrencyType == SvNumFormatType::CURRENCY ) { nFuncFmtType = nFmtCurrencyType; nFuncFmtIndex = nFmtCurrencyIndex; } PushDouble(fVal1 * fVal2); } } void ScInterpreter::ScDiv() { ScMatrixRef pMat1 = nullptr; ScMatrixRef pMat2 = nullptr; double fVal1 = 0.0, fVal2 = 0.0; SvNumFormatType nFmtCurrencyType = nCurFmtType; sal_uLong nFmtCurrencyIndex = nCurFmtIndex; SvNumFormatType nFmtCurrencyType2 = SvNumFormatType::UNDEFINED; if ( GetStackType() == svMatrix ) pMat2 = GetMatrix(); else { fVal2 = GetDouble(); // do not take over currency, 123kg/456USD is not USD nFmtCurrencyType2 = nCurFmtType; } if ( GetStackType() == svMatrix ) pMat1 = GetMatrix(); else { fVal1 = GetDouble(); switch ( nCurFmtType ) { case SvNumFormatType::CURRENCY : nFmtCurrencyType = nCurFmtType; nFmtCurrencyIndex = nCurFmtIndex; break; default: break; } } if (pMat1 && pMat2) { ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this); if (!pResMat) PushNoValue(); else PushMatrix(pResMat); } else if (pMat1 || pMat2) { double fVal; bool bFlag; ScMatrixRef pMat = pMat1; if (!pMat) { fVal = fVal1; pMat = pMat2; bFlag = true; // double - Matrix } else { fVal = fVal2; bFlag = false; // Matrix - double } SCSIZE nC, nR; pMat->GetDimensions(nC, nR); ScMatrixRef pResMat = GetNewMat(nC, nR, /*bEmpty*/true); if (pResMat) { pMat->DivOp( bFlag, fVal, *pResMat); PushMatrix(pResMat); } else PushIllegalArgument(); } else { // Determine nFuncFmtType type before PushDouble(). if ( nFmtCurrencyType == SvNumFormatType::CURRENCY && nFmtCurrencyType2 != SvNumFormatType::CURRENCY) { // even USD/USD is not USD nFuncFmtType = nFmtCurrencyType; nFuncFmtIndex = nFmtCurrencyIndex; } PushDouble( div( fVal1, fVal2) ); } } void ScInterpreter::ScPower() { if ( MustHaveParamCount( GetByte(), 2 ) ) ScPow(); } void ScInterpreter::ScPow() { ScMatrixRef pMat1 = nullptr; ScMatrixRef pMat2 = nullptr; double fVal1 = 0.0, fVal2 = 0.0; if ( GetStackType() == svMatrix ) pMat2 = GetMatrix(); else fVal2 = GetDouble(); if ( GetStackType() == svMatrix ) pMat1 = GetMatrix(); else fVal1 = GetDouble(); if (pMat1 && pMat2) { ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this); if (!pResMat) PushNoValue(); else PushMatrix(pResMat); } else if (pMat1 || pMat2) { double fVal; bool bFlag; ScMatrixRef pMat = pMat1; if (!pMat) { fVal = fVal1; pMat = pMat2; bFlag = true; // double - Matrix } else { fVal = fVal2; bFlag = false; // Matrix - double } SCSIZE nC, nR; pMat->GetDimensions(nC, nR); ScMatrixRef pResMat = GetNewMat(nC, nR, /*bEmpty*/true); if (pResMat) { pMat->PowOp( bFlag, fVal, *pResMat); PushMatrix(pResMat); } else PushIllegalArgument(); } else { PushDouble( sc::power( fVal1, fVal2)); } } void ScInterpreter::ScSumProduct() { short nParamCount = GetByte(); if ( !MustHaveParamCountMin( nParamCount, 1) ) return; // XXX NOTE: Excel returns #VALUE! for reference list and 0 (why?) for // array of references. We calculate the proper individual arrays if sizes // match. size_t nInRefList = 0; ScMatrixRef pMatLast; ScMatrixRef pMat; pMatLast = GetMatrix( --nParamCount, nInRefList); if (!pMatLast) { PushIllegalParameter(); return; } SCSIZE nC, nCLast, nR, nRLast; pMatLast->GetDimensions(nCLast, nRLast); std::vector aResArray; pMatLast->GetDoubleArray(aResArray); while (nParamCount--) { pMat = GetMatrix( nParamCount, nInRefList); if (!pMat) { PushIllegalParameter(); return; } pMat->GetDimensions(nC, nR); if (nC != nCLast || nR != nRLast) { PushNoValue(); return; } pMat->MergeDoubleArrayMultiply(aResArray); } KahanSum fSum = 0.0; for( double fPosArray : aResArray ) { FormulaError nErr = GetDoubleErrorValue(fPosArray); if (nErr == FormulaError::NONE) fSum += fPosArray; else if (nErr != FormulaError::ElementNaN) { // Propagate the first error encountered, ignore "this is not a number" elements. PushError(nErr); return; } } PushDouble(fSum.get()); } void ScInterpreter::ScSumX2MY2() { CalculateSumX2MY2SumX2DY2(false); } void ScInterpreter::CalculateSumX2MY2SumX2DY2(bool _bSumX2DY2) { if ( !MustHaveParamCount( GetByte(), 2 ) ) return; ScMatrixRef pMat1 = nullptr; ScMatrixRef pMat2 = nullptr; SCSIZE i, j; pMat2 = GetMatrix(); pMat1 = GetMatrix(); if (!pMat2 || !pMat1) { PushIllegalParameter(); return; } SCSIZE nC1, nC2; SCSIZE nR1, nR2; pMat2->GetDimensions(nC2, nR2); pMat1->GetDimensions(nC1, nR1); if (nC1 != nC2 || nR1 != nR2) { PushNoValue(); return; } double fVal; KahanSum fSum = 0.0; for (i = 0; i < nC1; i++) for (j = 0; j < nR1; j++) if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j)) { fVal = pMat1->GetDouble(i,j); fSum += fVal * fVal; fVal = pMat2->GetDouble(i,j); if ( _bSumX2DY2 ) fSum += fVal * fVal; else fSum -= fVal * fVal; } PushDouble(fSum.get()); } void ScInterpreter::ScSumX2DY2() { CalculateSumX2MY2SumX2DY2(true); } void ScInterpreter::ScSumXMY2() { if ( !MustHaveParamCount( GetByte(), 2 ) ) return; ScMatrixRef pMat2 = GetMatrix(); ScMatrixRef pMat1 = GetMatrix(); if (!pMat2 || !pMat1) { PushIllegalParameter(); return; } SCSIZE nC1, nC2; SCSIZE nR1, nR2; pMat2->GetDimensions(nC2, nR2); pMat1->GetDimensions(nC1, nR1); if (nC1 != nC2 || nR1 != nR2) { PushNoValue(); return; } // if (nC1 != nC2 || nR1 != nR2) ScMatrixRef pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this); if (!pResMat) { PushNoValue(); } else { PushDouble(pResMat->SumSquare(false).maAccumulator.get()); } } void ScInterpreter::ScFrequency() { if ( !MustHaveParamCount( GetByte(), 2 ) ) return; vector aBinArray; vector aBinIndexOrder; GetSortArray( 1, aBinArray, &aBinIndexOrder, false, false ); SCSIZE nBinSize = aBinArray.size(); if (nGlobalError != FormulaError::NONE) { PushNoValue(); return; } vector aDataArray; GetSortArray( 1, aDataArray, nullptr, false, false ); SCSIZE nDataSize = aDataArray.size(); if (aDataArray.empty() || nGlobalError != FormulaError::NONE) { PushNoValue(); return; } ScMatrixRef pResMat = GetNewMat(1, nBinSize+1, /*bEmpty*/true); if (!pResMat) { PushIllegalArgument(); return; } if (nBinSize != aBinIndexOrder.size()) { PushIllegalArgument(); return; } SCSIZE j; SCSIZE i = 0; for (j = 0; j < nBinSize; ++j) { SCSIZE nCount = 0; while (i < nDataSize && aDataArray[i] <= aBinArray[j]) { ++nCount; ++i; } pResMat->PutDouble(static_cast(nCount), aBinIndexOrder[j]); } pResMat->PutDouble(static_cast(nDataSize-i), j); PushMatrix(pResMat); } namespace { // Helper methods for LINEST/LOGEST and TREND/GROWTH // All matrices must already exist and have the needed size, no control tests // done. Those methods, which names start with lcl_T, are adapted to case 3, // where Y (=observed values) is given as row. // Remember, ScMatrix matrices are zero based, index access (column,row). // over all elements; uses the matrices as vectors of length M double lcl_GetSumProduct(const ScMatrixRef& pMatA, const ScMatrixRef& pMatB, SCSIZE nM) { KahanSum fSum = 0.0; for (SCSIZE i=0; iGetDouble(i) * pMatB->GetDouble(i); return fSum.get(); } // Special version for use within QR decomposition. // Euclidean norm of column index C starting in row index R; // matrix A has count N rows. double lcl_GetColumnEuclideanNorm(const ScMatrixRef& pMatA, SCSIZE nC, SCSIZE nR, SCSIZE nN) { KahanSum fNorm = 0.0; for (SCSIZE row=nR; rowGetDouble(nC,row)) * (pMatA->GetDouble(nC,row)); return sqrt(fNorm.get()); } // Euclidean norm of row index R starting in column index C; // matrix A has count N columns. double lcl_TGetColumnEuclideanNorm(const ScMatrixRef& pMatA, SCSIZE nR, SCSIZE nC, SCSIZE nN) { KahanSum fNorm = 0.0; for (SCSIZE col=nC; colGetDouble(col,nR)) * (pMatA->GetDouble(col,nR)); return sqrt(fNorm.get()); } // Special version for use within QR decomposition. // Maximum norm of column index C starting in row index R; // matrix A has count N rows. double lcl_GetColumnMaximumNorm(const ScMatrixRef& pMatA, SCSIZE nC, SCSIZE nR, SCSIZE nN) { double fNorm = 0.0; for (SCSIZE row=nR; rowGetDouble(nC,row)); if (fNorm < fVal) fNorm = fVal; } return fNorm; } // Maximum norm of row index R starting in col index C; // matrix A has count N columns. double lcl_TGetColumnMaximumNorm(const ScMatrixRef& pMatA, SCSIZE nR, SCSIZE nC, SCSIZE nN) { double fNorm = 0.0; for (SCSIZE col=nC; colGetDouble(col,nR)); if (fNorm < fVal) fNorm = fVal; } return fNorm; } // Special version for use within QR decomposition. // starting in row index R; // Ca and Cb are indices of columns, matrices A and B have count N rows. double lcl_GetColumnSumProduct(const ScMatrixRef& pMatA, SCSIZE nCa, const ScMatrixRef& pMatB, SCSIZE nCb, SCSIZE nR, SCSIZE nN) { KahanSum fResult = 0.0; for (SCSIZE row=nR; rowGetDouble(nCa,row) * pMatB->GetDouble(nCb,row); return fResult.get(); } // starting in column index C; // Ra and Rb are indices of rows, matrices A and B have count N columns. double lcl_TGetColumnSumProduct(const ScMatrixRef& pMatA, SCSIZE nRa, const ScMatrixRef& pMatB, SCSIZE nRb, SCSIZE nC, SCSIZE nN) { KahanSum fResult = 0.0; for (SCSIZE col=nC; colGetDouble(col,nRa) * pMatB->GetDouble(col,nRb); return fResult.get(); } // no mathematical signum, but used to switch between adding and subtracting double lcl_GetSign(double fValue) { return (fValue >= 0.0 ? 1.0 : -1.0 ); } /* Calculates a QR decomposition with Householder reflection. * For each NxK matrix A exists a decomposition A=Q*R with an orthogonal * NxN matrix Q and a NxK matrix R. * Q=H1*H2*...*Hk with Householder matrices H. Such a householder matrix can * be build from a vector u by H=I-(2/u'u)*(u u'). This vectors u are returned * in the columns of matrix A, overwriting the old content. * The matrix R has a quadric upper part KxK with values in the upper right * triangle and zeros in all other elements. Here the diagonal elements of R * are stored in the vector R and the other upper right elements in the upper * right of the matrix A. * The function returns false, if calculation breaks. But because of round-off * errors singularity is often not detected. */ bool lcl_CalculateQRdecomposition(const ScMatrixRef& pMatA, ::std::vector< double>& pVecR, SCSIZE nK, SCSIZE nN) { // ScMatrix matrices are zero based, index access (column,row) for (SCSIZE col = 0; col PutDouble( pMatA->GetDouble(col,row)/fScale, col, row); const double fEuclid = lcl_GetColumnEuclideanNorm(pMatA, col, col, nN); const double fFactor = 1.0/fEuclid/(fEuclid + fabs(pMatA->GetDouble(col,col))); const double fSignum = lcl_GetSign(pMatA->GetDouble(col,col)); pMatA->PutDouble( pMatA->GetDouble(col,col) + fSignum*fEuclid, col,col); pVecR[col] = -fSignum * fScale * fEuclid; // apply Householder transformation to A for (SCSIZE c=col+1; cPutDouble( pMatA->GetDouble(c,row) - fSum * fFactor * pMatA->GetDouble(col,row), c, row); } } return true; } // same with transposed matrix A, N is count of columns, K count of rows bool lcl_TCalculateQRdecomposition(const ScMatrixRef& pMatA, ::std::vector< double>& pVecR, SCSIZE nK, SCSIZE nN) { double fSum ; // ScMatrix matrices are zero based, index access (column,row) for (SCSIZE row = 0; row PutDouble( pMatA->GetDouble(col,row)/fScale, col, row); const double fEuclid = lcl_TGetColumnEuclideanNorm(pMatA, row, row, nN); const double fFactor = 1.0/fEuclid/(fEuclid + fabs(pMatA->GetDouble(row,row))); const double fSignum = lcl_GetSign(pMatA->GetDouble(row,row)); pMatA->PutDouble( pMatA->GetDouble(row,row) + fSignum*fEuclid, row,row); pVecR[row] = -fSignum * fScale * fEuclid; // apply Householder transformation to A for (SCSIZE r=row+1; rPutDouble( pMatA->GetDouble(col,r) - fSum * fFactor * pMatA->GetDouble(col,row), col, r); } } return true; } /* Applies a Householder transformation to a column vector Y with is given as * Nx1 Matrix. The vector u, from which the Householder transformation is built, * is the column part in matrix A, with column index C, starting with row * index C. A is the result of the QR decomposition as obtained from * lcl_CalculateQRdecomposition. */ void lcl_ApplyHouseholderTransformation(const ScMatrixRef& pMatA, SCSIZE nC, const ScMatrixRef& pMatY, SCSIZE nN) { // ScMatrix matrices are zero based, index access (column,row) double fDenominator = lcl_GetColumnSumProduct(pMatA, nC, pMatA, nC, nC, nN); double fNumerator = lcl_GetColumnSumProduct(pMatA, nC, pMatY, 0, nC, nN); double fFactor = 2.0 * (fNumerator/fDenominator); for (SCSIZE row = nC; row < nN; row++) pMatY->PutDouble( pMatY->GetDouble(row) - fFactor * pMatA->GetDouble(nC,row), row); } // Same with transposed matrices A and Y. void lcl_TApplyHouseholderTransformation(const ScMatrixRef& pMatA, SCSIZE nR, const ScMatrixRef& pMatY, SCSIZE nN) { // ScMatrix matrices are zero based, index access (column,row) double fDenominator = lcl_TGetColumnSumProduct(pMatA, nR, pMatA, nR, nR, nN); double fNumerator = lcl_TGetColumnSumProduct(pMatA, nR, pMatY, 0, nR, nN); double fFactor = 2.0 * (fNumerator/fDenominator); for (SCSIZE col = nR; col < nN; col++) pMatY->PutDouble( pMatY->GetDouble(col) - fFactor * pMatA->GetDouble(col,nR), col); } /* Solve for X in R*X=S using back substitution. The solution X overwrites S. * Uses R from the result of the QR decomposition of a NxK matrix A. * S is a column vector given as matrix, with at least elements on index * 0 to K-1; elements on index>=K are ignored. Vector R must not have zero * elements, no check is done. */ void lcl_SolveWithUpperRightTriangle(const ScMatrixRef& pMatA, ::std::vector< double>& pVecR, const ScMatrixRef& pMatS, SCSIZE nK, bool bIsTransposed) { // ScMatrix matrices are zero based, index access (column,row) SCSIZE row; // SCSIZE is never negative, therefore test with rowp1=row+1 for (SCSIZE rowp1 = nK; rowp1>0; rowp1--) { row = rowp1-1; KahanSum fSum = pMatS->GetDouble(row); for (SCSIZE col = rowp1; colGetDouble(row,col) * pMatS->GetDouble(col); else fSum -= pMatA->GetDouble(col,row) * pMatS->GetDouble(col); pMatS->PutDouble( fSum.get() / pVecR[row] , row); } } /* Solve for X in R' * X= T using forward substitution. The solution X * overwrites T. Uses R from the result of the QR decomposition of a NxK * matrix A. T is a column vectors given as matrix, with at least elements on * index 0 to K-1; elements on index>=K are ignored. Vector R must not have * zero elements, no check is done. */ void lcl_SolveWithLowerLeftTriangle(const ScMatrixRef& pMatA, ::std::vector< double>& pVecR, const ScMatrixRef& pMatT, SCSIZE nK, bool bIsTransposed) { // ScMatrix matrices are zero based, index access (column,row) for (SCSIZE row = 0; row < nK; row++) { KahanSum fSum = pMatT -> GetDouble(row); for (SCSIZE col=0; col < row; col++) { if (bIsTransposed) fSum -= pMatA->GetDouble(col,row) * pMatT->GetDouble(col); else fSum -= pMatA->GetDouble(row,col) * pMatT->GetDouble(col); } pMatT->PutDouble( fSum.get() / pVecR[row] , row); } } /* Calculates Z = R * B * R is given in matrix A and vector VecR as obtained from the QR * decomposition in lcl_CalculateQRdecomposition. B and Z are column vectors * given as matrix with at least index 0 to K-1; elements on index>=K are * not used. */ void lcl_ApplyUpperRightTriangle(const ScMatrixRef& pMatA, ::std::vector< double>& pVecR, const ScMatrixRef& pMatB, const ScMatrixRef& pMatZ, SCSIZE nK, bool bIsTransposed) { // ScMatrix matrices are zero based, index access (column,row) for (SCSIZE row = 0; row < nK; row++) { KahanSum fSum = pVecR[row] * pMatB->GetDouble(row); for (SCSIZE col = row+1; col < nK; col++) if (bIsTransposed) fSum += pMatA->GetDouble(row,col) * pMatB->GetDouble(col); else fSum += pMatA->GetDouble(col,row) * pMatB->GetDouble(col); pMatZ->PutDouble( fSum.get(), row); } } double lcl_GetMeanOverAll(const ScMatrixRef& pMat, SCSIZE nN) { KahanSum fSum = 0.0; for (SCSIZE i=0 ; iGetDouble(i); return fSum.get()/static_cast(nN); } // Calculates means of the columns of matrix X. X is a RxC matrix; // ResMat is a 1xC matrix (=row). void lcl_CalculateColumnMeans(const ScMatrixRef& pX, const ScMatrixRef& pResMat, SCSIZE nC, SCSIZE nR) { for (SCSIZE i=0; i < nC; i++) { KahanSum fSum =0.0; for (SCSIZE k=0; k < nR; k++) fSum += pX->GetDouble(i,k); // GetDouble(Column,Row) pResMat ->PutDouble( fSum.get()/static_cast(nR),i); } } // Calculates means of the rows of matrix X. X is a RxC matrix; // ResMat is a Rx1 matrix (=column). void lcl_CalculateRowMeans(const ScMatrixRef& pX, const ScMatrixRef& pResMat, SCSIZE nC, SCSIZE nR) { for (SCSIZE k=0; k < nR; k++) { KahanSum fSum = 0.0; for (SCSIZE i=0; i < nC; i++) fSum += pX->GetDouble(i,k); // GetDouble(Column,Row) pResMat ->PutDouble( fSum.get()/static_cast(nC),k); } } void lcl_CalculateColumnsDelta(const ScMatrixRef& pMat, const ScMatrixRef& pColumnMeans, SCSIZE nC, SCSIZE nR) { for (SCSIZE i = 0; i < nC; i++) for (SCSIZE k = 0; k < nR; k++) pMat->PutDouble( ::rtl::math::approxSub (pMat->GetDouble(i,k) , pColumnMeans->GetDouble(i) ) , i, k); } void lcl_CalculateRowsDelta(const ScMatrixRef& pMat, const ScMatrixRef& pRowMeans, SCSIZE nC, SCSIZE nR) { for (SCSIZE k = 0; k < nR; k++) for (SCSIZE i = 0; i < nC; i++) pMat->PutDouble( ::rtl::math::approxSub ( pMat->GetDouble(i,k) , pRowMeans->GetDouble(k) ) , i, k); } // Case1 = simple regression // MatX = X - MeanX, MatY = Y - MeanY, y - haty = (y - MeanY) - (haty - MeanY) // = (y-MeanY)-((slope*x+a)-(slope*MeanX+a)) = (y-MeanY)-slope*(x-MeanX) double lcl_GetSSresid(const ScMatrixRef& pMatX, const ScMatrixRef& pMatY, double fSlope, SCSIZE nN) { KahanSum fSum = 0.0; for (SCSIZE i=0; iGetDouble(i) - fSlope * pMatX->GetDouble(i); fSum += fTemp * fTemp; } return fSum.get(); } } // Fill default values in matrix X, transform Y to log(Y) in case LOGEST|GROWTH, // determine sizes of matrices X and Y, determine kind of regression, clone // Y in case LOGEST|GROWTH, if constant. bool ScInterpreter::CheckMatrix(bool _bLOG, sal_uInt8& nCase, SCSIZE& nCX, SCSIZE& nCY, SCSIZE& nRX, SCSIZE& nRY, SCSIZE& M, SCSIZE& N, ScMatrixRef& pMatX, ScMatrixRef& pMatY) { nCX = 0; nCY = 0; nRX = 0; nRY = 0; M = 0; N = 0; pMatY->GetDimensions(nCY, nRY); const SCSIZE nCountY = nCY * nRY; for ( SCSIZE i = 0; i < nCountY; i++ ) { if (!pMatY->IsValue(i)) { PushIllegalArgument(); return false; } } if ( _bLOG ) { ScMatrixRef pNewY = pMatY->CloneIfConst(); for (SCSIZE nElem = 0; nElem < nCountY; nElem++) { const double fVal = pNewY->GetDouble(nElem); if (fVal <= 0.0) { PushIllegalArgument(); return false; } else pNewY->PutDouble(log(fVal), nElem); } pMatY = pNewY; } if (pMatX) { pMatX->GetDimensions(nCX, nRX); const SCSIZE nCountX = nCX * nRX; for ( SCSIZE i = 0; i < nCountX; i++ ) if (!pMatX->IsValue(i)) { PushIllegalArgument(); return false; } if (nCX == nCY && nRX == nRY) { nCase = 1; // simple regression M = 1; N = nCountY; } else if (nCY != 1 && nRY != 1) { PushIllegalArgument(); return false; } else if (nCY == 1) { if (nRX != nRY) { PushIllegalArgument(); return false; } else { nCase = 2; // Y is column N = nRY; M = nCX; } } else if (nCX != nCY) { PushIllegalArgument(); return false; } else { nCase = 3; // Y is row N = nCY; M = nRX; } } else { pMatX = GetNewMat(nCY, nRY, /*bEmpty*/true); nCX = nCY; nRX = nRY; if (!pMatX) { PushIllegalArgument(); return false; } for ( SCSIZE i = 1; i <= nCountY; i++ ) pMatX->PutDouble(static_cast(i), i-1); nCase = 1; N = nCountY; M = 1; } return true; } // LINEST void ScInterpreter::ScLinest() { CalculateRGPRKP(false); } // LOGEST void ScInterpreter::ScLogest() { CalculateRGPRKP(true); } void ScInterpreter::CalculateRGPRKP(bool _bRKP) { sal_uInt8 nParamCount = GetByte(); if (!MustHaveParamCount( nParamCount, 1, 4 )) return; bool bConstant, bStats; // optional forth parameter if (nParamCount == 4) bStats = GetBool(); else bStats = false; // The third parameter may not be missing in ODF, if the forth parameter // is present. But Excel allows it with default true, we too. if (nParamCount >= 3) { if (IsMissing()) { Pop(); bConstant = true; // PushIllegalParameter(); if ODF behavior is desired // return; } else bConstant = GetBool(); } else bConstant = true; ScMatrixRef pMatX; if (nParamCount >= 2) { if (IsMissing()) { //In ODF1.2 empty second parameter (which is two ;; ) is allowed Pop(); pMatX = nullptr; } else { pMatX = GetMatrix(); } } else pMatX = nullptr; ScMatrixRef pMatY = GetMatrix(); if (!pMatY) { PushIllegalParameter(); return; } // 1 = simple; 2 = multiple with Y as column; 3 = multiple with Y as row sal_uInt8 nCase; SCSIZE nCX, nCY; // number of columns SCSIZE nRX, nRY; //number of rows SCSIZE K = 0, N = 0; // K=number of variables X, N=number of data samples if (!CheckMatrix(_bRKP,nCase,nCX,nCY,nRX,nRY,K,N,pMatX,pMatY)) { PushIllegalParameter(); return; } // Enough data samples? if ((bConstant && (NPutError( FormulaError::NotAvailable, i, 2); pResMat->PutError( FormulaError::NotAvailable, i, 3); pResMat->PutError( FormulaError::NotAvailable, i, 4); } } // Uses sum(x-MeanX)^2 and not [sum x^2]-N * MeanX^2 in case bConstant. // Clone constant matrices, so that Mat = Mat - Mean is possible. double fMeanY = 0.0; if (bConstant) { ScMatrixRef pNewX = pMatX->CloneIfConst(); ScMatrixRef pNewY = pMatY->CloneIfConst(); if (!pNewX || !pNewY) { PushError(FormulaError::CodeOverflow); return; } pMatX = pNewX; pMatY = pNewY; // DeltaY is possible here; DeltaX depends on nCase, so later fMeanY = lcl_GetMeanOverAll(pMatY, N); for (SCSIZE i=0; iPutDouble( ::rtl::math::approxSub(pMatY->GetDouble(i),fMeanY), i ); } } if (nCase==1) { // calculate simple regression double fMeanX = 0.0; if (bConstant) { // Mat = Mat - Mean fMeanX = lcl_GetMeanOverAll(pMatX, N); for (SCSIZE i=0; iPutDouble( ::rtl::math::approxSub(pMatX->GetDouble(i),fMeanX), i ); } } double fSumXY = lcl_GetSumProduct(pMatX,pMatY,N); double fSumX2 = lcl_GetSumProduct(pMatX,pMatX,N); if (fSumX2==0.0) { PushNoValue(); // all x-values are identical return; } double fSlope = fSumXY / fSumX2; double fIntercept = 0.0; if (bConstant) fIntercept = fMeanY - fSlope * fMeanX; pResMat->PutDouble(_bRKP ? exp(fIntercept) : fIntercept, 1, 0); //order (column,row) pResMat->PutDouble(_bRKP ? exp(fSlope) : fSlope, 0, 0); if (bStats) { double fSSreg = fSlope * fSlope * fSumX2; pResMat->PutDouble(fSSreg, 0, 4); double fDegreesFreedom =static_cast( bConstant ? N-2 : N-1 ); pResMat->PutDouble(fDegreesFreedom, 1, 3); double fSSresid = lcl_GetSSresid(pMatX,pMatY,fSlope,N); pResMat->PutDouble(fSSresid, 1, 4); if (fDegreesFreedom == 0.0 || fSSresid == 0.0 || fSSreg == 0.0) { // exact fit; test SSreg too, because SSresid might be // unequal zero due to round of errors pResMat->PutDouble(0.0, 1, 4); // SSresid pResMat->PutError( FormulaError::NotAvailable, 0, 3); // F pResMat->PutDouble(0.0, 1, 2); // RMSE pResMat->PutDouble(0.0, 0, 1); // SigmaSlope if (bConstant) pResMat->PutDouble(0.0, 1, 1); //SigmaIntercept else pResMat->PutError( FormulaError::NotAvailable, 1, 1); pResMat->PutDouble(1.0, 0, 2); // R^2 } else { double fFstatistic = (fSSreg / static_cast(K)) / (fSSresid / fDegreesFreedom); pResMat->PutDouble(fFstatistic, 0, 3); // standard error of estimate double fRMSE = sqrt(fSSresid / fDegreesFreedom); pResMat->PutDouble(fRMSE, 1, 2); double fSigmaSlope = fRMSE / sqrt(fSumX2); pResMat->PutDouble(fSigmaSlope, 0, 1); if (bConstant) { double fSigmaIntercept = fRMSE * sqrt(fMeanX*fMeanX/fSumX2 + 1.0/static_cast(N)); pResMat->PutDouble(fSigmaIntercept, 1, 1); } else { pResMat->PutError( FormulaError::NotAvailable, 1, 1); } double fR2 = fSSreg / (fSSreg + fSSresid); pResMat->PutDouble(fR2, 0, 2); } } PushMatrix(pResMat); } else // calculate multiple regression; { // Uses a QR decomposition X = QR. The solution B = (X'X)^(-1) * X' * Y // becomes B = R^(-1) * Q' * Y if (nCase ==2) // Y is column { ::std::vector< double> aVecR(N); // for QR decomposition // Enough memory for needed matrices? ScMatrixRef pMeans = GetNewMat(K, 1, /*bEmpty*/true); // mean of each column ScMatrixRef pMatZ; // for Q' * Y , inter alia if (bStats) pMatZ = pMatY->Clone(); // Y is used in statistic, keep it else pMatZ = pMatY; // Y can be overwritten ScMatrixRef pSlopes = GetNewMat(1,K, /*bEmpty*/true); // from b1 to bK if (!pMeans || !pMatZ || !pSlopes) { PushError(FormulaError::CodeOverflow); return; } if (bConstant) { lcl_CalculateColumnMeans(pMatX, pMeans, K, N); lcl_CalculateColumnsDelta(pMatX, pMeans, K, N); } if (!lcl_CalculateQRdecomposition(pMatX, aVecR, K, N)) { PushNoValue(); return; } // Later on we will divide by elements of aVecR, so make sure // that they aren't zero. bool bIsSingular=false; for (SCSIZE row=0; row < K && !bIsSingular; row++) bIsSingular = aVecR[row] == 0.0; if (bIsSingular) { PushNoValue(); return; } // Z = Q' Y; for (SCSIZE col = 0; col < K; col++) { lcl_ApplyHouseholderTransformation(pMatX, col, pMatZ, N); } // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z // result Z should have zeros for index>=K; if not, ignore values for (SCSIZE col = 0; col < K ; col++) { pSlopes->PutDouble( pMatZ->GetDouble(col), col); } lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, false); double fIntercept = 0.0; if (bConstant) fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); // Fill first line in result matrix pResMat->PutDouble(_bRKP ? exp(fIntercept) : fIntercept, K, 0 ); for (SCSIZE i = 0; i < K; i++) pResMat->PutDouble(_bRKP ? exp(pSlopes->GetDouble(i)) : pSlopes->GetDouble(i) , K-1-i, 0); if (bStats) { double fSSreg = 0.0; double fSSresid = 0.0; // re-use memory of Z; pMatZ->FillDouble(0.0, 0, 0, 0, N-1); // Z = R * Slopes lcl_ApplyUpperRightTriangle(pMatX, aVecR, pSlopes, pMatZ, K, false); // Z = Q * Z, that is Q * R * Slopes = X * Slopes for (SCSIZE colp1 = K; colp1 > 0; colp1--) { lcl_ApplyHouseholderTransformation(pMatX, colp1-1, pMatZ,N); } fSSreg =lcl_GetSumProduct(pMatZ, pMatZ, N); // re-use Y for residuals, Y = Y-Z for (SCSIZE row = 0; row < N; row++) pMatY->PutDouble(pMatY->GetDouble(row) - pMatZ->GetDouble(row), row); fSSresid = lcl_GetSumProduct(pMatY, pMatY, N); pResMat->PutDouble(fSSreg, 0, 4); pResMat->PutDouble(fSSresid, 1, 4); double fDegreesFreedom =static_cast( bConstant ? N-K-1 : N-K ); pResMat->PutDouble(fDegreesFreedom, 1, 3); if (fDegreesFreedom == 0.0 || fSSresid == 0.0 || fSSreg == 0.0) { // exact fit; incl. observed values Y are identical pResMat->PutDouble(0.0, 1, 4); // SSresid // F = (SSreg/K) / (SSresid/df) = #DIV/0! pResMat->PutError( FormulaError::NotAvailable, 0, 3); // F // RMSE = sqrt(SSresid / df) = sqrt(0 / df) = 0 pResMat->PutDouble(0.0, 1, 2); // RMSE // SigmaSlope[i] = RMSE * sqrt(matrix[i,i]) = 0 * sqrt(...) = 0 for (SCSIZE i=0; iPutDouble(0.0, K-1-i, 1); // SigmaIntercept = RMSE * sqrt(...) = 0 if (bConstant) pResMat->PutDouble(0.0, K, 1); //SigmaIntercept else pResMat->PutError( FormulaError::NotAvailable, K, 1); // R^2 = SSreg / (SSreg + SSresid) = 1.0 pResMat->PutDouble(1.0, 0, 2); // R^2 } else { double fFstatistic = (fSSreg / static_cast(K)) / (fSSresid / fDegreesFreedom); pResMat->PutDouble(fFstatistic, 0, 3); // standard error of estimate = root mean SSE double fRMSE = sqrt(fSSresid / fDegreesFreedom); pResMat->PutDouble(fRMSE, 1, 2); // standard error of slopes // = RMSE * sqrt(diagonal element of (R' R)^(-1) ) // standard error of intercept // = RMSE * sqrt( Xmean * (R' R)^(-1) * Xmean' + 1/N) // (R' R)^(-1) = R^(-1) * (R')^(-1). Do not calculate it as // a whole matrix, but iterate over unit vectors. KahanSum aSigmaIntercept = 0.0; double fPart; // for Xmean * single column of (R' R)^(-1) for (SCSIZE col = 0; col < K; col++) { //re-use memory of MatZ pMatZ->FillDouble(0.0,0,0,0,K-1); // Z = unit vector e pMatZ->PutDouble(1.0, col); //Solve R' * Z = e lcl_SolveWithLowerLeftTriangle(pMatX, aVecR, pMatZ, K, false); // Solve R * Znew = Zold lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pMatZ, K, false); // now Z is column col in (R' R)^(-1) double fSigmaSlope = fRMSE * sqrt(pMatZ->GetDouble(col)); pResMat->PutDouble(fSigmaSlope, K-1-col, 1); // (R' R) ^(-1) is symmetric if (bConstant) { fPart = lcl_GetSumProduct(pMeans, pMatZ, K); aSigmaIntercept += fPart * pMeans->GetDouble(col); } } if (bConstant) { double fSigmaIntercept = fRMSE * sqrt( (aSigmaIntercept + 1.0 / static_cast(N) ).get() ); pResMat->PutDouble(fSigmaIntercept, K, 1); } else { pResMat->PutError( FormulaError::NotAvailable, K, 1); } double fR2 = fSSreg / (fSSreg + fSSresid); pResMat->PutDouble(fR2, 0, 2); } } PushMatrix(pResMat); } else // nCase == 3, Y is row, all matrices are transposed { ::std::vector< double> aVecR(N); // for QR decomposition // Enough memory for needed matrices? ScMatrixRef pMeans = GetNewMat(1, K, /*bEmpty*/true); // mean of each row ScMatrixRef pMatZ; // for Q' * Y , inter alia if (bStats) pMatZ = pMatY->Clone(); // Y is used in statistic, keep it else pMatZ = pMatY; // Y can be overwritten ScMatrixRef pSlopes = GetNewMat(K,1, /*bEmpty*/true); // from b1 to bK if (!pMeans || !pMatZ || !pSlopes) { PushError(FormulaError::CodeOverflow); return; } if (bConstant) { lcl_CalculateRowMeans(pMatX, pMeans, N, K); lcl_CalculateRowsDelta(pMatX, pMeans, N, K); } if (!lcl_TCalculateQRdecomposition(pMatX, aVecR, K, N)) { PushNoValue(); return; } // Later on we will divide by elements of aVecR, so make sure // that they aren't zero. bool bIsSingular=false; for (SCSIZE row=0; row < K && !bIsSingular; row++) bIsSingular = aVecR[row] == 0.0; if (bIsSingular) { PushNoValue(); return; } // Z = Q' Y for (SCSIZE row = 0; row < K; row++) { lcl_TApplyHouseholderTransformation(pMatX, row, pMatZ, N); } // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z // result Z should have zeros for index>=K; if not, ignore values for (SCSIZE col = 0; col < K ; col++) { pSlopes->PutDouble( pMatZ->GetDouble(col), col); } lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, true); double fIntercept = 0.0; if (bConstant) fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); // Fill first line in result matrix pResMat->PutDouble(_bRKP ? exp(fIntercept) : fIntercept, K, 0 ); for (SCSIZE i = 0; i < K; i++) pResMat->PutDouble(_bRKP ? exp(pSlopes->GetDouble(i)) : pSlopes->GetDouble(i) , K-1-i, 0); if (bStats) { double fSSreg = 0.0; double fSSresid = 0.0; // re-use memory of Z; pMatZ->FillDouble(0.0, 0, 0, N-1, 0); // Z = R * Slopes lcl_ApplyUpperRightTriangle(pMatX, aVecR, pSlopes, pMatZ, K, true); // Z = Q * Z, that is Q * R * Slopes = X * Slopes for (SCSIZE rowp1 = K; rowp1 > 0; rowp1--) { lcl_TApplyHouseholderTransformation(pMatX, rowp1-1, pMatZ,N); } fSSreg =lcl_GetSumProduct(pMatZ, pMatZ, N); // re-use Y for residuals, Y = Y-Z for (SCSIZE col = 0; col < N; col++) pMatY->PutDouble(pMatY->GetDouble(col) - pMatZ->GetDouble(col), col); fSSresid = lcl_GetSumProduct(pMatY, pMatY, N); pResMat->PutDouble(fSSreg, 0, 4); pResMat->PutDouble(fSSresid, 1, 4); double fDegreesFreedom =static_cast( bConstant ? N-K-1 : N-K ); pResMat->PutDouble(fDegreesFreedom, 1, 3); if (fDegreesFreedom == 0.0 || fSSresid == 0.0 || fSSreg == 0.0) { // exact fit; incl. case observed values Y are identical pResMat->PutDouble(0.0, 1, 4); // SSresid // F = (SSreg/K) / (SSresid/df) = #DIV/0! pResMat->PutError( FormulaError::NotAvailable, 0, 3); // F // RMSE = sqrt(SSresid / df) = sqrt(0 / df) = 0 pResMat->PutDouble(0.0, 1, 2); // RMSE // SigmaSlope[i] = RMSE * sqrt(matrix[i,i]) = 0 * sqrt(...) = 0 for (SCSIZE i=0; iPutDouble(0.0, K-1-i, 1); // SigmaIntercept = RMSE * sqrt(...) = 0 if (bConstant) pResMat->PutDouble(0.0, K, 1); //SigmaIntercept else pResMat->PutError( FormulaError::NotAvailable, K, 1); // R^2 = SSreg / (SSreg + SSresid) = 1.0 pResMat->PutDouble(1.0, 0, 2); // R^2 } else { double fFstatistic = (fSSreg / static_cast(K)) / (fSSresid / fDegreesFreedom); pResMat->PutDouble(fFstatistic, 0, 3); // standard error of estimate = root mean SSE double fRMSE = sqrt(fSSresid / fDegreesFreedom); pResMat->PutDouble(fRMSE, 1, 2); // standard error of slopes // = RMSE * sqrt(diagonal element of (R' R)^(-1) ) // standard error of intercept // = RMSE * sqrt( Xmean * (R' R)^(-1) * Xmean' + 1/N) // (R' R)^(-1) = R^(-1) * (R')^(-1). Do not calculate it as // a whole matrix, but iterate over unit vectors. // (R' R) ^(-1) is symmetric KahanSum aSigmaIntercept = 0.0; double fPart; // for Xmean * single col of (R' R)^(-1) for (SCSIZE row = 0; row < K; row++) { //re-use memory of MatZ pMatZ->FillDouble(0.0,0,0,K-1,0); // Z = unit vector e pMatZ->PutDouble(1.0, row); //Solve R' * Z = e lcl_SolveWithLowerLeftTriangle(pMatX, aVecR, pMatZ, K, true); // Solve R * Znew = Zold lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pMatZ, K, true); // now Z is column col in (R' R)^(-1) double fSigmaSlope = fRMSE * sqrt(pMatZ->GetDouble(row)); pResMat->PutDouble(fSigmaSlope, K-1-row, 1); if (bConstant) { fPart = lcl_GetSumProduct(pMeans, pMatZ, K); aSigmaIntercept += fPart * pMeans->GetDouble(row); } } if (bConstant) { double fSigmaIntercept = fRMSE * sqrt( (aSigmaIntercept + 1.0 / static_cast(N) ).get() ); pResMat->PutDouble(fSigmaIntercept, K, 1); } else { pResMat->PutError( FormulaError::NotAvailable, K, 1); } double fR2 = fSSreg / (fSSreg + fSSresid); pResMat->PutDouble(fR2, 0, 2); } } PushMatrix(pResMat); } } } void ScInterpreter::ScTrend() { CalculateTrendGrowth(false); } void ScInterpreter::ScGrowth() { CalculateTrendGrowth(true); } void ScInterpreter::CalculateTrendGrowth(bool _bGrowth) { sal_uInt8 nParamCount = GetByte(); if (!MustHaveParamCount( nParamCount, 1, 4 )) return; // optional forth parameter bool bConstant; if (nParamCount == 4) bConstant = GetBool(); else bConstant = true; // The third parameter may be missing in ODF, although the forth parameter // is present. Default values depend on data not yet read. ScMatrixRef pMatNewX; if (nParamCount >= 3) { if (IsMissing()) { Pop(); pMatNewX = nullptr; } else pMatNewX = GetMatrix(); } else pMatNewX = nullptr; //In ODF1.2 empty second parameter (which is two ;; ) is allowed //Defaults will be set in CheckMatrix ScMatrixRef pMatX; if (nParamCount >= 2) { if (IsMissing()) { Pop(); pMatX = nullptr; } else { pMatX = GetMatrix(); } } else pMatX = nullptr; ScMatrixRef pMatY = GetMatrix(); if (!pMatY) { PushIllegalParameter(); return; } // 1 = simple; 2 = multiple with Y as column; 3 = multiple with Y as row sal_uInt8 nCase; SCSIZE nCX, nCY; // number of columns SCSIZE nRX, nRY; //number of rows SCSIZE K = 0, N = 0; // K=number of variables X, N=number of data samples if (!CheckMatrix(_bGrowth,nCase,nCX,nCY,nRX,nRY,K,N,pMatX,pMatY)) { PushIllegalParameter(); return; } // Enough data samples? if ((bConstant && (NClone(); // pMatX will be changed to X-meanX } else { pMatNewX->GetDimensions(nCXN, nRXN); if ((nCase == 2 && K != nCXN) || (nCase == 3 && K != nRXN)) { PushIllegalArgument(); return; } nCountXN = nCXN * nRXN; for (SCSIZE i = 0; i < nCountXN; i++) if (!pMatNewX->IsValue(i)) { PushIllegalArgument(); return; } } ScMatrixRef pResMat; // size depends on nCase if (nCase == 1) pResMat = GetNewMat(nCXN,nRXN, /*bEmpty*/true); else { if (nCase==2) pResMat = GetNewMat(1,nRXN, /*bEmpty*/true); else pResMat = GetNewMat(nCXN,1, /*bEmpty*/true); } if (!pResMat) { PushError(FormulaError::CodeOverflow); return; } // Uses sum(x-MeanX)^2 and not [sum x^2]-N * MeanX^2 in case bConstant. // Clone constant matrices, so that Mat = Mat - Mean is possible. double fMeanY = 0.0; if (bConstant) { ScMatrixRef pCopyX = pMatX->CloneIfConst(); ScMatrixRef pCopyY = pMatY->CloneIfConst(); if (!pCopyX || !pCopyY) { PushError(FormulaError::MatrixSize); return; } pMatX = pCopyX; pMatY = pCopyY; // DeltaY is possible here; DeltaX depends on nCase, so later fMeanY = lcl_GetMeanOverAll(pMatY, N); for (SCSIZE i=0; iPutDouble( ::rtl::math::approxSub(pMatY->GetDouble(i),fMeanY), i ); } } if (nCase==1) { // calculate simple regression double fMeanX = 0.0; if (bConstant) { // Mat = Mat - Mean fMeanX = lcl_GetMeanOverAll(pMatX, N); for (SCSIZE i=0; iPutDouble( ::rtl::math::approxSub(pMatX->GetDouble(i),fMeanX), i ); } } double fSumXY = lcl_GetSumProduct(pMatX,pMatY,N); double fSumX2 = lcl_GetSumProduct(pMatX,pMatX,N); if (fSumX2==0.0) { PushNoValue(); // all x-values are identical return; } double fSlope = fSumXY / fSumX2; double fHelp; if (bConstant) { double fIntercept = fMeanY - fSlope * fMeanX; for (SCSIZE i = 0; i < nCountXN; i++) { fHelp = pMatNewX->GetDouble(i)*fSlope + fIntercept; pResMat->PutDouble(_bGrowth ? exp(fHelp) : fHelp, i); } } else { for (SCSIZE i = 0; i < nCountXN; i++) { fHelp = pMatNewX->GetDouble(i)*fSlope; pResMat->PutDouble(_bGrowth ? exp(fHelp) : fHelp, i); } } } else // calculate multiple regression; { if (nCase ==2) // Y is column { ::std::vector< double> aVecR(N); // for QR decomposition // Enough memory for needed matrices? ScMatrixRef pMeans = GetNewMat(K, 1, /*bEmpty*/true); // mean of each column ScMatrixRef pSlopes = GetNewMat(1,K, /*bEmpty*/true); // from b1 to bK if (!pMeans || !pSlopes) { PushError(FormulaError::CodeOverflow); return; } if (bConstant) { lcl_CalculateColumnMeans(pMatX, pMeans, K, N); lcl_CalculateColumnsDelta(pMatX, pMeans, K, N); } if (!lcl_CalculateQRdecomposition(pMatX, aVecR, K, N)) { PushNoValue(); return; } // Later on we will divide by elements of aVecR, so make sure // that they aren't zero. bool bIsSingular=false; for (SCSIZE row=0; row < K && !bIsSingular; row++) bIsSingular = aVecR[row] == 0.0; if (bIsSingular) { PushNoValue(); return; } // Z := Q' Y; Y is overwritten with result Z for (SCSIZE col = 0; col < K; col++) { lcl_ApplyHouseholderTransformation(pMatX, col, pMatY, N); } // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z // result Z should have zeros for index>=K; if not, ignore values for (SCSIZE col = 0; col < K ; col++) { pSlopes->PutDouble( pMatY->GetDouble(col), col); } lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, false); // Fill result matrix lcl_MFastMult(pMatNewX,pSlopes,pResMat,nRXN,K,1); if (bConstant) { double fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); for (SCSIZE row = 0; row < nRXN; row++) pResMat->PutDouble(pResMat->GetDouble(row)+fIntercept, row); } if (_bGrowth) { for (SCSIZE i = 0; i < nRXN; i++) pResMat->PutDouble(exp(pResMat->GetDouble(i)), i); } } else { // nCase == 3, Y is row, all matrices are transposed ::std::vector< double> aVecR(N); // for QR decomposition // Enough memory for needed matrices? ScMatrixRef pMeans = GetNewMat(1, K, /*bEmpty*/true); // mean of each row ScMatrixRef pSlopes = GetNewMat(K,1, /*bEmpty*/true); // row from b1 to bK if (!pMeans || !pSlopes) { PushError(FormulaError::CodeOverflow); return; } if (bConstant) { lcl_CalculateRowMeans(pMatX, pMeans, N, K); lcl_CalculateRowsDelta(pMatX, pMeans, N, K); } if (!lcl_TCalculateQRdecomposition(pMatX, aVecR, K, N)) { PushNoValue(); return; } // Later on we will divide by elements of aVecR, so make sure // that they aren't zero. bool bIsSingular=false; for (SCSIZE row=0; row < K && !bIsSingular; row++) bIsSingular = aVecR[row] == 0.0; if (bIsSingular) { PushNoValue(); return; } // Z := Q' Y; Y is overwritten with result Z for (SCSIZE row = 0; row < K; row++) { lcl_TApplyHouseholderTransformation(pMatX, row, pMatY, N); } // B = R^(-1) * Q' * Y <=> B = R^(-1) * Z <=> R * B = Z // result Z should have zeros for index>=K; if not, ignore values for (SCSIZE col = 0; col < K ; col++) { pSlopes->PutDouble( pMatY->GetDouble(col), col); } lcl_SolveWithUpperRightTriangle(pMatX, aVecR, pSlopes, K, true); // Fill result matrix lcl_MFastMult(pSlopes,pMatNewX,pResMat,1,K,nCXN); if (bConstant) { double fIntercept = fMeanY - lcl_GetSumProduct(pMeans,pSlopes,K); for (SCSIZE col = 0; col < nCXN; col++) pResMat->PutDouble(pResMat->GetDouble(col)+fIntercept, col); } if (_bGrowth) { for (SCSIZE i = 0; i < nCXN; i++) pResMat->PutDouble(exp(pResMat->GetDouble(i)), i); } } } PushMatrix(pResMat); } void ScInterpreter::ScMatRef() { // In case it contains relative references resolve them as usual. Push( *pCur ); ScAddress aAdr; PopSingleRef( aAdr ); ScRefCellValue aCell(mrDoc, aAdr); if (aCell.meType != CELLTYPE_FORMULA) { PushError( FormulaError::NoRef ); return; } if (aCell.mpFormula->IsRunning()) { // Twisted odd corner case where an array element's cell tries to // access the top left matrix while it is still running, see tdf#88737 // This is a hackish workaround, not a general solution, the matrix // isn't available anyway and FormulaError::CircularReference would be set. PushError( FormulaError::RetryCircular ); return; } const ScMatrix* pMat = aCell.mpFormula->GetMatrix(); if (pMat) { SCSIZE nCols, nRows; pMat->GetDimensions( nCols, nRows ); SCSIZE nC = static_cast(aPos.Col() - aAdr.Col()); SCSIZE nR = static_cast(aPos.Row() - aAdr.Row()); #if 0 // XXX: this could be an additional change for tdf#145085 to not // display the URL in a voluntary entered 2-rows array context. // However, that might as well be used on purpose to implement a check // on the URL, which existing documents may have done, the more that // before the accompanying change of // ScFormulaCell::GetResultDimensions() the cell array was forced to // two rows. Do not change without compelling reason. Note that this // repeating top cell is what Excel implements, but it has no // additional value so probably isn't used there. Exporting to and // using in Excel though will lose this capability. if (aCell.mpFormula->GetCode()->IsHyperLink()) { // Row 2 element is the URL that is not to be displayed, fake a // 1-row cell-text-only matrix that is repeated. assert(nRows == 2); nR = 0; } #endif if ((nCols <= nC && nCols != 1) || (nRows <= nR && nRows != 1)) PushNA(); else { const ScMatrixValue nMatVal = pMat->Get( nC, nR); ScMatValType nMatValType = nMatVal.nType; if (ScMatrix::IsNonValueType( nMatValType)) { if (ScMatrix::IsEmptyPathType( nMatValType)) { // result of empty false jump path nFuncFmtType = SvNumFormatType::LOGICAL; PushInt(0); } else if (ScMatrix::IsEmptyType( nMatValType)) { // Not inherited (really?) and display as empty string, not 0. PushTempToken( new ScEmptyCellToken( false, true)); } else PushString( nMatVal.GetString() ); } else { // Determine nFuncFmtType type before PushDouble(). mrDoc.GetNumberFormatInfo(mrContext, nCurFmtType, nCurFmtIndex, aAdr); nFuncFmtType = nCurFmtType; nFuncFmtIndex = nCurFmtIndex; PushDouble(nMatVal.fVal); // handles DoubleError } } } else { // Determine nFuncFmtType type before PushDouble(). mrDoc.GetNumberFormatInfo(mrContext, nCurFmtType, nCurFmtIndex, aAdr); nFuncFmtType = nCurFmtType; nFuncFmtIndex = nCurFmtIndex; // If not a result matrix, obtain the cell value. FormulaError nErr = aCell.mpFormula->GetErrCode(); if (nErr != FormulaError::NONE) PushError( nErr ); else if (aCell.mpFormula->IsValue()) PushDouble(aCell.mpFormula->GetValue()); else { svl::SharedString aVal = aCell.mpFormula->GetString(); PushString( aVal ); } } } void ScInterpreter::ScInfo() { if( !MustHaveParamCount( GetByte(), 1 ) ) return; OUString aStr = GetString().getString(); ScCellKeywordTranslator::transKeyword(aStr, &ScGlobal::GetLocale(), ocInfo); if( aStr == "SYSTEM" ) PushString( SC_INFO_OSVERSION ); else if( aStr == "OSVERSION" ) #if (defined LINUX || defined __FreeBSD__) PushString(Application::GetOSVersion()); #elif defined MACOSX // TODO tdf#140286 handle MACOSX version to get result compatible to Excel PushString("Windows (32-bit) NT 5.01"); #else // handle Windows (WNT, WIN_NT, WIN32, _WIN32) // TODO tdf#140286 handle Windows version to get a result compatible to Excel PushString( "Windows (32-bit) NT 5.01" ); #endif else if( aStr == "RELEASE" ) PushString( ::utl::Bootstrap::getBuildIdData( OUString() ) ); else if( aStr == "NUMFILE" ) PushDouble( 1 ); else if( aStr == "RECALC" ) PushString( ScResId( mrDoc.GetAutoCalc() ? STR_RECALC_AUTO : STR_RECALC_MANUAL ) ); else if (aStr == "DIRECTORY" || aStr == "MEMAVAIL" || aStr == "MEMUSED" || aStr == "ORIGIN" || aStr == "TOTMEM") PushNA(); else PushIllegalArgument(); } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */