/* -*- 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 <rtl/math.hxx>
#include <string.h>
#include <math.h>

#ifdef DEBUG_SC_LUP_DECOMPOSITION
#include <stdio.h>
#endif

#include <unotools/bootstrap.hxx>
#include <svl/zforlist.hxx>
#include <tools/duration.hxx>

#include <interpre.hxx>
#include <global.hxx>
#include <formulacell.hxx>
#include <document.hxx>
#include <dociter.hxx>
#include <scmatrix.hxx>
#include <globstr.hrc>
#include <scresid.hxx>
#include <cellkeytranslator.hxx>
#include <formulagroup.hxx>
#include <vcl/svapp.hxx> //Application::

#include <vector>

using namespace formula;

namespace {

double MatrixAdd(const double& lhs, const double& rhs)
{
    return ::rtl::math::approxAdd( lhs,rhs);
}

double MatrixSub(const double& lhs, const double& rhs)
{
    return ::rtl::math::approxSub( lhs,rhs);
}

double MatrixMul(const double& lhs, const double& rhs)
{
    return lhs * rhs;
}

double MatrixDiv(const double& lhs, const double& rhs)
{
    return ScInterpreter::div( lhs,rhs);
}

double MatrixPow(const double& lhs, const double& 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,
                   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, 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, 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<double>& 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 let's 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<SCSIZE>(nCol2) - nCol1 + 1;
    SCSIZE nMatRows = static_cast<SCSIZE>(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<FormulaToken*>((*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<SCSIZE>(nThisCol - nCol1);
            const SCSIZE nMatRow = static_cast<SCSIZE>(nThisRow - nRow1);
            ScRefCellValue aCell( aCellIter.getRefCellValue());
            if (aCellIter.isEmpty() || aCell.hasEmptyValue())
            {
                pMat->PutEmpty( nMatCol, nMatRow);
            }
            else if (aCell.hasError())
            {
                pMat->PutError( aCell.getFormula()->GetErrCode(), nMatCol, nMatRow);
            }
            else if (aCell.hasNumeric())
            {
                double fVal = aCell.getValue();
                // CELLTYPE_FORMULA already stores the rounded value.
                if (aCell.getType() == 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<SCSIZE>(GetUInt32());
    SCSIZE nC = static_cast<SCSIZE>(GetUInt32());
    if (nGlobalError != FormulaError::NONE)
    {
        PushError( nGlobalError);
        return;
    }
    switch (GetStackType())
    {
        case svSingleRef :
        {
            ScAddress aAdr;
            PopSingleRef( aAdr );
            ScRefCellValue aCell(mrDoc, aAdr);
            if (aCell.getType() == CELLTYPE_FORMULA)
            {
                FormulaError nErrCode = aCell.getFormula()->GetErrCode();
                if (nErrCode != FormulaError::NONE)
                    PushError( nErrCode);
                else
                {
                    const ScMatrix* pMat = aCell.getFormula()->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<SCCOL>(nR) &&
                    nRow2 - nRow1 >= static_cast<SCROW>(nC) &&
                    nTab1 == nTab2)
            {
                ScAddress aAdr( sal::static_int_cast<SCCOL>( nCol1 + nR ),
                                sal::static_int_cast<SCROW>( 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<SCSIZE>(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; i<n && !bSingular; i++)
        bSingular = (mA->GetDouble(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::ScMatSequence()
{
    sal_uInt8 nParamCount = GetByte();
    if (!MustHaveParamCount(nParamCount, 1, 4))
        return;

    // 4th argument is the step number (optional)
    double nSteps = 1.0;
    if (nParamCount == 4)
        nSteps = GetDoubleWithDefault(nSteps);

    // 3d argument is the start number (optional)
    double nStart = 1.0;
    if (nParamCount >= 3)
        nStart = GetDoubleWithDefault(nStart);

    // 2nd argument is the col number (optional)
    sal_Int32 nColumns = 1;
    if (nParamCount >= 2)
    {
        nColumns = GetInt32WithDefault(nColumns);
        if (nColumns < 1)
        {
            PushIllegalArgument();
            return;
        }
    }

    // 1st argument is the row number (required)
    sal_Int32 nRows = GetInt32WithDefault(1);
    if (nRows < 1)
    {
        PushIllegalArgument();
        return;
    }

    if (nGlobalError != FormulaError::NONE)
    {
        PushError(nGlobalError);
        return;
    }

    size_t nMatrixSize = static_cast<size_t>(nColumns) * nRows;
    ScMatrixRef pResMat = GetNewMat(nColumns, nRows, /*bEmpty*/true);
    for (size_t iPos = 0; iPos < nMatrixSize; iPos++)
    {
        pResMat->PutDoubleTrans(nStart, iPos);
        nStart = nStart + nSteps;
    }

    if (pResMat)
    {
        PushMatrix(pResMat);
    }
    else
    {
        PushIllegalParameter();
        return;
    }
}

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;
}

static ScMatrixRef lcl_MatrixCalculation(
    const ScMatrix& rMat1, const ScMatrix& rMat2, ScInterpreter* pInterpreter, const ScMatrix::CalculateOpFunction& Op)
{
    SCSIZE nC1, nC2, nMinC;
    SCSIZE nR1, nR2, nMinR;
    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)
        xResMat->ExecuteBinaryOp(nMinC, nMinR, rMat1, rMat2, pInterpreter, Op);
    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, mrContext, 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;
    bool bDuration = false;
    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;
                bDuration = true;
            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;
                bDuration = true;
            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, MatrixSub);
        }
        else
        {
            pResMat = lcl_MatrixCalculation( *pMat1, *pMat2, this, MatrixAdd);
        }

        if (!pResMat)
            PushNoValue();
        else
            PushMatrix(pResMat);
    }
    else if (pMat1 || pMat2)
    {
        double fVal;
        bool bFlag;
        ScMatrixRef pMat = std::move(pMat1);
        if (!pMat)
        {
            fVal = fVal1;
            pMat = std::move(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 ((nFuncFmtType == SvNumFormatType::DURATION || bDuration)
                && ((_bSub && std::fabs(fVal1 - fVal2) <= SAL_MAX_INT32)
                    || (!_bSub && std::fabs(fVal1 + fVal2) <= SAL_MAX_INT32)))
        {
            // Limit to microseconds resolution on date inflicted or duration
            // values of 24 hours or more.
            const sal_uInt64 nEpsilon = ((std::fabs(fVal1) >= 1.0 || std::fabs(fVal2) >= 1.0) ?
                    ::tools::Duration::kAccuracyEpsilonNanosecondsMicroseconds :
                    ::tools::Duration::kAccuracyEpsilonNanoseconds);
            if (_bSub)
                PushDouble( ::tools::Duration( fVal1 - fVal2, nEpsilon).GetInDays());
            else
                PushDouble( ::tools::Duration( fVal1 + fVal2, nEpsilon).GetInDays());
        }
        else
        {
            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 = std::move(pMat1);
        if (!pMat)
        {
            sStr = sStr1;
            pMat = std::move(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(mrContext, 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(mrContext, 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, MatrixMul);
        if (!pResMat)
            PushNoValue();
        else
            PushMatrix(pResMat);
    }
    else if (pMat1 || pMat2)
    {
        double fVal;
        ScMatrixRef pMat = std::move(pMat1);
        if (!pMat)
        {
            fVal = fVal1;
            pMat = std::move(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, MatrixDiv);
        if (!pResMat)
            PushNoValue();
        else
            PushMatrix(pResMat);
    }
    else if (pMat1 || pMat2)
    {
        double fVal;
        bool bFlag;
        ScMatrixRef pMat = std::move(pMat1);
        if (!pMat)
        {
            fVal = fVal1;
            pMat = std::move(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, MatrixPow);
        if (!pResMat)
            PushNoValue();
        else
            PushMatrix(pResMat);
    }
    else if (pMat1 || pMat2)
    {
        double fVal;
        bool bFlag;
        ScMatrixRef pMat = std::move(pMat1);
        if (!pMat)
        {
            fVal = fVal1;
            pMat = std::move(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<double> 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, MatrixSub);
    if (!pResMat)
    {
        PushNoValue();
    }
    else
    {
        PushDouble(pResMat->SumSquare(false).maAccumulator.get());
    }
}

void ScInterpreter::ScFrequency()
{
    if ( !MustHaveParamCount( GetByte(), 2 ) )
        return;

    std::vector<double>  aBinArray;
    std::vector<tools::Long>    aBinIndexOrder;

    GetSortArray( 1, aBinArray, &aBinIndexOrder, false, false );
    SCSIZE nBinSize = aBinArray.size();
    if (nGlobalError != FormulaError::NONE)
    {
        PushNoValue();
        return;
    }

    std::vector<double>  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<double>(nCount), aBinIndexOrder[j]);
    }
    pResMat->PutDouble(static_cast<double>(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).

// <A;B> 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; i<nM; i++)
        fSum += pMatA->GetDouble(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; row<nN; row++)
        fNorm  += (pMatA->GetDouble(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; col<nN; col++)
        fNorm  += (pMatA->GetDouble(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; row<nN; row++)
    {
        double fVal = fabs(pMatA->GetDouble(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; col<nN; col++)
    {
        double fVal = fabs(pMatA->GetDouble(col,nR));
        if (fNorm < fVal)
            fNorm = fVal;
    }
    return fNorm;
}

// Special version for use within QR decomposition.
// <A(Ca);B(Cb)> 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; row<nN; row++)
        fResult += pMatA->GetDouble(nCa,row) * pMatB->GetDouble(nCb,row);
    return fResult.get();
}

// <A(Ra);B(Rb)> 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; col<nN; col++)
        fResult += pMatA->GetDouble(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 <nK; col++)
    {
        // calculate vector u of the householder transformation
        const double fScale = lcl_GetColumnMaximumNorm(pMatA, col, col, nN);
        if (fScale == 0.0)
        {
            // A is singular
            return false;
        }
        for (SCSIZE row = col; row <nN; row++)
            pMatA->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; c<nK; c++)
        {
            const double fSum =lcl_GetColumnSumProduct(pMatA, col, pMatA, c, col, nN);
            for (SCSIZE row = col; row <nN; row++)
                pMatA->PutDouble( 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 <nK; row++)
    {
        // calculate vector u of the householder transformation
        const double fScale = lcl_TGetColumnMaximumNorm(pMatA, row, row, nN);
        if (fScale == 0.0)
        {
            // A is singular
            return false;
        }
        for (SCSIZE col = row; col <nN; col++)
            pMatA->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; r<nK; r++)
        {
            fSum =lcl_TGetColumnSumProduct(pMatA, row, pMatA, r, row, nN);
            for (SCSIZE col = row; col <nN; col++)
                pMatA->PutDouble(
                    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 * o3tl::div_allow_zero(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 * o3tl::div_allow_zero(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; col<nK ; col++)
            if (bIsTransposed)
                fSum -= pMatA->GetDouble(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 ; i<nN; i++)
        fSum += pMat->GetDouble(i);
    return fSum.get()/static_cast<double>(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<double>(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<double>(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; i<nN; i++)
    {
        const double fTemp = pMatY->GetDouble(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 = std::move(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<double>(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 && (N<K+1)) || (!bConstant && (N<K)) || (N<1) || (K<1))
    {
        PushIllegalParameter();
        return;
    }

    ScMatrixRef pResMat;
    if (bStats)
        pResMat = GetNewMat(K+1,5, /*bEmpty*/true);
    else
        pResMat = GetNewMat(K+1,1, /*bEmpty*/true);
    if (!pResMat)
    {
        PushError(FormulaError::CodeOverflow);
        return;
    }
    // Fill unused cells in pResMat; order (column,row)
    if (bStats)
    {
        for (SCSIZE i=2; i<K+1; i++)
        {
            pResMat->PutError( 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 = std::move(pNewX);
        pMatY = std::move(pNewY);
        // DeltaY is possible here; DeltaX depends on nCase, so later
        fMeanY = lcl_GetMeanOverAll(pMatY, N);
        for (SCSIZE i=0; i<N; i++)
        {
            pMatY->PutDouble( ::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; i<N; i++)
            {
                pMatX->PutDouble( ::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<double>( 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<double>(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<double>(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<double>( 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; i<K; i++)
                        pResMat->PutDouble(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<double>(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<double>(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<double>( 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; i<K; i++)
                        pResMat->PutDouble(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<double>(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<double>(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 && (N<K+1)) || (!bConstant && (N<K)) || (N<1) || (K<1))
    {
        PushIllegalParameter();
        return;
    }

    // Set default pMatNewX if necessary
    SCSIZE nCXN, nRXN;
    SCSIZE nCountXN;
    if (!pMatNewX)
    {
        nCXN = nCX;
        nRXN = nRX;
        nCountXN = nCXN * nRXN;
        pMatNewX = pMatX->Clone(); // 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 = std::move(pCopyX);
        pMatY = std::move(pCopyY);
        // DeltaY is possible here; DeltaX depends on nCase, so later
        fMeanY = lcl_GetMeanOverAll(pMatY, N);
        for (SCSIZE i=0; i<N; i++)
        {
            pMatY->PutDouble( ::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; i<N; i++)
            {
                pMatX->PutDouble( ::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.getType() != CELLTYPE_FORMULA)
    {
        PushError( FormulaError::NoRef );
        return;
    }

    if (aCell.getFormula()->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.getFormula()->GetMatrix();
    if (pMat)
    {
        SCSIZE nCols, nRows;
        pMat->GetDimensions( nCols, nRows );
        SCSIZE nC = static_cast<SCSIZE>(aPos.Col() - aAdr.Col());
        SCSIZE nR = static_cast<SCSIZE>(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.getFormula()->GetErrCode();
        if (nErr != FormulaError::NONE)
            PushError( nErr );
        else if (aCell.getFormula()->IsValue())
            PushDouble(aCell.getFormula()->GetValue());
        else
        {
            svl::SharedString aVal = aCell.getFormula()->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( u"" SC_INFO_OSVERSION ""_ustr );
    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: */
