/* -*- 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 <memory>
#include <interpre.hxx>
 
#include <comphelper/string.hxx>
#include <o3tl/float_int_conversion.hxx>
#include <o3tl/string_view.hxx>
#include <sfx2/bindings.hxx>
#include <sfx2/linkmgr.hxx>
#include <sfx2/objsh.hxx>
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <tools/duration.hxx>
#include <sal/macros.h>
#include <osl/diagnose.h>
 
#include <sc.hrc>
#include <ddelink.hxx>
#include <scmatrix.hxx>
#include <formulacell.hxx>
#include <document.hxx>
#include <dociter.hxx>
#include <docsh.hxx>
#include <unitconv.hxx>
#include <hints.hxx>
#include <dpobject.hxx>
#include <tokenarray.hxx>
#include <globalnames.hxx>
#include <stlpool.hxx>
#include <stlsheet.hxx>
#include <dpcache.hxx>
 
#include <com/sun/star/sheet/DataPilotFieldFilter.hpp>
 
#include <string.h>
 
using namespace com::sun::star;
using namespace formula;
 
#define SCdEpsilon                1.0E-7
 
// Date and Time
 
double ScInterpreter::GetDateSerial( sal_Int16 nYear, sal_Int16 nMonth, sal_Int16 nDay,
        bool bStrict )
{
    if ( nYear < 100 && !bStrict )
        nYear = mrContext.NFExpandTwoDigitYear( nYear );
    // Do not use a default Date ctor here because it asks system time with a
    // performance penalty.
    sal_Int16 nY, nM, nD;
    if (bStrict)
    {
        nY = nYear;
        nM = nMonth;
        nD = nDay;
    }
    else
    {
        if (nMonth > 0)
        {
            nY = nYear + (nMonth-1) / 12;
            nM = ((nMonth-1) % 12) + 1;
        }
        else
        {
            nY = nYear + (nMonth-12) / 12;
            nM = 12 - (-nMonth) % 12;
        }
        nD = 1;
    }
    Date aDate( nD, nM, nY);
    if (!bStrict)
        aDate.AddDays( nDay - 1 );
    if (aDate.IsValidAndGregorian())
        return static_cast<double>(aDate - mrContext.NFGetNullDate());
    else
    {
        SetError(FormulaError::NoValue);
        return 0;
    }
}
 
void ScInterpreter::ScGetActDate()
{
    nFuncFmtType = SvNumFormatType::DATE;
    Date aActDate( Date::SYSTEM );
    tools::Long nDiff = aActDate - mrContext.NFGetNullDate();
    PushDouble(static_cast<double>(nDiff));
}
 
void ScInterpreter::ScGetActTime()
{
    nFuncFmtType = SvNumFormatType::DATETIME;
    DateTime aActTime( DateTime::SYSTEM );
    tools::Long nDiff = aActTime - mrContext.NFGetNullDate();
    double fTime = aActTime.GetHour()    / static_cast<double>(::tools::Time::hourPerDay)   +
                   aActTime.GetMin()     / static_cast<double>(::tools::Time::minutePerDay) +
                   aActTime.GetSec()     / static_cast<double>(::tools::Time::secondPerDay) +
                   aActTime.GetNanoSec() / static_cast<double>(::tools::Time::nanoSecPerDay);
    PushDouble( static_cast<double>(nDiff) + fTime );
}
 
void ScInterpreter::ScGetYear()
{
    Date aDate = mrContext.NFGetNullDate();
    aDate.AddDays( GetFloor32());
    PushDouble( static_cast<double>(aDate.GetYear()) );
}
 
void ScInterpreter::ScGetMonth()
{
    Date aDate = mrContext.NFGetNullDate();
    aDate.AddDays( GetFloor32());
    PushDouble( static_cast<double>(aDate.GetMonth()) );
}
 
void ScInterpreter::ScGetDay()
{
    Date aDate = mrContext.NFGetNullDate();
    if (aDate.CheckedAddDays(GetFloor32()))
        PushDouble(static_cast<double>(aDate.GetDay()));
    else
    {
        SetError(FormulaError::IllegalArgument);
        PushDouble(HUGE_VAL);
    }
}
 
void ScInterpreter::ScGetMin()
{
    sal_uInt16 nHour, nMinute, nSecond;
    double fFractionOfSecond;
    tools::Time::GetClock( GetDouble(), nHour, nMinute, nSecond, fFractionOfSecond, 0);
    PushDouble( nMinute);
}
 
void ScInterpreter::ScGetSec()
{
    sal_uInt16 nHour, nMinute, nSecond;
    double fFractionOfSecond;
    tools::Time::GetClock( GetDouble(), nHour, nMinute, nSecond, fFractionOfSecond, 0);
    if ( fFractionOfSecond >= 0.5 )
        nSecond = ( nSecond + 1 ) % 60;
    PushDouble( nSecond );
 
}
 
void ScInterpreter::ScGetHour()
{
    sal_uInt16 nHour, nMinute, nSecond;
    double fFractionOfSecond;
    tools::Time::GetClock( GetDouble(), nHour, nMinute, nSecond, fFractionOfSecond, 0);
    PushDouble( nHour);
}
 
void ScInterpreter::ScGetDateValue()
{
    OUString aInputString = GetString().getString();
    sal_uInt32 nFIndex = 0;                 // for a default country/language
    double fVal;
    if (mrContext.NFIsNumberFormat(aInputString, nFIndex, fVal))
    {
        SvNumFormatType eType = mrContext.NFGetType(nFIndex);
        if (eType == SvNumFormatType::DATE || eType == SvNumFormatType::DATETIME)
        {
            nFuncFmtType = SvNumFormatType::DATE;
            PushDouble(::rtl::math::approxFloor(fVal));
        }
        else
            PushIllegalArgument();
    }
    else
        PushIllegalArgument();
}
 
void ScInterpreter::ScGetDayOfWeek()
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
        return;
 
    sal_Int16 nFlag;
    if (nParamCount == 2)
        nFlag = GetInt16();
    else
        nFlag = 1;
 
    Date aDate = mrContext.NFGetNullDate();
    aDate.AddDays( GetFloor32());
    int nVal = static_cast<int>(aDate.GetDayOfWeek());  // MONDAY = 0
    switch (nFlag)
    {
        case 1:     // Sunday = 1
            if (nVal == 6)
                nVal = 1;
            else
                nVal += 2;
        break;
        case 2:     // Monday = 1
            nVal += 1;
        break;
        case 3:     // Monday = 0
            ;   // nothing
        break;
        case 11:    // Monday = 1
        case 12:    // Tuesday = 1
        case 13:    // Wednesday = 1
        case 14:    // Thursday = 1
        case 15:    // Friday = 1
        case 16:    // Saturday = 1
        case 17:    // Sunday = 1
            if (nVal < nFlag - 11)      // x = nFlag - 11 = 0,1,2,3,4,5,6
                nVal += 19 - nFlag;     // nVal += (8 - (nFlag - 11) = 8 - x = 8,7,6,5,4,3,2)
            else
                nVal -= nFlag - 12;     // nVal -= ((nFlag - 11) - 1 = x - 1 = -1,0,1,2,3,4,5)
        break;
        default:
            SetError( FormulaError::IllegalArgument);
    }
    PushInt( nVal );
}
 
void ScInterpreter::ScWeeknumOOo()
{
    if ( MustHaveParamCount( GetByte(), 2 ) )
    {
        sal_Int16 nFlag = GetInt16();
 
        Date aDate = mrContext.NFGetNullDate();
        aDate.AddDays( GetFloor32());
        PushInt( static_cast<int>(aDate.GetWeekOfYear( nFlag == 1 ? SUNDAY : MONDAY )));
    }
}
 
void ScInterpreter::ScGetWeekOfYear()
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
        return;
 
    sal_Int16 nFlag = (nParamCount == 1) ? 1 : GetInt16WithDefault(1);
 
    Date aDate = mrContext.NFGetNullDate();
    aDate.AddDays( GetFloor32());
 
    sal_Int32 nMinimumNumberOfDaysInWeek;
    DayOfWeek eFirstDayOfWeek;
    switch ( nFlag )
    {
        case   1 :
            eFirstDayOfWeek = SUNDAY;
            nMinimumNumberOfDaysInWeek = 1;
            break;
        case   2 :
            eFirstDayOfWeek = MONDAY;
            nMinimumNumberOfDaysInWeek = 1;
            break;
        case  11 :
        case  12 :
        case  13 :
        case  14 :
        case  15 :
        case  16 :
        case  17 :
            eFirstDayOfWeek = static_cast<DayOfWeek>( nFlag - 11 ); // MONDAY := 0
            nMinimumNumberOfDaysInWeek = 1; //the week containing January 1 is week 1
            break;
        case  21 :
        case 150 :
            // ISO 8601
            eFirstDayOfWeek = MONDAY;
            nMinimumNumberOfDaysInWeek = 4;
            break;
        default :
            PushIllegalArgument();
            return;
    }
    PushInt( static_cast<int>(aDate.GetWeekOfYear( eFirstDayOfWeek, nMinimumNumberOfDaysInWeek )) );
}
 
void ScInterpreter::ScGetIsoWeekOfYear()
{
    if ( MustHaveParamCount( GetByte(), 1 ) )
    {
        Date aDate = mrContext.NFGetNullDate();
        aDate.AddDays( GetFloor32());
        PushInt( static_cast<int>(aDate.GetWeekOfYear()) );
    }
}
 
void ScInterpreter::ScEasterSunday()
{
    nFuncFmtType = SvNumFormatType::DATE;
    if ( !MustHaveParamCount( GetByte(), 1 ) )
        return;
 
    sal_Int16 nYear = GetInt16();
    if (nGlobalError != FormulaError::NONE)
    {
        PushError( nGlobalError);
        return;
    }
    if ( nYear < 100 )
        nYear = mrContext.NFExpandTwoDigitYear( nYear );
    if (nYear < 1583 || nYear > 9956)
    {
        // Valid Gregorian and maximum year constraints not met.
        PushIllegalArgument();
        return;
    }
    // don't worry, be happy :)
    int B,C,D,E,F,G,H,I,K,L,M,N,O;
    N = nYear % 19;
    B = int(nYear / 100);
    C = nYear % 100;
    D = int(B / 4);
    E = B % 4;
    F = int((B + 8) / 25);
    G = int((B - F + 1) / 3);
    H = (19 * N + B - D - G + 15) % 30;
    I = int(C / 4);
    K = C % 4;
    L = (32 + 2 * E + 2 * I - H - K) % 7;
    M = int((N + 11 * H + 22 * L) / 451);
    O = H + L - 7 * M + 114;
    sal_Int16 nDay = sal::static_int_cast<sal_Int16>( O % 31 + 1 );
    sal_Int16 nMonth = sal::static_int_cast<sal_Int16>( int(O / 31) );
    PushDouble( GetDateSerial( nYear, nMonth, nDay, true ) );
}
 
FormulaError ScInterpreter::GetWeekendAndHolidayMasks(
    const sal_uInt8 nParamCount, const sal_Int32 nNullDate, std::vector< double >& rSortArray,
    bool bWeekendMask[ 7 ] )
{
    if ( nParamCount == 4 )
    {
        std::vector< double > nWeekendDays;
        GetNumberSequenceArray( 1, nWeekendDays, false );
        if ( nGlobalError != FormulaError::NONE )
            return nGlobalError;
        else
        {
            if ( nWeekendDays.size() != 7 )
                return  FormulaError::IllegalArgument;
 
            // Weekend days defined by string, Sunday...Saturday
            for ( int i = 0; i < 7; i++ )
                bWeekendMask[ i ] = static_cast<bool>(nWeekendDays[ ( i == 6 ? 0 : i + 1 ) ]);
        }
    }
    else
    {
        for ( int i = 0; i < 7; i++ )
            bWeekendMask[ i] = false;
 
        bWeekendMask[ SATURDAY ] = true;
        bWeekendMask[ SUNDAY ]   = true;
    }
 
    if ( nParamCount >= 3 )
    {
        GetSortArray( 1, rSortArray, nullptr, true, true );
        size_t nMax = rSortArray.size();
        for ( size_t i = 0; i < nMax; i++ )
            rSortArray.at( i ) = ::rtl::math::approxFloor( rSortArray.at( i ) ) + nNullDate;
    }
 
    return nGlobalError;
}
 
FormulaError ScInterpreter::GetWeekendAndHolidayMasks_MS(
    const sal_uInt8 nParamCount, const sal_Int32 nNullDate, std::vector< double >& rSortArray,
    bool bWeekendMask[ 7 ], bool bWorkdayFunction )
{
    FormulaError nErr = FormulaError::NONE;
    OUString aWeekendDays;
    if ( nParamCount == 4 )
    {
        GetSortArray( 1, rSortArray, nullptr, true, true );
        size_t nMax = rSortArray.size();
        for ( size_t i = 0; i < nMax; i++ )
            rSortArray.at( i ) = ::rtl::math::approxFloor( rSortArray.at( i ) ) + nNullDate;
    }
 
    if ( nParamCount >= 3 )
    {
        if ( IsMissing() )
            Pop();
        else
        {
            switch ( GetStackType() )
            {
                case svDoubleRef :
                case svExternalDoubleRef :
                    return FormulaError::NoValue;
 
                default :
                    {
                        double fDouble;
                        svl::SharedString aSharedString;
                        bool bDouble = GetDoubleOrString( fDouble, aSharedString);
                        if ( bDouble )
                        {
                            if ( fDouble >= 1.0 && fDouble <= 17 )
                                aWeekendDays = OUString::number( fDouble );
                            else
                                return FormulaError::NoValue;
                        }
                        else
                        {
                            if ( aSharedString.isEmpty() || aSharedString.getLength() != 7 ||
                                 ( bWorkdayFunction && aSharedString.getString() == "1111111" ) )
                                return FormulaError::NoValue;
                            else
                                aWeekendDays = aSharedString.getString();
                        }
                    }
                    break;
            }
        }
    }
 
    for ( int i = 0; i < 7; i++ )
        bWeekendMask[ i] = false;
 
    if ( aWeekendDays.isEmpty() )
    {
        bWeekendMask[ SATURDAY ] = true;
        bWeekendMask[ SUNDAY ]   = true;
    }
    else
    {
        switch ( aWeekendDays.getLength() )
        {
            case 1 :
                // Weekend days defined by code
                switch ( aWeekendDays[ 0 ] )
                {
                    case '1' : bWeekendMask[ SATURDAY ]  = true; bWeekendMask[ SUNDAY ]    = true; break;
                    case '2' : bWeekendMask[ SUNDAY ]    = true; bWeekendMask[ MONDAY ]    = true; break;
                    case '3' : bWeekendMask[ MONDAY ]    = true; bWeekendMask[ TUESDAY ]   = true; break;
                    case '4' : bWeekendMask[ TUESDAY ]   = true; bWeekendMask[ WEDNESDAY ] = true; break;
                    case '5' : bWeekendMask[ WEDNESDAY ] = true; bWeekendMask[ THURSDAY ]  = true; break;
                    case '6' : bWeekendMask[ THURSDAY ]  = true; bWeekendMask[ FRIDAY ]    = true; break;
                    case '7' : bWeekendMask[ FRIDAY ]    = true; bWeekendMask[ SATURDAY ]  = true; break;
                    default  : nErr = FormulaError::IllegalArgument;                                          break;
                }
                break;
            case 2 :
                // Weekend day defined by code
                if ( aWeekendDays[ 0 ] == '1' )
                {
                    switch ( aWeekendDays[ 1 ] )
                    {
                        case '1' : bWeekendMask[ SUNDAY ]    = true; break;
                        case '2' : bWeekendMask[ MONDAY ]    = true; break;
                        case '3' : bWeekendMask[ TUESDAY ]   = true; break;
                        case '4' : bWeekendMask[ WEDNESDAY ] = true; break;
                        case '5' : bWeekendMask[ THURSDAY ]  = true; break;
                        case '6' : bWeekendMask[ FRIDAY ]    = true; break;
                        case '7' : bWeekendMask[ SATURDAY ]  = true; break;
                        default  : nErr = FormulaError::IllegalArgument;        break;
                    }
                }
                else
                    nErr = FormulaError::IllegalArgument;
                break;
            case 7 :
                // Weekend days defined by string
                for ( int i = 0; i < 7 && nErr == FormulaError::NONE; i++ )
                {
                    switch ( aWeekendDays[ i ] )
                    {
                        case '0' : bWeekendMask[ i ] = false; break;
                        case '1' : bWeekendMask[ i ] = true;  break;
                        default  : nErr = FormulaError::IllegalArgument; break;
                    }
                }
                break;
            default :
                nErr = FormulaError::IllegalArgument;
                break;
        }
    }
    return nErr;
}
 
void ScInterpreter::ScNetWorkdays( bool bOOXML_Version )
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 2, 4 ) )
        return;
 
    std::vector<double> nSortArray;
    bool bWeekendMask[ 7 ];
    const Date& rNullDate = mrContext.NFGetNullDate();
    sal_Int32 nNullDate = rNullDate.GetAsNormalizedDays();
    FormulaError nErr;
    if ( bOOXML_Version )
    {
        nErr = GetWeekendAndHolidayMasks_MS( nParamCount, nNullDate,
                        nSortArray, bWeekendMask, false );
    }
    else
    {
        nErr = GetWeekendAndHolidayMasks( nParamCount, nNullDate,
                        nSortArray, bWeekendMask );
    }
    if ( nErr != FormulaError::NONE )
        PushError( nErr );
    else
    {
        sal_Int32 nDate2 = GetFloor32();
        sal_Int32 nDate1 = GetFloor32();
        if (nGlobalError != FormulaError::NONE || (nDate1 > SAL_MAX_INT32 - nNullDate) || nDate2 > (SAL_MAX_INT32 - nNullDate))
        {
            PushIllegalArgument();
            return;
        }
        nDate2 += nNullDate;
        nDate1 += nNullDate;
 
        sal_Int32 nCnt = 0;
        size_t nRef = 0;
        bool bReverse = ( nDate1 > nDate2 );
        if ( bReverse )
            std::swap( nDate1, nDate2 );
        size_t nMax = nSortArray.size();
        while ( nDate1 <= nDate2 )
        {
            if ( !bWeekendMask[ GetDayOfWeek( nDate1 ) ] )
            {
                while ( nRef < nMax && nSortArray.at( nRef ) < nDate1 )
                    nRef++;
                if ( nRef >= nMax || nSortArray.at( nRef ) != nDate1 )
                    nCnt++;
            }
            ++nDate1;
        }
        PushDouble( static_cast<double>( bReverse ? -nCnt : nCnt ) );
    }
}
 
void ScInterpreter::ScWorkday_MS()
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 2, 4 ) )
        return;
 
    nFuncFmtType = SvNumFormatType::DATE;
    std::vector<double> nSortArray;
    bool bWeekendMask[ 7 ];
    const Date& rNullDate = mrContext.NFGetNullDate();
    sal_Int32 nNullDate = rNullDate.GetAsNormalizedDays();
    FormulaError nErr = GetWeekendAndHolidayMasks_MS( nParamCount, nNullDate,
                        nSortArray, bWeekendMask, true );
    if ( nErr != FormulaError::NONE )
        PushError( nErr );
    else
    {
        sal_Int32 nDays = GetFloor32();
        sal_Int32 nDate = GetFloor32();
        if (nGlobalError != FormulaError::NONE || (nDate > SAL_MAX_INT32 - nNullDate))
        {
            PushIllegalArgument();
            return;
        }
        nDate += nNullDate;
 
        if ( !nDays )
            PushDouble( static_cast<double>( nDate - nNullDate ) );
        else
        {
            size_t nMax = nSortArray.size();
            if ( nDays > 0 )
            {
                size_t nRef = 0;
                while ( nDays )
                {
                    do
                    {
                        ++nDate;
                    }
                    while ( bWeekendMask[ GetDayOfWeek( nDate ) ] ); //jump over weekend day(s)
 
                    while ( nRef < nMax && nSortArray.at( nRef ) < nDate )
                        nRef++;
 
                    if ( nRef >= nMax || nSortArray.at( nRef ) != nDate )
                        nDays--;
                }
            }
            else
            {
                sal_Int16 nRef = nMax - 1;
                while ( nDays )
                {
                    do
                    {
                      --nDate;
                    }
                    while ( bWeekendMask[ GetDayOfWeek( nDate ) ] ); //jump over weekend day(s)
 
                    while ( nRef >= 0 && nSortArray.at( nRef ) > nDate )
                        nRef--;
 
                    if (nRef < 0 || nSortArray.at(nRef) != nDate)
                         nDays++;
                }
            }
            PushDouble( static_cast<double>( nDate - nNullDate ) );
        }
    }
}
 
void ScInterpreter::ScGetDate()
{
    nFuncFmtType = SvNumFormatType::DATE;
    if ( !MustHaveParamCount( GetByte(), 3 ) )
        return;
 
    sal_Int16 nDay   = GetInt16();
    sal_Int16 nMonth = GetInt16();
    if (IsMissing())
        SetError( FormulaError::ParameterExpected);    // Year must be given.
    sal_Int16 nYear  = GetInt16();
    if (nGlobalError != FormulaError::NONE || nYear < 0)
        PushIllegalArgument();
    else
        PushDouble(GetDateSerial(nYear, nMonth, nDay, false));
}
 
void ScInterpreter::ScGetTime()
{
    nFuncFmtType = SvNumFormatType::TIME;
    if ( MustHaveParamCount( GetByte(), 3 ) )
    {
        double fSec = GetDouble();
        double fMin = GetDouble();
        double fHour = GetDouble();
        double fTime = fmod( (fHour * ::tools::Time::secondPerHour) + (fMin * ::tools::Time::secondPerMinute) + fSec, DATE_TIME_FACTOR) / DATE_TIME_FACTOR;
        if (fTime < 0)
            PushIllegalArgument();
        else
            PushDouble( fTime);
    }
}
 
void ScInterpreter::ScGetDiffDate()
{
    if ( MustHaveParamCount( GetByte(), 2 ) )
    {
        double fDate2 = GetDouble();
        double fDate1 = GetDouble();
        PushDouble(fDate1 - fDate2);
    }
}
 
void ScInterpreter::ScGetDiffDate360()
{
    /* Implementation follows
     * http://www.bondmarkets.com/eCommerce/SMD_Fields_030802.pdf
     * Appendix B: Day-Count Bases, there are 7 different ways to calculate the
     * 30-days count. That document also claims that Excel implements the "PSA
     * 30" or "NASD 30" method (funny enough they also state that Excel is the
     * only tool that does so).
     *
     * Note that the definition given in
     * http://msdn.microsoft.com/library/en-us/office97/html/SEB7C.asp
     * is _not_ the way how it is actually calculated by Excel (that would not
     * even match any of the 7 methods mentioned above) and would result in the
     * following test cases producing wrong results according to that appendix B:
     *
     * 28-Feb-95  31-Aug-95  181 instead of 180
     * 29-Feb-96  31-Aug-96  181 instead of 180
     * 30-Jan-96  31-Mar-96   61 instead of  60
     * 31-Jan-96  31-Mar-96   61 instead of  60
     *
     * Still, there is a difference between OOoCalc and Excel:
     * In Excel:
     * 02-Feb-99 31-Mar-00 results in  419
     * 31-Mar-00 02-Feb-99 results in -418
     * In Calc the result is 419 respectively -419. I consider the -418 a bug in Excel.
     */
 
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 2, 3 ) )
        return;
 
    bool bFlag = nParamCount == 3 && GetBool();
    sal_Int32 nDate2 = GetFloor32();
    sal_Int32 nDate1 = GetFloor32();
    if (nGlobalError != FormulaError::NONE)
        PushError( nGlobalError);
    else
    {
        sal_Int32 nSign;
        // #i84934# only for non-US European algorithm swap dates. Else
        // follow Excel's meaningless extrapolation for "interoperability".
        if (bFlag && (nDate2 < nDate1))
        {
            nSign = nDate1;
            nDate1 = nDate2;
            nDate2 = nSign;
            nSign = -1;
        }
        else
            nSign = 1;
        Date aDate1 = mrContext.NFGetNullDate();
        aDate1.AddDays( nDate1);
        Date aDate2 = mrContext.NFGetNullDate();
        aDate2.AddDays( nDate2);
        if (aDate1.GetDay() == 31)
            aDate1.AddDays( -1);
        else if (!bFlag)
        {
            if (aDate1.GetMonth() == 2)
            {
                switch ( aDate1.GetDay() )
                {
                    case 28 :
                        if ( !aDate1.IsLeapYear() )
                            aDate1.SetDay(30);
                    break;
                    case 29 :
                        aDate1.SetDay(30);
                    break;
                }
            }
        }
        if (aDate2.GetDay() == 31)
        {
            if (!bFlag )
            {
                if (aDate1.GetDay() == 30)
                    aDate2.AddDays( -1);
            }
            else
                aDate2.SetDay(30);
        }
        PushDouble( static_cast<double>(nSign) *
            (  static_cast<double>(aDate2.GetDay()) + static_cast<double>(aDate2.GetMonth()) * 30.0 +
               static_cast<double>(aDate2.GetYear()) * 360.0
             - static_cast<double>(aDate1.GetDay()) - static_cast<double>(aDate1.GetMonth()) * 30.0
             - static_cast<double>(aDate1.GetYear()) * 360.0) );
    }
}
 
// fdo#44456 function DATEDIF as defined in ODF1.2 (Par. 6.10.3)
void ScInterpreter::ScGetDateDif()
{
    if ( !MustHaveParamCount( GetByte(), 3 ) )
        return;
 
    OUString aInterval = GetString().getString();
    sal_Int32 nDate2 = GetFloor32();
    sal_Int32 nDate1 = GetFloor32();
 
    if (nGlobalError != FormulaError::NONE)
    {
        PushError( nGlobalError);
        return;
    }
 
    // Excel doesn't swap dates or return negative numbers, so don't we.
    if (nDate1 > nDate2)
    {
        PushIllegalArgument();
        return;
    }
 
    double dd = nDate2 - nDate1;
    // Zero difference or number of days can be returned immediately.
    if (dd == 0.0 || aInterval.equalsIgnoreAsciiCase( "d" ))
    {
        PushDouble( dd );
        return;
    }
 
    // split dates in day, month, year for use with formats other than "d"
    sal_uInt16 d1, m1, d2, m2;
    sal_Int16 y1, y2;
    Date aDate1( mrContext.NFGetNullDate());
    aDate1.AddDays( nDate1);
    y1 = aDate1.GetYear();
    m1 = aDate1.GetMonth();
    d1 = aDate1.GetDay();
    Date aDate2( mrContext.NFGetNullDate());
    aDate2.AddDays( nDate2);
    y2 = aDate2.GetYear();
    m2 = aDate2.GetMonth();
    d2 = aDate2.GetDay();
 
    // Close the year 0 gap to calculate year difference.
    if (y1 < 0 && y2 > 0)
        ++y1;
    else if (y1 > 0 && y2 < 0)
        ++y2;
 
    if (  aInterval.equalsIgnoreAsciiCase( "m" ) )
    {
        // Return number of months.
        int md = m2 - m1 + 12 * (y2 - y1);
        if (d1 > d2)
            --md;
        PushInt( md );
    }
    else if ( aInterval.equalsIgnoreAsciiCase( "y" ) )
    {
        // Return number of years.
        int yd;
        if ( y2 > y1 )
        {
            if (m2 > m1 || (m2 == m1 && d2 >= d1))
                yd = y2 - y1;       // complete years between dates
            else
                yd = y2 - y1 - 1;   // one incomplete year
        }
        else
        {
            // Year is equal as we don't allow reversed arguments, no
            // complete year between dates.
            yd = 0;
        }
        PushInt( yd );
    }
    else if ( aInterval.equalsIgnoreAsciiCase( "md" ) )
    {
        // Return number of days, excluding months and years.
        // This is actually the remainder of days when subtracting years
        // and months from the difference of dates. Birthday-like 23 years
        // and 10 months and 19 days.
 
        // Algorithm's roll-over behavior extracted from Excel by try and
        // error...
        // If day1 <= day2 then simply day2 - day1.
        // If day1 > day2 then set month1 to month2-1 and year1 to
        // year2(-1) and subtract dates, e.g. for 2012-01-28,2012-03-01 set
        // 2012-02-28 and then (2012-03-01)-(2012-02-28) => 2 days (leap
        // year).
        // For 2011-01-29,2011-03-01 the non-existent 2011-02-29 rolls over
        // to 2011-03-01 so the result is 0. Same for day 31 in months with
        // only 30 days.
 
        tools::Long nd;
        if (d1 <= d2)
            nd = d2 - d1;
        else
        {
            if (m2 == 1)
            {
                aDate1.SetYear( y2 == 1 ? -1 : y2 - 1 );
                aDate1.SetMonth( 12 );
            }
            else
            {
                aDate1.SetYear( y2 );
                aDate1.SetMonth( m2 - 1 );
            }
            aDate1.Normalize();
            nd = aDate2 - aDate1;
        }
        PushDouble( nd );
    }
    else if ( aInterval.equalsIgnoreAsciiCase( "ym" ) )
    {
        // Return number of months, excluding years.
        int md = m2 - m1 + 12 * (y2 - y1);
        if (d1 > d2)
            --md;
        md %= 12;
        PushInt( md );
    }
    else if ( aInterval.equalsIgnoreAsciiCase( "yd" ) )
    {
        // Return number of days, excluding years.
 
        // Condition corresponds with "y".
        if (m2 > m1 || (m2 == m1 && d2 >= d1))
            aDate1.SetYear( y2 );
        else
            aDate1.SetYear( y2 - 1 );
            // XXX NOTE: Excel for the case 1988-06-22,2012-05-11 returns
            // 323, whereas the result here is 324. Don't they use the leap
            // year of 2012?
            // http://www.cpearson.com/excel/datedif.aspx "DATEDIF And Leap
            // Years" is not correct and Excel 2010 correctly returns 0 in
            // both cases mentioned there. Also using year1 as mentioned
            // produces incorrect results in other cases and different from
            // Excel 2010. Apparently they fixed some calculations.
        aDate1.Normalize();
        double fd = aDate2 - aDate1;
        PushDouble( fd );
    }
    else
        PushIllegalArgument();               // unsupported format
}
 
void ScInterpreter::ScGetTimeValue()
{
    OUString aInputString = GetString().getString();
    sal_uInt32 nFIndex = 0;                 // damit default Land/Spr.
    double fVal;
    if (mrContext.NFIsNumberFormat(aInputString, nFIndex, fVal, SvNumInputOptions::LAX_TIME))
    {
        SvNumFormatType eType = mrContext.NFGetType(nFIndex);
        if (eType == SvNumFormatType::TIME || eType == SvNumFormatType::DATETIME)
        {
            nFuncFmtType = SvNumFormatType::TIME;
            double fDateVal = rtl::math::approxFloor(fVal);
            double fTimeVal = fVal - fDateVal;
            fTimeVal = ::tools::Duration(fTimeVal).GetInDays();  // force corrected
            PushDouble(fTimeVal);
        }
        else
            PushIllegalArgument();
    }
    else
        PushIllegalArgument();
}
 
void ScInterpreter::ScPlusMinus()
{
    double fVal = GetDouble();
    short n = 0;
    if (fVal < 0.0)
        n = -1;
    else if (fVal > 0.0)
        n = 1;
    PushInt( n );
}
 
void ScInterpreter::ScAbs()
{
    PushDouble(std::abs(GetDouble()));
}
 
void ScInterpreter::ScInt()
{
    PushDouble(::rtl::math::approxFloor(GetDouble()));
}
 
void ScInterpreter::RoundNumber( rtl_math_RoundingMode eMode )
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
        return;
 
    double fVal = 0.0;
    if (nParamCount == 1)
        fVal = ::rtl::math::round( GetDouble(), 0, eMode );
    else
    {
        const sal_Int16 nDec = GetInt16();
        const double fX = GetDouble();
        if (nGlobalError == FormulaError::NONE)
        {
            // A quite aggressive approach with 12 significant digits.
            // However, using 14 or some other doesn't work because other
            // values may fail, like =ROUNDDOWN(2-5E-015;13) would produce
            // 2 (another example in tdf#124286).
            constexpr sal_Int16 kSigDig = 12;
 
            if ( ( eMode == rtl_math_RoundingMode_Down ||
                   eMode == rtl_math_RoundingMode_Up ) &&
                 nDec < kSigDig && fmod( fX, 1.0 ) != 0.0 )
 
            {
                // tdf124286 : round to significant digits before rounding
                //             down or up to avoid unexpected rounding errors
                //             caused by decimal -> binary -> decimal conversion
 
                double fRes = fX;
                // Similar to RoundSignificant() but omitting the back-scaling
                // and interim integer rounding before the final rounding,
                // which would result in double rounding. Instead, adjust the
                // decimals and round into integer part before scaling back.
                const double fTemp = floor( log10( std::abs(fRes))) + 1.0 - kSigDig;
                // Avoid inaccuracy of negative powers of 10.
                if (fTemp < 0.0)
                    fRes *= pow(10.0, -fTemp);
                else
                    fRes /= pow(10.0, fTemp);
                if (std::isfinite(fRes))
                {
                    // fRes is now at a decimal normalized scale.
                    // Truncate up-rounding to opposite direction for values
                    // like 0.0600000000000005 =ROUNDUP(8.06-8;2) that here now
                    // is 600000000000.005 and otherwise would yield 0.07
                    if (eMode == rtl_math_RoundingMode_Up)
                        fRes = ::rtl::math::approxFloor(fRes);
                    fVal = ::rtl::math::round( fRes, nDec + fTemp, eMode );
                    if (fTemp < 0.0)
                        fVal /= pow(10.0, -fTemp);
                    else
                        fVal *= pow(10.0, fTemp);
                }
                else
                {
                    // Overflow. Let our round() decide if and how to round.
                    fVal = ::rtl::math::round( fX, nDec, eMode );
                }
            }
            else
                fVal = ::rtl::math::round( fX, nDec, eMode );
        }
    }
    PushDouble(fVal);
}
 
void ScInterpreter::ScRound()
{
    RoundNumber( rtl_math_RoundingMode_Corrected );
}
 
void ScInterpreter::ScRoundDown()
{
    RoundNumber( rtl_math_RoundingMode_Down );
}
 
void ScInterpreter::ScRoundUp()
{
    RoundNumber( rtl_math_RoundingMode_Up );
}
 
void ScInterpreter::RoundSignificant( double fX, double fDigits, double &fRes )
{
    double fTemp = floor( log10( std::abs(fX) ) ) + 1.0 - fDigits;
    double fIn = fX;
    // Avoid inaccuracy of negative powers of 10.
    if (fTemp < 0.0)
        fIn *= pow(10.0, -fTemp);
    else
        fIn /= pow(10.0, fTemp);
    // For very large fX there might be an overflow in fIn resulting in
    // non-finite. rtl::math::round() handles that and it will be propagated as
    // usual.
    fRes = ::rtl::math::round(fIn);
    if (fTemp < 0.0)
        fRes /= pow(10.0, -fTemp);
    else
        fRes *= pow(10.0, fTemp);
}
 
// tdf#105931
void ScInterpreter::ScRoundSignificant()
{
    if ( !MustHaveParamCount( GetByte(), 2 ) )
        return;
 
    double fDigits = ::rtl::math::approxFloor( GetDouble() );
    double fX = GetDouble();
    if ( nGlobalError != FormulaError::NONE || fDigits < 1.0 )
    {
        PushIllegalArgument();
        return;
    }
 
    if ( fX == 0.0 )
        PushDouble( 0.0 );
    else
    {
        double fRes;
        RoundSignificant( fX, fDigits, fRes );
        PushDouble( fRes );
    }
}
 
/** tdf69552 ODFF1.2 function CEILING and Excel function CEILING.MATH
    In essence, the difference between the two is that ODFF-CEILING needs to
    have arguments value and significance of the same sign and with
    CEILING.MATH the sign of argument significance is irrevelevant.
    This is why ODFF-CEILING is exported to Excel as CEILING.MATH and
    CEILING.MATH is imported in Calc as CEILING.MATH
 */
void ScInterpreter::ScCeil( bool bODFF )
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 1, 3 ) )
        return;
 
    bool bAbs = nParamCount == 3 && GetBool();
    double fDec, fVal;
    if ( nParamCount == 1 )
    {
        fVal = GetDouble();
        fDec = ( fVal < 0 ? -1 : 1 );
    }
    else
    {
        bool bArgumentMissing = IsMissing();
        fDec = GetDouble();
        fVal = GetDouble();
        if ( bArgumentMissing )
            fDec = ( fVal < 0 ? -1 : 1 );
    }
    if ( fVal == 0 || fDec == 0.0 )
        PushInt( 0 );
    else
    {
        if ( bODFF && fVal * fDec < 0 )
            PushIllegalArgument();
        else
        {
            if ( fVal * fDec < 0.0 )
                fDec = -fDec;
 
            if ( !bAbs && fVal < 0.0 )
                PushDouble(::rtl::math::approxFloor( fVal / fDec ) * fDec );
            else
                PushDouble(::rtl::math::approxCeil( fVal / fDec ) * fDec );
        }
    }
}
 
void ScInterpreter::ScCeil_MS()
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 2 ) )
        return;
 
    double fDec = GetDouble();
    double fVal = GetDouble();
    if ( fVal == 0 || fDec == 0.0 )
        PushInt(0);
    else if ( fVal * fDec > 0 )
        PushDouble(::rtl::math::approxCeil( fVal / fDec ) * fDec );
    else if ( fVal < 0.0 )
        PushDouble(::rtl::math::approxFloor( fVal / -fDec ) * -fDec );
    else
        PushIllegalArgument();
}
 
void ScInterpreter::ScCeil_Precise()
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
        return;
 
    double fDec, fVal;
    if ( nParamCount == 1 )
    {
        fVal = GetDouble();
        fDec = 1.0;
    }
    else
    {
        fDec = std::abs( GetDoubleWithDefault( 1.0 ));
        fVal = GetDouble();
    }
    if ( fDec == 0.0 || fVal == 0.0 )
        PushInt( 0 );
    else
        PushDouble(::rtl::math::approxCeil( fVal / fDec ) * fDec );
}
 
/** tdf69552 ODFF1.2 function FLOOR and Excel function FLOOR.MATH
    In essence, the difference between the two is that ODFF-FLOOR needs to
    have arguments value and significance of the same sign and with
    FLOOR.MATH the sign of argument significance is irrevelevant.
    This is why ODFF-FLOOR is exported to Excel as FLOOR.MATH and
    FLOOR.MATH is imported in Calc as FLOOR.MATH
 */
void ScInterpreter::ScFloor( bool bODFF )
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 1, 3 ) )
        return;
 
    bool bAbs = ( nParamCount == 3 && GetBool() );
    double fDec, fVal;
    if ( nParamCount == 1 )
    {
        fVal = GetDouble();
        fDec = ( fVal < 0 ? -1 : 1 );
    }
    else
    {
        bool bArgumentMissing = IsMissing();
        fDec = GetDouble();
        fVal = GetDouble();
        if ( bArgumentMissing )
            fDec = ( fVal < 0 ? -1 : 1 );
    }
    if ( fDec == 0.0 || fVal == 0.0 )
        PushInt( 0 );
    else
    {
        if ( bODFF && ( fVal * fDec < 0.0 ) )
            PushIllegalArgument();
        else
        {
            if ( fVal * fDec < 0.0 )
                fDec = -fDec;
 
            if ( !bAbs && fVal < 0.0 )
                PushDouble(::rtl::math::approxCeil( fVal / fDec ) * fDec );
            else
                PushDouble(::rtl::math::approxFloor( fVal / fDec ) * fDec );
        }
    }
}
 
void ScInterpreter::ScFloor_MS()
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 2 ) )
        return;
 
    double fDec = GetDouble();
    double fVal = GetDouble();
 
    if ( fVal == 0 )
        PushInt( 0 );
    else if ( fVal * fDec > 0 )
        PushDouble(::rtl::math::approxFloor( fVal / fDec ) * fDec );
    else if ( fDec == 0 )
        PushIllegalArgument();
    else if ( fVal < 0.0 )
        PushDouble(::rtl::math::approxCeil( fVal / -fDec ) * -fDec );
    else
        PushIllegalArgument();
}
 
void ScInterpreter::ScFloor_Precise()
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
        return;
 
    double fDec = nParamCount == 1 ? 1.0 : std::abs( GetDoubleWithDefault( 1.0 ) );
    double fVal = GetDouble();
    if ( fDec == 0.0 || fVal == 0.0 )
        PushInt( 0 );
    else
        PushDouble(::rtl::math::approxFloor( fVal / fDec ) * fDec );
}
 
void ScInterpreter::ScEven()
{
    double fVal = GetDouble();
    if (fVal < 0.0)
        PushDouble(::rtl::math::approxFloor(fVal/2.0) * 2.0);
    else
        PushDouble(::rtl::math::approxCeil(fVal/2.0) * 2.0);
}
 
void ScInterpreter::ScOdd()
{
    double fVal = GetDouble();
    if (fVal >= 0.0)
    {
        fVal = ::rtl::math::approxCeil(fVal);
        if (fmod(fVal, 2.0) == 0.0)
            ++fVal;
    }
    else
    {
        fVal = ::rtl::math::approxFloor(fVal);
        if (fmod(fVal, 2.0) == 0.0)
            --fVal;
    }
    PushDouble(fVal);
}
 
void ScInterpreter::ScArcTan2()
{
    if ( MustHaveParamCount( GetByte(), 2 ) )
    {
        double fVal2 = GetDouble();
        double fVal1 = GetDouble();
        PushDouble(atan2(fVal2, fVal1));
    }
}
 
void ScInterpreter::ScLog()
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
        return;
 
    double fBase = nParamCount == 2 ? GetDouble() : 10.0;
    double fVal = GetDouble();
    if (fVal > 0.0 && fBase > 0.0 && fBase != 1.0)
        PushDouble(log(fVal) / log(fBase));
    else
        PushIllegalArgument();
}
 
void ScInterpreter::ScLn()
{
    double fVal = GetDouble();
    if (fVal > 0.0)
        PushDouble(log(fVal));
    else
        PushIllegalArgument();
}
 
void ScInterpreter::ScLog10()
{
    double fVal = GetDouble();
    if (fVal > 0.0)
        PushDouble(log10(fVal));
    else
        PushIllegalArgument();
}
 
void ScInterpreter::ScNPV()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    short nParamCount = GetByte();
    if ( !MustHaveParamCountMin( nParamCount, 2) )
        return;
 
    KahanSum fVal = 0.0;
    // We turn the stack upside down!
    ReverseStack( nParamCount);
    if (nGlobalError == FormulaError::NONE)
    {
        double  fCount = 1.0;
        double  fRate = GetDouble();
        --nParamCount;
        size_t nRefInList = 0;
        ScRange aRange;
        while (nParamCount-- > 0)
        {
            switch (GetStackType())
            {
                case svDouble :
                {
                    fVal += GetDouble() / pow(1.0 + fRate, fCount);
                    fCount++;
                }
                break;
                case svSingleRef :
                {
                    ScAddress aAdr;
                    PopSingleRef( aAdr );
                    ScRefCellValue aCell(mrDoc, aAdr);
                    if (!aCell.hasEmptyValue() && aCell.hasNumeric())
                    {
                        double fCellVal = GetCellValue(aAdr, aCell);
                        fVal += fCellVal / pow(1.0 + fRate, fCount);
                        fCount++;
                    }
                }
                break;
                case svDoubleRef :
                case svRefList :
                {
                    FormulaError nErr = FormulaError::NONE;
                    double fCellVal;
                    PopDoubleRef( aRange, nParamCount, nRefInList);
                    ScHorizontalValueIterator aValIter( mrDoc, aRange );
                    while ((nErr == FormulaError::NONE) && aValIter.GetNext(fCellVal, nErr))
                    {
                        fVal += fCellVal / pow(1.0 + fRate, fCount);
                        fCount++;
                    }
                    if ( nErr != FormulaError::NONE )
                        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)
                        {
                            PushIllegalArgument();
                            return;
                        }
                        else
                        {
                            double fx;
                            for ( SCSIZE j = 0; j < nC; j++ )
                            {
                                for (SCSIZE k = 0; k < nR; ++k)
                                {
                                    if (!pMat->IsValue(j,k))
                                    {
                                        PushIllegalArgument();
                                        return;
                                    }
                                    fx = pMat->GetDouble(j,k);
                                    fVal += fx / pow(1.0 + fRate, fCount);
                                    fCount++;
                                }
                            }
                        }
                    }
                }
                break;
                default : SetError(FormulaError::IllegalParameter); break;
            }
        }
    }
    PushDouble(fVal.get());
}
 
void ScInterpreter::ScIRR()
{
    nFuncFmtType = SvNumFormatType::PERCENT;
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
        return;
    double fEstimated = nParamCount == 2 ? GetDouble() : 0.1;
    double fEps = 1.0;
    // If it's -1 the default result for division by zero else startvalue
    double x = fEstimated == -1.0 ? 0.1 : fEstimated;
    double fValue;
 
    ScRange aRange;
    ScMatrixRef pMat;
    SCSIZE nC = 0;
    SCSIZE nR = 0;
    bool bIsMatrix = false;
    switch (GetStackType())
    {
        case svDoubleRef:
            PopDoubleRef(aRange);
        break;
        case svMatrix:
        case svExternalSingleRef:
        case svExternalDoubleRef:
            pMat = GetMatrix();
            if (pMat)
            {
                pMat->GetDimensions(nC, nR);
                if (nC == 0 || nR == 0)
                {
                    PushIllegalParameter();
                    return;
                }
                bIsMatrix = true;
            }
            else
            {
                PushIllegalParameter();
                return;
            }
            break;
        default:
        {
            PushIllegalParameter();
            return;
        }
    }
    const sal_uInt16 nIterationsMax = 20;
    sal_uInt16 nItCount = 0;
    FormulaError nIterError = FormulaError::NONE;
    while (fEps > SCdEpsilon && nItCount < nIterationsMax && nGlobalError == FormulaError::NONE)
    {                                       // Newtons method:
        KahanSum fNom = 0.0;
        KahanSum fDenom = 0.0;
        double fCount = 0.0;
        if (bIsMatrix)
        {
            for (SCSIZE j = 0; j < nC && nGlobalError == FormulaError::NONE; j++)
            {
                for (SCSIZE k = 0; k < nR; k++)
                {
                    if (!pMat->IsValue(j, k))
                        continue;
                    fValue = pMat->GetDouble(j, k);
                    if (nGlobalError != FormulaError::NONE)
                        break;
 
                    fNom   +=           fValue / pow(1.0+x,fCount);
                    fDenom += -fCount * fValue / pow(1.0+x,fCount+1.0);
                    fCount++;
                }
            }
        }
        else
        {
            ScValueIterator aValIter(mrContext, aRange, mnSubTotalFlags);
            bool bLoop = aValIter.GetFirst(fValue, nIterError);
            while (bLoop && nIterError == FormulaError::NONE)
            {
                fNom   +=           fValue / pow(1.0+x,fCount);
                fDenom += -fCount * fValue / pow(1.0+x,fCount+1.0);
                fCount++;
 
                bLoop = aValIter.GetNext(fValue, nIterError);
            }
            SetError(nIterError);
        }
        double xNew = x - o3tl::div_allow_zero(fNom.get(), fDenom.get());  // x(i+1) = x(i)-f(x(i))/f'(x(i))
        nItCount++;
        fEps = std::abs(xNew - x);
        x = xNew;
    }
    if (fEstimated == 0.0 && std::abs(x) < SCdEpsilon)
        x = 0.0;                        // adjust to zero
    if (fEps < SCdEpsilon)
        PushDouble(x);
    else
        PushError( FormulaError::NoConvergence);
}
 
void ScInterpreter::ScMIRR()
{   // range_of_values ; rate_invest ; rate_reinvest
    nFuncFmtType = SvNumFormatType::PERCENT;
    if ( !MustHaveParamCount( GetByte(), 3 ) )
        return;
 
    double fRate1_reinvest = GetDouble() + 1;
    double fRate1_invest = GetDouble() + 1;
 
    ScRange aRange;
    ScMatrixRef pMat;
    SCSIZE nC = 0;
    SCSIZE nR = 0;
    bool bIsMatrix = false;
    switch ( GetStackType() )
    {
        case svDoubleRef :
            PopDoubleRef( aRange );
            break;
        case svMatrix :
        case svExternalSingleRef:
        case svExternalDoubleRef:
            {
                pMat = GetMatrix();
                if ( pMat )
                {
                    pMat->GetDimensions( nC, nR );
                    if ( nC == 0 || nR == 0 )
                        SetError( FormulaError::IllegalArgument );
                    bIsMatrix = true;
                }
                else
                    SetError( FormulaError::IllegalArgument );
            }
            break;
        default :
            SetError( FormulaError::IllegalParameter );
            break;
    }
 
    if ( nGlobalError != FormulaError::NONE )
        PushError( nGlobalError );
    else
    {
        KahanSum fNPV_reinvest = 0.0;
        double fPow_reinvest = 1.0;
        KahanSum fNPV_invest = 0.0;
        double fPow_invest = 1.0;
        sal_uLong nCount = 0;
        bool bHasPosValue = false;
        bool bHasNegValue = false;
 
        if ( bIsMatrix )
        {
            double fX;
            for ( SCSIZE j = 0; j < nC; j++ )
            {
                for ( SCSIZE k = 0; k < nR; ++k )
                {
                    if ( !pMat->IsValue( j, k ) )
                        continue;
                    fX = pMat->GetDouble( j, k );
                    if ( nGlobalError != FormulaError::NONE )
                        break;
 
                    if ( fX > 0.0 )
                    {    // reinvestments
                        bHasPosValue = true;
                        fNPV_reinvest += fX * fPow_reinvest;
                    }
                    else if ( fX < 0.0 )
                    {   // investments
                        bHasNegValue = true;
                        fNPV_invest += fX * fPow_invest;
                    }
                    fPow_reinvest /= fRate1_reinvest;
                    fPow_invest /= fRate1_invest;
                    nCount++;
                }
            }
        }
        else
        {
            ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags );
            double fCellValue;
            FormulaError nIterError = FormulaError::NONE;
 
            bool bLoop = aValIter.GetFirst( fCellValue, nIterError );
            while( bLoop )
            {
                if( fCellValue > 0.0 )          // reinvestments
                {    // reinvestments
                    bHasPosValue = true;
                    fNPV_reinvest += fCellValue * fPow_reinvest;
                }
                else if( fCellValue < 0.0 )     // investments
                {   // investments
                    bHasNegValue = true;
                    fNPV_invest += fCellValue * fPow_invest;
                }
                fPow_reinvest /= fRate1_reinvest;
                fPow_invest /= fRate1_invest;
                nCount++;
 
                bLoop = aValIter.GetNext( fCellValue, nIterError );
            }
 
            if ( nIterError != FormulaError::NONE )
                SetError( nIterError );
        }
        if ( !( bHasPosValue && bHasNegValue ) )
            SetError( FormulaError::IllegalArgument );
 
        if ( nGlobalError != FormulaError::NONE )
            PushError( nGlobalError );
        else
        {
            double fResult = -o3tl::div_allow_zero(fNPV_reinvest.get(), fNPV_invest.get());
            fResult *= pow( fRate1_reinvest, static_cast<double>( nCount - 1 ) );
            fResult = pow( fResult, div( 1.0, (nCount - 1)) );
            PushDouble( fResult - 1.0 );
        }
    }
}
 
void ScInterpreter::ScISPMT()
{   // rate ; period ; total_periods ; invest
    if( MustHaveParamCount( GetByte(), 4 ) )
    {
        double fInvest = GetDouble();
        double fTotal = GetDouble();
        double fPeriod = GetDouble();
        double fRate = GetDouble();
 
        if( nGlobalError != FormulaError::NONE )
            PushError( nGlobalError);
        else
            PushDouble( fInvest * fRate * (o3tl::div_allow_zero(fPeriod, fTotal) - 1.0) );
    }
}
 
// financial functions
double ScInterpreter::ScGetPV(double fRate, double fNper, double fPmt,
                              double fFv, bool bPayInAdvance)
{
    double fPv;
    if (fRate == 0.0)
        fPv = fFv + fPmt * fNper;
    else
    {
        if (bPayInAdvance)
            fPv = (fFv * pow(1.0 + fRate, -fNper))
                    + (fPmt * (1.0 - pow(1.0 + fRate, -fNper + 1.0)) / fRate)
                    + fPmt;
        else
            fPv = (fFv * pow(1.0 + fRate, -fNper))
                    + (fPmt * (1.0 - pow(1.0 + fRate, -fNper)) / fRate);
    }
    return -fPv;
}
 
void ScInterpreter::ScPV()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 3, 5 ) )
        return;
 
    bool bPayInAdvance = nParamCount == 5 && GetBool();
    double fFv   = nParamCount >= 4 ? GetDouble() : 0;
    double fPmt  = GetDouble();
    double fNper = GetDouble();
    double fRate = GetDouble();
    PushDouble(ScGetPV(fRate, fNper, fPmt, fFv, bPayInAdvance));
}
 
void ScInterpreter::ScSYD()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    if ( MustHaveParamCount( GetByte(), 4 ) )
    {
        double fPer = GetDouble();
        double fLife = GetDouble();
        double fSalvage = GetDouble();
        double fCost = GetDouble();
        double fSyd = o3tl::div_allow_zero((fCost - fSalvage) * (fLife - fPer + 1.0),
                      (fLife * (fLife + 1.0)) / 2.0);
        PushDouble(fSyd);
    }
}
 
double ScInterpreter::ScGetDDB(double fCost, double fSalvage, double fLife,
                double fPeriod, double fFactor)
{
    double fDdb, fRate, fOldValue, fNewValue;
    fRate = o3tl::div_allow_zero(fFactor, fLife);
    if (fRate >= 1.0)
    {
        fRate = 1.0;
        fOldValue = fPeriod == 1.0 ? fCost : 0;
    }
    else
        fOldValue = fCost * pow(1.0 - fRate, fPeriod - 1.0);
    fNewValue = fCost * pow(1.0 - fRate, fPeriod);
 
    fDdb = fNewValue < fSalvage ? fOldValue - fSalvage : fOldValue - fNewValue;
    return fDdb < 0 ? 0 : fDdb;
}
 
void ScInterpreter::ScDDB()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 4, 5 ) )
        return;
 
    double fFactor = nParamCount == 5 ? GetDouble() : 2.0;
    double fPeriod = GetDouble();
    double fLife   = GetDouble();
    double fSalvage    = GetDouble();
    double fCost    = GetDouble();
    if (fCost < 0.0 || fSalvage < 0.0 || fFactor <= 0.0 || fSalvage > fCost
                    || fPeriod < 1.0 || fPeriod > fLife)
        PushIllegalArgument();
    else
        PushDouble(ScGetDDB(fCost, fSalvage, fLife, fPeriod, fFactor));
}
 
void ScInterpreter::ScDB()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 4, 5 ) )
        return ;
    double fMonths = nParamCount == 4 ? 12.0 : ::rtl::math::approxFloor(GetDouble());
    double fPeriod = GetDouble();
    double fLife = GetDouble();
    double fSalvage = GetDouble();
    double fCost = GetDouble();
    if (fMonths < 1.0 || fMonths > 12.0 || fLife > 1200.0 || fSalvage < 0.0 ||
        fPeriod > (fLife + 1.0) || fSalvage > fCost || fCost <= 0.0 ||
        fLife <= 0 || fPeriod <= 0 )
    {
        PushIllegalArgument();
        return;
    }
    double fOffRate = 1.0 - pow(fSalvage / fCost, 1.0 / fLife);
    fOffRate = ::rtl::math::approxFloor((fOffRate * 1000.0) + 0.5) / 1000.0;
    double fFirstOffRate = fCost * fOffRate * fMonths / 12.0;
    double fDb = 0.0;
    if (::rtl::math::approxFloor(fPeriod) == 1)
        fDb = fFirstOffRate;
    else
    {
        KahanSum fSumOffRate = fFirstOffRate;
        double fMin = fLife;
        if (fMin > fPeriod) fMin = fPeriod;
        sal_uInt16 iMax = static_cast<sal_uInt16>(::rtl::math::approxFloor(fMin));
        for (sal_uInt16 i = 2; i <= iMax; i++)
        {
            fDb = -(fSumOffRate - fCost).get() * fOffRate;
            fSumOffRate += fDb;
        }
        if (fPeriod > fLife)
            fDb = -(fSumOffRate - fCost).get() * fOffRate * (12.0 - fMonths) / 12.0;
    }
    PushDouble(fDb);
}
 
double ScInterpreter::ScInterVDB(double fCost, double fSalvage, double fLife,
                             double fLife1, double fPeriod, double fFactor)
{
    KahanSum fVdb = 0.0;
    double fIntEnd   = ::rtl::math::approxCeil(fPeriod);
    sal_uLong nLoopEnd   = static_cast<sal_uLong>(fIntEnd);
 
    double fTerm, fSln = 0; // SLN: Straight-Line Depreciation
    double fSalvageValue = fCost - fSalvage;
    bool bNowSln = false;
 
    double fDdb;
    sal_uLong i;
    for ( i = 1; i <= nLoopEnd; i++)
    {
        if(!bNowSln)
        {
            fDdb = ScGetDDB(fCost, fSalvage, fLife, static_cast<double>(i), fFactor);
            fSln = fSalvageValue/ (fLife1 - static_cast<double>(i-1));
 
            if (fSln > fDdb)
            {
                fTerm = fSln;
                bNowSln = true;
            }
            else
            {
                fTerm = fDdb;
                fSalvageValue -= fDdb;
            }
        }
        else
        {
            fTerm = fSln;
        }
 
        if ( i == nLoopEnd)
            fTerm *= ( fPeriod + 1.0 - fIntEnd );
 
        fVdb += fTerm;
    }
    return fVdb.get();
}
 
void ScInterpreter::ScVDB()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 5, 7 ) )
        return;
 
    KahanSum fVdb = 0.0;
    bool bNoSwitch = nParamCount == 7 && GetBool();
    double  fFactor = nParamCount >= 6 ? GetDouble() : 2.0;
    double fEnd   = GetDouble();
    double fStart = GetDouble();
    double fLife  = GetDouble();
    double fSalvage   = GetDouble();
    double fCost   = GetDouble();
    if (fStart < 0.0 || fEnd < fStart || fEnd > fLife || fCost < 0.0
                      || fSalvage > fCost || fFactor <= 0.0)
        PushIllegalArgument();
    else
    {
        double fIntStart = ::rtl::math::approxFloor(fStart);
        double fIntEnd   = ::rtl::math::approxCeil(fEnd);
        sal_uLong nLoopStart = static_cast<sal_uLong>(fIntStart);
        sal_uLong nLoopEnd   = static_cast<sal_uLong>(fIntEnd);
 
        if (bNoSwitch)
        {
            for (sal_uLong i = nLoopStart + 1; i <= nLoopEnd; i++)
            {
                double fTerm = ScGetDDB(fCost, fSalvage, fLife, static_cast<double>(i), fFactor);
 
                //respect partial period in the Beginning/ End:
                if ( i == nLoopStart+1 )
                    fTerm *= ( std::min( fEnd, fIntStart + 1.0 ) - fStart );
                else if ( i == nLoopEnd )
                    fTerm *= ( fEnd + 1.0 - fIntEnd );
 
                fVdb += fTerm;
            }
        }
        else
        {
            double fPart = 0.0;
            // respect partial period in the Beginning / End:
            if ( !::rtl::math::approxEqual( fStart, fIntStart ) ||
                 !::rtl::math::approxEqual( fEnd, fIntEnd ) )
            {
                if ( !::rtl::math::approxEqual( fStart, fIntStart ) )
                {
                    // part to be subtracted at the beginning
                    double fTempIntEnd = fIntStart + 1.0;
                    double fTempValue = fCost -
                        ScInterVDB( fCost, fSalvage, fLife, fLife, fIntStart, fFactor );
                    fPart += ( fStart - fIntStart ) *
                        ScInterVDB( fTempValue, fSalvage, fLife, fLife - fIntStart,
                        fTempIntEnd - fIntStart, fFactor);
                }
                if ( !::rtl::math::approxEqual( fEnd, fIntEnd ) )
                {
                    // part to be subtracted at the end
                    double fTempIntStart = fIntEnd - 1.0;
                    double fTempValue = fCost -
                        ScInterVDB( fCost, fSalvage, fLife, fLife, fTempIntStart, fFactor );
                    fPart += ( fIntEnd - fEnd ) *
                        ScInterVDB( fTempValue, fSalvage, fLife, fLife - fTempIntStart,
                        fIntEnd - fTempIntStart, fFactor);
                }
            }
            // calculate depreciation for whole periods
            fCost -= ScInterVDB( fCost, fSalvage, fLife, fLife, fIntStart, fFactor );
            fVdb = ScInterVDB( fCost, fSalvage, fLife, fLife - fIntStart,
                fIntEnd - fIntStart, fFactor);
            fVdb -= fPart;
        }
    }
    PushDouble(fVdb.get());
}
 
void ScInterpreter::ScPDuration()
{
    if ( MustHaveParamCount( GetByte(), 3 ) )
    {
        double fFuture = GetDouble();
        double fPresent = GetDouble();
        double fRate = GetDouble();
        if ( fFuture <= 0.0 || fPresent <= 0.0 || fRate <= 0.0 )
            PushIllegalArgument();
        else
            PushDouble( std::log( fFuture / fPresent ) / std::log1p( fRate ) );
    }
}
 
void ScInterpreter::ScSLN()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    if ( MustHaveParamCount( GetByte(), 3 ) )
    {
        double fLife = GetDouble();
        double fSalvage = GetDouble();
        double fCost = GetDouble();
        PushDouble( div( fCost - fSalvage, fLife ) );
    }
}
 
double ScInterpreter::ScGetPMT(double fRate, double fNper, double fPv,
                       double fFv, bool bPayInAdvance)
{
    double fPayment;
    if (fRate == 0.0)
        fPayment = o3tl::div_allow_zero(fPv + fFv, fNper);
    else
    {
        if (bPayInAdvance) // payment in advance
            fPayment = (fFv + fPv * exp( fNper * ::std::log1p(fRate) ) ) * fRate /
                (std::expm1( (fNper + 1) * ::std::log1p(fRate) ) - fRate);
        else  // payment in arrear
            fPayment = (fFv + fPv * exp(fNper * ::std::log1p(fRate) ) ) * fRate /
                std::expm1( fNper * ::std::log1p(fRate) );
    }
    return -fPayment;
}
 
void ScInterpreter::ScPMT()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 3, 5 ) )
        return;
    bool bPayInAdvance = nParamCount == 5 && GetBool();
    double fFv   = nParamCount >= 4 ? GetDouble() : 0;
    double fPv   = GetDouble();
    double fNper = GetDouble();
    double fRate = GetDouble();
    PushDouble(ScGetPMT(fRate, fNper, fPv, fFv, bPayInAdvance));
}
 
void ScInterpreter::ScRRI()
{
    nFuncFmtType = SvNumFormatType::PERCENT;
    if ( MustHaveParamCount( GetByte(), 3 ) )
    {
        double fFutureValue = GetDouble();
        double fPresentValue = GetDouble();
        double fNrOfPeriods = GetDouble();
        if ( fNrOfPeriods <= 0.0  || fPresentValue == 0.0 )
            PushIllegalArgument();
        else
            PushDouble(pow(fFutureValue / fPresentValue, 1.0 / fNrOfPeriods) - 1.0);
    }
}
 
double ScInterpreter::ScGetFV(double fRate, double fNper, double fPmt,
                              double fPv, bool bPayInAdvance)
{
    double fFv;
    if (fRate == 0.0)
        fFv = fPv + fPmt * fNper;
    else
    {
        double fTerm = pow(1.0 + fRate, fNper);
        if (bPayInAdvance)
            fFv = fPv * fTerm + fPmt*(1.0 + fRate)*(fTerm - 1.0)/fRate;
        else
            fFv = fPv * fTerm + fPmt*(fTerm - 1.0)/fRate;
    }
    return -fFv;
}
 
void ScInterpreter::ScFV()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 3, 5 ) )
        return;
    bool bPayInAdvance = nParamCount == 5 && GetBool();
    double fPv   = nParamCount >= 4 ? GetDouble() : 0;
    double fPmt  = GetDouble();
    double fNper = GetDouble();
    double fRate = GetDouble();
    PushDouble(ScGetFV(fRate, fNper, fPmt, fPv, bPayInAdvance));
}
 
void ScInterpreter::ScNper()
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 3, 5 ) )
        return;
    bool bPayInAdvance = nParamCount == 5 && GetBool();
    double fFV   = nParamCount >= 4 ? GetDouble() : 0;
    double fPV   = GetDouble();      // Present Value
    double fPmt  = GetDouble();      // Payment
    double fRate = GetDouble();
    // Note that due to the function specification in ODFF1.2 (and Excel) the
    // amount to be paid to get from fPV to fFV is fFV_+_fPV.
    if ( fPV + fFV == 0.0 )
        PushDouble( 0.0 );
    else if (fRate == 0.0)
        PushDouble(-o3tl::div_allow_zero(fPV + fFV, fPmt));
    else if (bPayInAdvance)
        PushDouble(log(-o3tl::div_allow_zero(fRate*fFV-fPmt*(1.0+fRate), (fRate*fPV+fPmt*(1.0+fRate))))
                  / std::log1p(fRate));
    else
        PushDouble(log(-(fRate*fFV-fPmt)/(fRate*fPV+fPmt)) / std::log1p(fRate));
}
 
bool ScInterpreter::RateIteration( double fNper, double fPayment, double fPv,
                                   double fFv, bool bPayType, double & fGuess )
{
    // See also #i15090#
    // Newton-Raphson method: x(i+1) = x(i) - f(x(i)) / f'(x(i))
    // This solution handles integer and non-integer values of Nper different.
    // If ODFF will constraint Nper to integer, the distinction of cases can be
    // removed; only the integer-part is needed then.
    bool bValid = true, bFound = false;
    double fX, fXnew, fTerm, fTermDerivation;
    double fGeoSeries, fGeoSeriesDerivation;
    const sal_uInt16 nIterationsMax = 150;
    sal_uInt16 nCount = 0;
    const double fEpsilonSmall = 1.0E-14;
    if ( bPayType )
    {
        // payment at beginning of each period
        fFv = fFv - fPayment;
        fPv = fPv + fPayment;
    }
    if (fNper == ::rtl::math::round( fNper ))
    { // Nper is an integer value
        fX = fGuess;
        while (!bFound && nCount < nIterationsMax)
        {
            double fPowN, fPowNminus1;  // for (1.0+fX)^Nper and (1.0+fX)^(Nper-1)
            fPowNminus1 = pow( 1.0+fX, fNper-1.0);
            fPowN = fPowNminus1 * (1.0+fX);
            if (fX == 0.0)
            {
                fGeoSeries = fNper;
                fGeoSeriesDerivation = fNper * (fNper-1.0)/2.0;
            }
            else
            {
                fGeoSeries = (fPowN-1.0)/fX;
                fGeoSeriesDerivation = fNper * fPowNminus1 / fX - fGeoSeries / fX;
            }
            fTerm = fFv + fPv *fPowN+ fPayment * fGeoSeries;
            fTermDerivation = fPv * fNper * fPowNminus1 + fPayment * fGeoSeriesDerivation;
            if (std::abs(fTerm) < fEpsilonSmall)
                bFound = true;  // will catch root which is at an extreme
            else
            {
                if (fTermDerivation == 0.0)
                    fXnew = fX + 1.1 * SCdEpsilon;  // move away from zero slope
                else
                    fXnew = fX - fTerm / fTermDerivation;
                nCount++;
                // more accuracy not possible in oscillating cases
                bFound = (std::abs(fXnew - fX) < SCdEpsilon);
                fX = fXnew;
            }
        }
        // Gnumeric returns roots < -1, Excel gives an error in that cases,
        // ODFF says nothing about it. Enable the statement, if you want Excel's
        // behavior.
        //bValid =(fX >=-1.0);
        // Update 2013-06-17: Gnumeric (v1.12.2) doesn't return roots <= -1
        // anymore.
        bValid = (fX > -1.0);
    }
    else
    { // Nper is not an integer value.
        fX = (fGuess < -1.0) ? -1.0 : fGuess;   // start with a valid fX
        while (bValid && !bFound && nCount < nIterationsMax)
        {
            if (fX == 0.0)
            {
                fGeoSeries = fNper;
                fGeoSeriesDerivation = fNper * (fNper-1.0)/2.0;
            }
            else
            {
                fGeoSeries = (pow( 1.0+fX, fNper) - 1.0) / fX;
                fGeoSeriesDerivation = fNper * pow( 1.0+fX, fNper-1.0) / fX - fGeoSeries / fX;
            }
            fTerm = fFv + fPv *pow(1.0 + fX,fNper)+ fPayment * fGeoSeries;
            fTermDerivation = fPv * fNper * pow( 1.0+fX, fNper-1.0) + fPayment * fGeoSeriesDerivation;
            if (std::abs(fTerm) < fEpsilonSmall)
                bFound = true;  // will catch root which is at an extreme
            else
            {
                if (fTermDerivation == 0.0)
                    fXnew = fX + 1.1 * SCdEpsilon;  // move away from zero slope
                else
                    fXnew = fX - fTerm / fTermDerivation;
                nCount++;
                // more accuracy not possible in oscillating cases
                bFound = (std::abs(fXnew - fX) < SCdEpsilon);
                fX = fXnew;
                bValid = (fX >= -1.0);  // otherwise pow(1.0+fX,fNper) will fail
            }
        }
    }
    fGuess = fX;    // return approximate root
    return bValid && bFound;
}
 
// In Calc UI it is the function RATE(Nper;Pmt;Pv;Fv;Type;Guess)
void ScInterpreter::ScRate()
{
    nFuncFmtType = SvNumFormatType::PERCENT;
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 3, 6 ) )
        return;
 
    // defaults for missing arguments, see ODFF spec
    double fGuess = nParamCount == 6 ? GetDouble() : 0.1;
    bool bDefaultGuess = nParamCount != 6;
    bool bPayType = nParamCount >= 5 && GetBool();
    double fFv = nParamCount >= 4 ? GetDouble() : 0;
    double fPv = GetDouble();
    double fPayment = GetDouble();
    double fNper = GetDouble();
    double fOrigGuess = fGuess;
 
    if (fNper <= 0.0) // constraint from ODFF spec
    {
        PushIllegalArgument();
        return;
    }
    bool bValid = RateIteration(fNper, fPayment, fPv, fFv, bPayType, fGuess);
 
    if (!bValid)
    {
        /* TODO: try also for specified guess values, not only default? As is,
         * a specified 0.1 guess may be error result but a default 0.1 guess
         * may succeed. On the other hand, using a different guess value than
         * the specified one may not be desired, even if that didn't match. */
        if (bDefaultGuess)
        {
            /* TODO: this is rather ugly, instead of looping over different
             * guess values and doing a Newton goal seek for each we could
             * first insert the values into the RATE equation to obtain a set
             * of y values and then do a bisecting goal seek, possibly using
             * different algorithms. */
            double fX = fOrigGuess;
            for (int nStep = 2; nStep <= 10 && !bValid; ++nStep)
            {
                fGuess = fX * nStep;
                bValid = RateIteration( fNper, fPayment, fPv, fFv, bPayType, fGuess);
                if (!bValid)
                {
                    fGuess = fX / nStep;
                    bValid = RateIteration( fNper, fPayment, fPv, fFv, bPayType, fGuess);
                }
            }
        }
        if (!bValid)
            SetError(FormulaError::NoConvergence);
    }
    PushDouble(fGuess);
}
 
double ScInterpreter::ScGetIpmt(double fRate, double fPer, double fNper, double fPv,
                                 double fFv, bool bPayInAdvance, double& fPmt)
{
    fPmt = ScGetPMT(fRate, fNper, fPv, fFv, bPayInAdvance);     // for PPMT also if fPer == 1
    double fIpmt;
    nFuncFmtType = SvNumFormatType::CURRENCY;
    if (fPer == 1.0)
        fIpmt = bPayInAdvance ? 0.0 : -fPv;
    else
    {
        if (bPayInAdvance)
            fIpmt = ScGetFV(fRate, fPer-2.0, fPmt, fPv, true) - fPmt;
        else
            fIpmt = ScGetFV(fRate, fPer-1.0, fPmt, fPv, false);
    }
    return fIpmt * fRate;
}
 
void ScInterpreter::ScIpmt()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 4, 6 ) )
        return;
    bool bPayInAdvance = nParamCount == 6 && GetBool();
    double fFv   = nParamCount >= 5 ? GetDouble() : 0;
    double fPv   = GetDouble();
    double fNper = GetDouble();
    double fPer  = GetDouble();
    double fRate = GetDouble();
    if (fPer < 1.0 || fPer > fNper)
        PushIllegalArgument();
    else
    {
        double fPmt;
        PushDouble(ScGetIpmt(fRate, fPer, fNper, fPv, fFv, bPayInAdvance, fPmt));
    }
}
 
void ScInterpreter::ScPpmt()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 4, 6 ) )
        return;
    bool bPayInAdvance = nParamCount == 6 && GetBool();
    double fFv   = nParamCount >= 5 ? GetDouble() : 0;
    double fPv   = GetDouble();
    double fNper = GetDouble();
    double fPer  = GetDouble();
    double fRate = GetDouble();
    if (fPer < 1.0 || fPer > fNper)
        PushIllegalArgument();
    else
    {
        double fPmt;
        double fInterestPer = ScGetIpmt(fRate, fPer, fNper, fPv, fFv, bPayInAdvance, fPmt);
        PushDouble(fPmt - fInterestPer);
    }
}
 
void ScInterpreter::ScCumIpmt()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    if ( !MustHaveParamCount( GetByte(), 6 ) )
        return;
 
    double fFlag  = GetDoubleWithDefault( -1.0 );
    double fEnd   = ::rtl::math::approxFloor(GetDouble());
    double fStart = ::rtl::math::approxFloor(GetDouble());
    double fPv    = GetDouble();
    double fNper  = GetDouble();
    double fRate  = GetDouble();
    if (fStart < 1.0 || fEnd < fStart || fRate <= 0.0 ||
        fEnd > fNper  || fNper <= 0.0 || fPv <= 0.0 ||
        ( fFlag != 0.0 && fFlag != 1.0 ))
        PushIllegalArgument();
    else
    {
        bool bPayInAdvance = static_cast<bool>(fFlag);
        sal_uLong nStart = static_cast<sal_uLong>(fStart);
        sal_uLong nEnd = static_cast<sal_uLong>(fEnd) ;
        double fPmt = ScGetPMT(fRate, fNper, fPv, 0.0, bPayInAdvance);
        KahanSum fIpmt = 0.0;
        if (nStart == 1)
        {
            if (!bPayInAdvance)
                fIpmt = -fPv;
            nStart++;
        }
        for (sal_uLong i = nStart; i <= nEnd; i++)
        {
            if (bPayInAdvance)
                fIpmt += ScGetFV(fRate, static_cast<double>(i-2), fPmt, fPv, true) - fPmt;
            else
                fIpmt += ScGetFV(fRate, static_cast<double>(i-1), fPmt, fPv, false);
        }
        fIpmt *= fRate;
        PushDouble(fIpmt.get());
    }
}
 
void ScInterpreter::ScCumPrinc()
{
    nFuncFmtType = SvNumFormatType::CURRENCY;
    if ( !MustHaveParamCount( GetByte(), 6 ) )
        return;
 
    double fFlag  = GetDoubleWithDefault( -1.0 );
    double fEnd   = ::rtl::math::approxFloor(GetDouble());
    double fStart = ::rtl::math::approxFloor(GetDouble());
    double fPv    = GetDouble();
    double fNper  = GetDouble();
    double fRate  = GetDouble();
    if (fStart < 1.0 || fEnd < fStart || fRate <= 0.0 ||
        fEnd > fNper  || fNper <= 0.0 || fPv <= 0.0 ||
        ( fFlag != 0.0 && fFlag != 1.0 ))
        PushIllegalArgument();
    else
    {
        bool bPayInAdvance = static_cast<bool>(fFlag);
        double fPmt = ScGetPMT(fRate, fNper, fPv, 0.0, bPayInAdvance);
        KahanSum fPpmt = 0.0;
        sal_uLong nStart = static_cast<sal_uLong>(fStart);
        sal_uLong nEnd = static_cast<sal_uLong>(fEnd);
        if (nStart == 1)
        {
            fPpmt = bPayInAdvance ? fPmt : fPmt + fPv * fRate;
            nStart++;
        }
        for (sal_uLong i = nStart; i <= nEnd; i++)
        {
            if (bPayInAdvance)
                fPpmt += fPmt - (ScGetFV(fRate, static_cast<double>(i-2), fPmt, fPv, true) - fPmt) * fRate;
            else
                fPpmt += fPmt - ScGetFV(fRate, static_cast<double>(i-1), fPmt, fPv, false) * fRate;
        }
        PushDouble(fPpmt.get());
    }
}
 
void ScInterpreter::ScEffect()
{
    nFuncFmtType = SvNumFormatType::PERCENT;
    if ( !MustHaveParamCount( GetByte(), 2 ) )
        return;
 
    double fPeriods = GetDouble();
    double fNominal = GetDouble();
    if (fPeriods < 1.0 || fNominal < 0.0)
        PushIllegalArgument();
    else if ( fNominal == 0.0 )
        PushDouble( 0.0 );
    else
    {
        fPeriods = ::rtl::math::approxFloor(fPeriods);
        PushDouble(pow(1.0 + fNominal/fPeriods, fPeriods) - 1.0);
    }
}
 
void ScInterpreter::ScNominal()
{
    nFuncFmtType = SvNumFormatType::PERCENT;
    if ( MustHaveParamCount( GetByte(), 2 ) )
    {
        double fPeriods = GetDouble();
        double fEffective = GetDouble();
        if (fPeriods < 1.0 || fEffective <= 0.0)
            PushIllegalArgument();
        else
        {
            fPeriods = ::rtl::math::approxFloor(fPeriods);
            PushDouble( (pow(fEffective + 1.0, 1.0 / fPeriods) - 1.0) * fPeriods );
        }
    }
}
 
void ScInterpreter::ScMod()
{
    if ( !MustHaveParamCount( GetByte(), 2 ) )
        return;
 
    double fDenom   = GetDouble();   // Denominator
    if ( fDenom == 0.0 )
    {
        PushError(FormulaError::DivisionByZero);
        return;
    }
    double fNum = GetDouble();   // Numerator
    double fRes = ::rtl::math::approxSub( fNum,
            ::rtl::math::approxFloor( fNum / fDenom ) * fDenom );
    if ( ( fDenom > 0 && fRes >= 0 && fRes < fDenom ) ||
         ( fDenom < 0 && fRes <= 0 && fRes > fDenom ) )
        PushDouble( fRes );
    else
        PushError( FormulaError::NoValue );
}
 
void ScInterpreter::ScIntersect()
{
    formula::FormulaConstTokenRef p2nd = PopToken();
    formula::FormulaConstTokenRef p1st = PopToken();
 
    if (nGlobalError != FormulaError::NONE || !p2nd || !p1st)
    {
        PushIllegalArgument();
        return;
    }
 
    StackVar sv1 = p1st->GetType();
    StackVar sv2 = p2nd->GetType();
    if ((sv1 != svSingleRef && sv1 != svDoubleRef && sv1 != svRefList) ||
        (sv2 != svSingleRef && sv2 != svDoubleRef && sv2 != svRefList))
    {
        PushIllegalArgument();
        return;
    }
 
    const formula::FormulaToken* x1 = p1st.get();
    const formula::FormulaToken* x2 = p2nd.get();
    if (sv1 == svRefList || sv2 == svRefList)
    {
        // Now this is a bit nasty but it simplifies things, and having
        // intersections with lists isn't too common, if at all...
        // Convert a reference to list.
        const formula::FormulaToken* xt[2] = { x1, x2 };
        StackVar sv[2] = { sv1, sv2 };
        // There may only be one reference; the other is necessarily a list
        // Ensure converted list proper destruction
        std::unique_ptr<formula::FormulaToken> p;
        for (size_t i=0; i<2; ++i)
        {
            if (sv[i] == svSingleRef)
            {
                ScComplexRefData aRef;
                aRef.Ref1 = aRef.Ref2 = *xt[i]->GetSingleRef();
                p.reset(new ScRefListToken);
                p->GetRefList()->push_back( aRef);
                xt[i] = p.get();
            }
            else if (sv[i] == svDoubleRef)
            {
                ScComplexRefData aRef = *xt[i]->GetDoubleRef();
                p.reset(new ScRefListToken);
                p->GetRefList()->push_back( aRef);
                xt[i] = p.get();
            }
        }
        x1 = xt[0];
        x2 = xt[1];
 
        ScTokenRef xRes = new ScRefListToken;
        ScRefList* pRefList = xRes->GetRefList();
        for (const auto& rRef1 : *x1->GetRefList())
        {
            const ScAddress r11 = rRef1.Ref1.toAbs(mrDoc, aPos);
            const ScAddress r12 = rRef1.Ref2.toAbs(mrDoc, aPos);
            for (const auto& rRef2 : *x2->GetRefList())
            {
                const ScAddress r21 = rRef2.Ref1.toAbs(mrDoc, aPos);
                const ScAddress r22 = rRef2.Ref2.toAbs(mrDoc, aPos);
                SCCOL nCol1 = ::std::max( r11.Col(), r21.Col());
                SCROW nRow1 = ::std::max( r11.Row(), r21.Row());
                SCTAB nTab1 = ::std::max( r11.Tab(), r21.Tab());
                SCCOL nCol2 = ::std::min( r12.Col(), r22.Col());
                SCROW nRow2 = ::std::min( r12.Row(), r22.Row());
                SCTAB nTab2 = ::std::min( r12.Tab(), r22.Tab());
                if (nCol2 < nCol1 || nRow2 < nRow1 || nTab2 < nTab1)
                    ;   // nothing
                else
                {
                    ScComplexRefData aRef;
                    aRef.InitRange( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
                    pRefList->push_back( aRef);
                }
            }
        }
        size_t n = pRefList->size();
        if (!n)
            PushError( FormulaError::NoCode);
        else if (n == 1)
        {
            const ScComplexRefData& rRef = (*pRefList)[0];
            if (rRef.Ref1 == rRef.Ref2)
                PushTempToken( new ScSingleRefToken(mrDoc.GetSheetLimits(), rRef.Ref1));
            else
                PushTempToken( new ScDoubleRefToken(mrDoc.GetSheetLimits(), rRef));
        }
        else
            PushTokenRef( xRes);
    }
    else
    {
        const formula::FormulaToken* pt[2] = { x1, x2 };
        StackVar sv[2] = { sv1, sv2 };
        SCCOL nC1[2], nC2[2];
        SCROW nR1[2], nR2[2];
        SCTAB nT1[2], nT2[2];
        for (size_t i=0; i<2; ++i)
        {
            switch (sv[i])
            {
                case svSingleRef:
                case svDoubleRef:
                {
                    {
                        const ScAddress r = pt[i]->GetSingleRef()->toAbs(mrDoc, aPos);
                        nC1[i] = r.Col();
                        nR1[i] = r.Row();
                        nT1[i] = r.Tab();
                    }
                    if (sv[i] == svDoubleRef)
                    {
                        const ScAddress r = pt[i]->GetSingleRef2()->toAbs(mrDoc, aPos);
                        nC2[i] = r.Col();
                        nR2[i] = r.Row();
                        nT2[i] = r.Tab();
                    }
                    else
                    {
                        nC2[i] = nC1[i];
                        nR2[i] = nR1[i];
                        nT2[i] = nT1[i];
                    }
                }
                break;
                default:
                    ;   // nothing, prevent compiler warning
            }
        }
        SCCOL nCol1 = ::std::max( nC1[0], nC1[1]);
        SCROW nRow1 = ::std::max( nR1[0], nR1[1]);
        SCTAB nTab1 = ::std::max( nT1[0], nT1[1]);
        SCCOL nCol2 = ::std::min( nC2[0], nC2[1]);
        SCROW nRow2 = ::std::min( nR2[0], nR2[1]);
        SCTAB nTab2 = ::std::min( nT2[0], nT2[1]);
        if (nCol2 < nCol1 || nRow2 < nRow1 || nTab2 < nTab1)
            PushError( FormulaError::NoCode);
        else if (nCol2 == nCol1 && nRow2 == nRow1 && nTab2 == nTab1)
            PushSingleRef( nCol1, nRow1, nTab1);
        else
            PushDoubleRef( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
    }
}
 
void ScInterpreter::ScRangeFunc()
{
    formula::FormulaConstTokenRef x2 = PopToken();
    formula::FormulaConstTokenRef x1 = PopToken();
 
    if (nGlobalError != FormulaError::NONE || !x2 || !x1)
    {
        PushIllegalArgument();
        return;
    }
    // We explicitly tell extendRangeReference() to not reuse the token,
    // casting const away spares two clones.
    FormulaTokenRef xRes = extendRangeReference(
            mrDoc.GetSheetLimits(), const_cast<FormulaToken&>(*x1), const_cast<FormulaToken&>(*x2), aPos, false);
    if (!xRes)
        PushIllegalArgument();
    else
        PushTokenRef( xRes);
}
 
void ScInterpreter::ScUnionFunc()
{
    formula::FormulaConstTokenRef p2nd = PopToken();
    formula::FormulaConstTokenRef p1st = PopToken();
 
    if (nGlobalError != FormulaError::NONE || !p2nd || !p1st)
    {
        PushIllegalArgument();
        return;
    }
 
    StackVar sv1 = p1st->GetType();
    StackVar sv2 = p2nd->GetType();
    if ((sv1 != svSingleRef && sv1 != svDoubleRef && sv1 != svRefList) ||
        (sv2 != svSingleRef && sv2 != svDoubleRef && sv2 != svRefList))
    {
        PushIllegalArgument();
        return;
    }
 
    const formula::FormulaToken* x1 = p1st.get();
    const formula::FormulaToken* x2 = p2nd.get();
 
    ScTokenRef xRes;
    // Append to an existing RefList if there is one.
    if (sv1 == svRefList)
    {
        xRes = x1->Clone();
        sv1 = svUnknown;    // mark as handled
    }
    else if (sv2 == svRefList)
    {
        xRes = x2->Clone();
        sv2 = svUnknown;    // mark as handled
    }
    else
        xRes = new ScRefListToken;
    ScRefList* pRes = xRes->GetRefList();
    const formula::FormulaToken* pt[2] = { x1, x2 };
    StackVar sv[2] = { sv1, sv2 };
    for (size_t i=0; i<2; ++i)
    {
        if (pt[i] == xRes)
            continue;
        switch (sv[i])
        {
            case svSingleRef:
                {
                    ScComplexRefData aRef;
                    aRef.Ref1 = aRef.Ref2 = *pt[i]->GetSingleRef();
                    pRes->push_back( aRef);
                }
                break;
            case svDoubleRef:
                pRes->push_back( *pt[i]->GetDoubleRef());
                break;
            case svRefList:
                {
                    const ScRefList* p = pt[i]->GetRefList();
                    for (const auto& rRef : *p)
                    {
                        pRes->push_back( rRef);
                    }
                }
                break;
            default:
                ;   // nothing, prevent compiler warning
        }
    }
    ValidateRef( *pRes);    // set #REF! if needed
    PushTokenRef( xRes);
}
 
void ScInterpreter::ScCurrent()
{
    FormulaConstTokenRef xTok( PopToken());
    if (xTok)
    {
        PushTokenRef( xTok);
        PushTokenRef( xTok);
    }
    else
        PushError( FormulaError::UnknownStackVariable);
}
 
void ScInterpreter::ScStyle()
{
    sal_uInt8 nParamCount = GetByte();
    if (!MustHaveParamCount(nParamCount, 1, 3))
        return;
 
    OUString aStyle2;                           // Style after timer
    if (nParamCount >= 3)
        aStyle2 = GetString().getString();
    tools::Long nTimeOut = 0;                          // timeout
    if (nParamCount >= 2)
        nTimeOut = static_cast<tools::Long>(GetDouble()*1000.0);
    OUString aStyle1 = GetString().getString(); // Style for immediate
 
    if (nTimeOut < 0)
        nTimeOut = 0;
 
    // Execute request to apply style
    if ( !mrDoc.IsClipOrUndo() )
    {
        ScDocShell* pShell = mrDoc.GetDocumentShell();
        if (pShell)
        {
            // Normalize style names right here, making sure that character case is correct,
            // and that we only apply anything when there's something to apply
            auto pPool = mrDoc.GetStyleSheetPool();
            if (!aStyle1.isEmpty())
            {
                if (auto pNewStyle = pPool->FindAutoStyle(aStyle1))
                    aStyle1 = pNewStyle->GetName();
                else
                    aStyle1.clear();
            }
            if (!aStyle2.isEmpty())
            {
                if (auto pNewStyle = pPool->FindAutoStyle(aStyle2))
                    aStyle2 = pNewStyle->GetName();
                else
                    aStyle2.clear();
            }
            // notify object shell directly!
            if (!aStyle1.isEmpty() || !aStyle2.isEmpty())
            {
                const ScStyleSheet* pStyle = mrDoc.GetStyle(aPos.Col(), aPos.Row(), aPos.Tab());
 
                const bool bNotify = !pStyle
                                     || (!aStyle1.isEmpty() && pStyle->GetName() != aStyle1)
                                     || (!aStyle2.isEmpty() && pStyle->GetName() != aStyle2);
                if (bNotify)
                {
                    ScRange aRange(aPos);
                    ScAutoStyleHint aHint(aRange, aStyle1, nTimeOut, aStyle2);
                    pShell->Broadcast(aHint);
                }
            }
        }
    }
 
    PushDouble(0.0);
}
 
static ScDdeLink* lcl_GetDdeLink( const sfx2::LinkManager* pLinkMgr,
                                std::u16string_view rA, std::u16string_view rT, std::u16string_view rI, sal_uInt8 nM )
{
    size_t nCount = pLinkMgr->GetLinks().size();
    for (size_t i=0; i<nCount; i++ )
    {
        ::sfx2::SvBaseLink* pBase = pLinkMgr->GetLinks()[i].get();
        if (ScDdeLink* pLink = dynamic_cast<ScDdeLink*>(pBase))
        {
            if ( pLink->GetAppl() == rA &&
                 pLink->GetTopic() == rT &&
                 pLink->GetItem() == rI &&
                 pLink->GetMode() == nM )
                return pLink;
        }
    }
 
    return nullptr;
}
 
void ScInterpreter::ScDde()
{
    //  application, file, scope
    //  application, Topic, Item
 
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 3, 4 ) )
        return;
 
    sal_uInt8 nMode = SC_DDE_DEFAULT;
    if (nParamCount == 4)
    {
        sal_Int32 nTmp = GetInt32();
        if (nGlobalError != FormulaError::NONE || nTmp < 0 || nTmp > SAL_MAX_UINT8)
        {
            PushIllegalArgument();
            return;
        }
        nMode = static_cast<sal_uInt8>(nTmp);
    }
    OUString aItem  = GetString().getString();
    OUString aTopic = GetString().getString();
    OUString aAppl  = GetString().getString();
 
    if (nMode > SC_DDE_TEXT)
        nMode = SC_DDE_DEFAULT;
 
    //  temporary documents (ScFunctionAccess) have no DocShell
    //  and no LinkManager -> abort
 
    //sfx2::LinkManager* pLinkMgr = mrDoc.GetLinkManager();
    if (!mpLinkManager)
    {
        PushNoValue();
        return;
    }
 
    // Need to reinterpret after loading (build links)
    pArr->AddRecalcMode( ScRecalcMode::ONLOAD_LENIENT );
 
        //  while the link is not evaluated, idle must be disabled (to avoid circular references)
 
    bool bOldEnabled = mrDoc.IsIdleEnabled();
    mrDoc.EnableIdle(false);
 
        // Get/ Create link object
 
    ScDdeLink* pLink = lcl_GetDdeLink( mpLinkManager, aAppl, aTopic, aItem, nMode );
 
    //TODO: Save Dde-links (in addition) more efficient at document !!!!!
    //      ScDdeLink* pLink = mrDoc.GetDdeLink( aAppl, aTopic, aItem );
 
    bool bWasError = ( pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE );
 
    if (!pLink)
    {
        pLink = new ScDdeLink( mrDoc, aAppl, aTopic, aItem, nMode );
        mpLinkManager->InsertDDELink( pLink, aAppl, aTopic, aItem );
        if ( mpLinkManager->GetLinks().size() == 1 )                    // the first one?
        {
            SfxBindings* pBindings = mrDoc.GetViewBindings();
            if (pBindings)
                pBindings->Invalidate( SID_LINKS );             // Link-Manager enabled
        }
 
        //if the document was just loaded, but the ScDdeLink entry was missing, then
        //don't update this link until the links are updated in response to the users
        //decision
        if (!mrDoc.HasLinkFormulaNeedingCheck())
        {
                                //TODO: evaluate asynchron ???
            pLink->TryUpdate(); //  TryUpdate doesn't call Update multiple times
        }
 
        if (pMyFormulaCell)
        {
            // StartListening after the Update to avoid circular references
            pMyFormulaCell->StartListening( *pLink );
        }
    }
    else
    {
        if (pMyFormulaCell)
            pMyFormulaCell->StartListening( *pLink );
    }
 
    //  If a new Error from Reschedule appears when the link is executed then reset the errorflag
 
 
    if ( pMyFormulaCell && pMyFormulaCell->GetRawError() != FormulaError::NONE && !bWasError )
        pMyFormulaCell->SetErrCode(FormulaError::NONE);
 
        //  check the value
 
    const ScMatrix* pLinkMat = pLink->GetResult();
    if (pLinkMat)
    {
        SCSIZE nC, nR;
        pLinkMat->GetDimensions(nC, nR);
        ScMatrixRef pNewMat = GetNewMat( nC, nR, /*bEmpty*/true);
        if (pNewMat)
        {
            pLinkMat->MatCopy(*pNewMat);        // copy
            PushMatrix( pNewMat );
        }
        else
            PushIllegalArgument();
    }
    else
        PushNA();
 
    mrDoc.EnableIdle(bOldEnabled);
    mpLinkManager->CloseCachedComps();
}
 
void ScInterpreter::ScBase()
{   // Value, Base [, MinLen]
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 2, 3 ) )
        return;
 
    static const sal_Unicode pDigits[] = {
        '0','1','2','3','4','5','6','7','8','9',
        'A','B','C','D','E','F','G','H','I','J','K','L','M',
        'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
        0
    };
    static const int nDigits = SAL_N_ELEMENTS(pDigits) - 1;
    sal_Int32 nMinLen;
    if ( nParamCount == 3 )
    {
        double fLen = ::rtl::math::approxFloor( GetDouble() );
        if ( 1.0 <= fLen && fLen < SAL_MAX_UINT16 )
            nMinLen = static_cast<sal_Int32>(fLen);
        else
            nMinLen = fLen == 0.0 ? 1 : 0; // 0 means error
    }
    else
        nMinLen = 1;
    double fBase = ::rtl::math::approxFloor( GetDouble() );
    double fVal = ::rtl::math::approxFloor( GetDouble() );
    double fChars = ((fVal > 0.0 && fBase > 0.0) ?
        (ceil( log( fVal ) / log( fBase ) ) + 2.0) :
        2.0);
    if ( fChars >= SAL_MAX_UINT16 )
        nMinLen = 0;    // Error
 
    if ( nGlobalError == FormulaError::NONE && nMinLen && 2 <= fBase && fBase <= nDigits && 0 <= fVal )
    {
        const sal_Int32 nConstBuf = 128;
        sal_Unicode aBuf[nConstBuf];
        sal_Int32 nBuf = std::max<sal_Int32>( fChars, nMinLen + 1 );
        sal_Unicode* pBuf = (nBuf <= nConstBuf ? aBuf : new sal_Unicode[nBuf]);
        for ( sal_Int32 j = 0; j < nBuf; ++j )
        {
            pBuf[j] = '0';
        }
        sal_Unicode* p = pBuf + nBuf - 1;
        *p = 0;
        if ( o3tl::convertsToAtMost(fVal, sal_uLong(~0)) )
        {
            sal_uLong nVal = static_cast<sal_uLong>(fVal);
            sal_uLong nBase = static_cast<sal_uLong>(fBase);
            while ( nVal && p > pBuf )
            {
                *--p = pDigits[ nVal % nBase ];
                nVal /= nBase;
            }
            fVal = static_cast<double>(nVal);
        }
        else
        {
            bool bDirt = false;
            while ( fVal && p > pBuf )
            {
//TODO: roundoff error starting with numbers greater than 2**48
//                  double fDig = ::rtl::math::approxFloor( fmod( fVal, fBase ) );
// a little bit better:
                double fInt = ::rtl::math::approxFloor( fVal / fBase );
                double fMult = fInt * fBase;
#if 0
                // =BASIS(1e308;36) => GPF with
                // nDig = (size_t) ::rtl::math::approxFloor( fVal - fMult );
                // in spite off previous test if fVal >= fMult
                double fDebug1 = fVal - fMult;
                // fVal    := 7,5975311883090e+290
                // fMult   := 7,5975311883090e+290
                // fDebug1 := 1,3848924157003e+275  <- RoundOff-Error
                // fVal != fMult, aber: ::rtl::math::approxEqual( fVal, fMult ) == TRUE
                double fDebug2 = ::rtl::math::approxSub( fVal, fMult );
                // and ::rtl::math::approxSub( fVal, fMult ) == 0
                double fDebug3 = ( fInt ? fVal / fInt : 0.0 );
 
                // Actual after strange fDebug1 and fVal < fMult is fDebug2 == fBase, but
                // anyway it can't be compared, then bDirt is executed an everything is good...
 
                // prevent compiler warnings
                (void)fDebug1; (void)fDebug2; (void)fDebug3;
#endif
                size_t nDig;
                if ( fVal < fMult )
                {   // something is wrong there
                    bDirt = true;
                    nDig = 0;
                }
                else
                {
                    double fDig = ::rtl::math::approxFloor( ::rtl::math::approxSub( fVal, fMult ) );
                    if ( bDirt )
                    {
                        bDirt = false;
                        --fDig;
                    }
                    if ( fDig <= 0.0 )
                        nDig = 0;
                    else if ( fDig >= fBase )
                        nDig = static_cast<size_t>(fBase) - 1;
                    else
                        nDig = static_cast<size_t>(fDig);
                }
                *--p = pDigits[ nDig ];
                fVal = fInt;
            }
        }
        if ( fVal )
            PushError( FormulaError::StringOverflow );
        else
        {
            if ( nBuf - (p - pBuf) <= nMinLen )
                p = pBuf + nBuf - 1 - nMinLen;
            PushStringBuffer( p );
        }
        if ( pBuf != aBuf )
            delete [] pBuf;
    }
    else
        PushIllegalArgument();
}
 
void ScInterpreter::ScDecimal()
{   // Text, Base
    if ( !MustHaveParamCount( GetByte(), 2 ) )
        return;
 
    double fBase = ::rtl::math::approxFloor( GetDouble() );
    OUString aStr = GetString().getString();
    if ( nGlobalError == FormulaError::NONE && 2 <= fBase && fBase <= 36 )
    {
        double fVal = 0.0;
        int nBase = static_cast<int>(fBase);
        const sal_Unicode* p = aStr.getStr();
        while ( *p == ' ' || *p == '\t' )
            p++;        // strip leading white space
        if ( nBase == 16 )
        {   // evtl. hex-prefix stripped
            if ( *p == 'x' || *p == 'X' )
                p++;
            else if ( *p == '0' && (*(p+1) == 'x' || *(p+1) == 'X') )
                p += 2;
        }
        while ( *p )
        {
            int n;
            if ( '0' <= *p && *p <= '9' )
                n = *p - '0';
            else if ( 'A' <= *p && *p <= 'Z' )
                n = 10 + (*p - 'A');
            else if ( 'a' <= *p && *p <= 'z' )
                n = 10 + (*p - 'a');
            else
                n = nBase;
            if ( nBase <= n )
            {
                if ( *(p+1) == 0 &&
                        ( (nBase ==  2 && (*p == 'b' || *p == 'B'))
                        ||(nBase == 16 && (*p == 'h' || *p == 'H')) )
                    )
                    ;       // 101b and F00Dh are ok
                else
                {
                    PushIllegalArgument();
                    return ;
                }
            }
            else
                fVal = fVal * fBase + n;
            p++;
 
        }
        PushDouble( fVal );
    }
    else
        PushIllegalArgument();
}
 
void ScInterpreter::ScConvertOOo()
{   // Value, FromUnit, ToUnit
    if ( !MustHaveParamCount( GetByte(), 3 ) )
        return;
 
    OUString aToUnit = GetString().getString();
    OUString aFromUnit = GetString().getString();
    double fVal = GetDouble();
    if ( nGlobalError != FormulaError::NONE )
        PushError( nGlobalError);
    else
    {
        // first of all search for the given order; if it can't be found then search for the inverse
        double fConv;
        if ( ScGlobal::GetUnitConverter()->GetValue( fConv, aFromUnit, aToUnit ) )
            PushDouble( fVal * fConv );
        else if ( ScGlobal::GetUnitConverter()->GetValue( fConv, aToUnit, aFromUnit ) )
            PushDouble( fVal / fConv );
        else
            PushNA();
    }
}
 
void ScInterpreter::ScRoman()
{   // Value [Mode]
    sal_uInt8 nParamCount = GetByte();
    if( !MustHaveParamCount( nParamCount, 1, 2 ) )
        return;
 
    double fMode = (nParamCount == 2) ? ::rtl::math::approxFloor( GetDouble() ) : 0.0;
    double fVal = ::rtl::math::approxFloor( GetDouble() );
    if( nGlobalError != FormulaError::NONE )
        PushError( nGlobalError);
    else if( (fMode >= 0.0) && (fMode < 5.0) && (fVal >= 0.0) && (fVal < 4000.0) )
    {
        static const sal_Unicode pChars[] = { 'M', 'D', 'C', 'L', 'X', 'V', 'I' };
        static const sal_uInt16 pValues[] = { 1000, 500, 100, 50, 10, 5, 1 };
        static const sal_uInt16 nMaxIndex = sal_uInt16(SAL_N_ELEMENTS(pValues) - 1);
 
        OUStringBuffer aRoman;
        sal_uInt16 nVal = static_cast<sal_uInt16>(fVal);
        sal_uInt16 nMode = static_cast<sal_uInt16>(fMode);
 
        for( sal_uInt16 i = 0; i <= nMaxIndex / 2; i++ )
        {
            sal_uInt16 nIndex = 2 * i;
            sal_uInt16 nDigit = nVal / pValues[ nIndex ];
 
            if( (nDigit % 5) == 4 )
            {
                // assert can't happen with nVal<4000 precondition
                assert( ((nDigit == 4) ? (nIndex >= 1) : (nIndex >= 2)));
 
                sal_uInt16 nIndex2 = (nDigit == 4) ? nIndex - 1 : nIndex - 2;
                sal_uInt16 nSteps = 0;
                while( (nSteps < nMode) && (nIndex < nMaxIndex) )
                {
                    nSteps++;
                    if( pValues[ nIndex2 ] - pValues[ nIndex + 1 ] <= nVal )
                        nIndex++;
                    else
                        nSteps = nMode;
                }
                aRoman.append( OUStringChar(pChars[ nIndex ]) + OUStringChar(pChars[ nIndex2 ]) );
                nVal = sal::static_int_cast<sal_uInt16>( nVal + pValues[ nIndex ] );
                nVal = sal::static_int_cast<sal_uInt16>( nVal - pValues[ nIndex2 ] );
            }
            else
            {
                if( nDigit > 4 )
                {
                    // assert can't happen with nVal<4000 precondition
                    assert( nIndex >= 1 );
                    aRoman.append( pChars[ nIndex - 1 ] );
                }
                sal_Int32 nPad = nDigit % 5;
                if (nPad)
                {
                    comphelper::string::padToLength(aRoman, aRoman.getLength() + nPad,
                        pChars[nIndex]);
                }
                nVal %= pValues[ nIndex ];
            }
        }
 
        PushString( aRoman.makeStringAndClear() );
    }
    else
        PushIllegalArgument();
}
 
static bool lcl_GetArabicValue( sal_Unicode cChar, sal_uInt16& rnValue, bool& rbIsDec )
{
    switch( cChar )
    {
        case 'M':   rnValue = 1000; rbIsDec = true;     break;
        case 'D':   rnValue = 500;  rbIsDec = false;    break;
        case 'C':   rnValue = 100;  rbIsDec = true;     break;
        case 'L':   rnValue = 50;   rbIsDec = false;    break;
        case 'X':   rnValue = 10;   rbIsDec = true;     break;
        case 'V':   rnValue = 5;    rbIsDec = false;    break;
        case 'I':   rnValue = 1;    rbIsDec = true;     break;
        default:    return false;
    }
    return true;
}
 
void ScInterpreter::ScArabic()
{
    OUString aRoman = GetString().getString();
    if( nGlobalError != FormulaError::NONE )
        PushError( nGlobalError);
    else
    {
        aRoman = aRoman.toAsciiUpperCase();
 
        sal_uInt16 nValue = 0;
        sal_uInt16 nValidRest = 3999;
        sal_Int32 nCharIndex = 0;
        sal_Int32 nCharCount = aRoman.getLength();
        bool bValid = true;
 
        while( bValid && (nCharIndex < nCharCount) )
        {
            sal_uInt16 nDigit1 = 0;
            sal_uInt16 nDigit2 = 0;
            bool bIsDec1 = false;
            bValid = lcl_GetArabicValue( aRoman[nCharIndex], nDigit1, bIsDec1 );
            if( bValid && (nCharIndex + 1 < nCharCount) )
            {
                bool bIsDec2 = false;
                bValid = lcl_GetArabicValue( aRoman[nCharIndex + 1], nDigit2, bIsDec2 );
            }
            if( bValid )
            {
                if( nDigit1 >= nDigit2 )
                {
                    nValue = sal::static_int_cast<sal_uInt16>( nValue + nDigit1 );
                    nValidRest %= (nDigit1 * (bIsDec1 ? 5 : 2));
                    bValid = (nValidRest >= nDigit1);
                    if( bValid )
                        nValidRest = sal::static_int_cast<sal_uInt16>( nValidRest - nDigit1 );
                    nCharIndex++;
                }
                else if( nDigit1 * 2 != nDigit2 )
                {
                    sal_uInt16 nDiff = nDigit2 - nDigit1;
                    nValue = sal::static_int_cast<sal_uInt16>( nValue + nDiff );
                    bValid = (nValidRest >= nDiff);
                    if( bValid )
                        nValidRest = nDigit1 - 1;
                    nCharIndex += 2;
                }
                else
                    bValid = false;
            }
        }
        if( bValid )
            PushInt( nValue );
        else
            PushIllegalArgument();
    }
}
 
void ScInterpreter::ScHyperLink()
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 1, 2 ) )
        return;
 
    double fVal = 0.0;
    svl::SharedString aStr;
    ScMatValType nResultType = ScMatValType::String;
 
    if ( nParamCount == 2 )
    {
        switch ( GetStackType() )
        {
            case svDouble:
                fVal = GetDouble();
                nResultType = ScMatValType::Value;
            break;
            case svString:
                aStr = GetString();
            break;
            case svSingleRef:
            case svDoubleRef:
            {
                ScAddress aAdr;
                if ( !PopDoubleRefOrSingleRef( aAdr ) )
                    break;
 
                ScRefCellValue aCell(mrDoc, aAdr);
                if (aCell.hasEmptyValue())
                    nResultType = ScMatValType::Empty;
                else
                {
                    FormulaError nErr = GetCellErrCode(aCell);
                    if (nErr != FormulaError::NONE)
                        SetError( nErr);
                    else if (aCell.hasNumeric())
                    {
                        fVal = GetCellValue(aAdr, aCell);
                        nResultType = ScMatValType::Value;
                    }
                    else
                        GetCellString(aStr, aCell);
                }
            }
            break;
            case svMatrix:
                nResultType = GetDoubleOrStringFromMatrix( fVal, aStr);
            break;
            case svMissing:
            case svEmptyCell:
                Pop();
                // mimic xcl
                fVal = 0.0;
                nResultType = ScMatValType::Value;
            break;
            default:
                PopError();
                SetError( FormulaError::IllegalArgument);
        }
    }
    svl::SharedString aUrl = GetString();
    ScMatrixRef pResMat = GetNewMat( 1, 2);
    if (nGlobalError != FormulaError::NONE)
    {
        fVal = CreateDoubleError( nGlobalError);
        nResultType = ScMatValType::Value;
    }
    if (nParamCount == 2 || nGlobalError != FormulaError::NONE)
    {
        if (ScMatrix::IsValueType( nResultType))
            pResMat->PutDouble( fVal, 0);
        else if (ScMatrix::IsRealStringType( nResultType))
            pResMat->PutString(aStr, 0);
        else    // EmptyType, EmptyPathType, mimic xcl
            pResMat->PutDouble( 0.0, 0 );
    }
    else
        pResMat->PutString(aUrl, 0);
    pResMat->PutString(aUrl, 1);
    bMatrixFormula = true;
    PushMatrix(pResMat);
}
 
/** Resources at the website of the European Commission:
    http://ec.europa.eu/economy_finance/euro/adoption/conversion/
    http://ec.europa.eu/economy_finance/euro/countries/
 */
static bool lclConvertMoney( std::u16string_view aSearchUnit, double& rfRate, int& rnDec )
{
    struct ConvertInfo
    {
        const char* pCurrText;
        double      fRate;
        int         nDec;
    };
    static const ConvertInfo aConvertTable[] = {
        { "EUR", 1.0,      2 },
        { "ATS", 13.7603,  2 },
        { "BEF", 40.3399,  0 },
        { "DEM", 1.95583,  2 },
        { "ESP", 166.386,  0 },
        { "FIM", 5.94573,  2 },
        { "FRF", 6.55957,  2 },
        { "IEP", 0.787564, 2 },
        { "ITL", 1936.27,  0 },
        { "LUF", 40.3399,  0 },
        { "NLG", 2.20371,  2 },
        { "PTE", 200.482,  2 },
        { "GRD", 340.750,  2 },
        { "SIT", 239.640,  2 },
        { "MTL", 0.429300, 2 },
        { "CYP", 0.585274, 2 },
        { "SKK", 30.1260,  2 },
        { "EEK", 15.6466,  2 },
        { "LVL", 0.702804, 2 },
        { "LTL", 3.45280,  2 },
        { "HRK", 7.53450,  2 },
        { "BGN", 1.95583,  2 }
    };
 
    for (const auto & i : aConvertTable)
        if ( o3tl::equalsIgnoreAsciiCase( aSearchUnit, i.pCurrText ) )
        {
            rfRate = i.fRate;
            rnDec  = i.nDec;
            return true;
        }
    return false;
}
 
void ScInterpreter::ScEuroConvert()
{   //Value, FromUnit, ToUnit[, FullPrecision, [TriangulationPrecision]]
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 3, 5 ) )
        return;
 
    double fPrecision = 0.0;
    if ( nParamCount == 5 )
    {
        fPrecision = ::rtl::math::approxFloor(GetDouble());
        if ( fPrecision < 3 )
        {
            PushIllegalArgument();
            return;
        }
    }
 
    bool bFullPrecision = nParamCount >= 4 && GetBool();
    OUString aToUnit = GetString().getString();
    OUString aFromUnit = GetString().getString();
    double fVal = GetDouble();
    if ( nGlobalError != FormulaError::NONE )
        PushError( nGlobalError);
    else
    {
        double fFromRate;
        double fToRate;
        int    nFromDec;
        int    nToDec;
        if ( lclConvertMoney( aFromUnit, fFromRate, nFromDec )
            && lclConvertMoney( aToUnit, fToRate, nToDec ) )
        {
            double fRes;
            if ( aFromUnit.equalsIgnoreAsciiCase( aToUnit ) )
                fRes = fVal;
            else
            {
                if ( aFromUnit.equalsIgnoreAsciiCase( "EUR" ) )
                   fRes = fVal * fToRate;
                else
                {
                    double fIntermediate = fVal / fFromRate;
                    if ( fPrecision )
                        fIntermediate = ::rtl::math::round( fIntermediate,
                                                        static_cast<int>(fPrecision) );
                    fRes = fIntermediate * fToRate;
                }
                if ( !bFullPrecision )
                    fRes = ::rtl::math::round( fRes, nToDec );
            }
            PushDouble( fRes );
        }
        else
            PushIllegalArgument();
    }
}
 
// BAHTTEXT
// local functions
namespace {
 
constexpr std::u16string_view TH_0      = u"ศูนย์";
constexpr std::u16string_view TH_1      = u"หนึ่ง";
constexpr std::u16string_view TH_2      = u"สอง";
constexpr std::u16string_view TH_3      = u"สาม";
constexpr std::u16string_view TH_4      = u"สี่";
constexpr std::u16string_view TH_5      = u"ห้า";
constexpr std::u16string_view TH_6      = u"หก";
constexpr std::u16string_view TH_7      = u"เจ็ด";
constexpr std::u16string_view TH_8      = u"แปด";
constexpr std::u16string_view TH_9      = u"เก้า";
constexpr std::u16string_view TH_10     = u"สิบ";
constexpr std::u16string_view TH_11     = u"เอ็ด";
constexpr std::u16string_view TH_20     = u"ยี่";
constexpr std::u16string_view TH_1E2    = u"ร้อย";
constexpr std::u16string_view TH_1E3    = u"พัน";
constexpr std::u16string_view TH_1E4    = u"หมื่น";
constexpr std::u16string_view TH_1E5    = u"แสน";
constexpr std::u16string_view TH_1E6    = u"ล้าน";
constexpr std::u16string_view TH_DOT0   = u"ถ้วน";
constexpr std::u16string_view TH_BAHT   = u"บาท";
constexpr std::u16string_view TH_SATANG = u"สตางค์";
constexpr std::u16string_view TH_MINUS  = u"ลบ";
 
/** Appends a digit (1 to 9) to the passed string. */
void lclAppendDigit( OUStringBuffer& rText, char nDigit )
{
    assert(nDigit >= '1' && nDigit <= '9');
    switch( nDigit )
    {
        case '1': rText.append( TH_1 ); break;
        case '2': rText.append( TH_2 ); break;
        case '3': rText.append( TH_3 ); break;
        case '4': rText.append( TH_4 ); break;
        case '5': rText.append( TH_5 ); break;
        case '6': rText.append( TH_6 ); break;
        case '7': rText.append( TH_7 ); break;
        case '8': rText.append( TH_8 ); break;
        case '9': rText.append( TH_9 ); break;
    }
}
 
/** Appends a value raised to a power of 10: nDigit*10^nPow10.
    @param nDigit  A digit in the range from 1 to 9.
    @param nPow10  A value in the range from 2 to 5.
 */
void lclAppendPow10( OUStringBuffer& rText, char nDigit, sal_Int32 nPow10 )
{
    assert(nPow10 >= 2 && nPow10 <= 5);
    lclAppendDigit( rText, nDigit );
    switch( nPow10 )
    {
        case 2: rText.append( TH_1E2 );   break;
        case 3: rText.append( TH_1E3 );   break;
        case 4: rText.append( TH_1E4 );   break;
        case 5: rText.append( TH_1E5 );   break;
    }
}
 
/** Appends a block of 6 digits (value from 1 to 999,999) to the passed string. */
void lclAppendBlock( OUStringBuffer& rText, std::string_view block )
{
    assert(block.size() > 0 && block.size() <= 6);
    auto it = block.begin();
    for (size_t pow = block.size() - 1; pow >= 2; --pow)
        if (char ch = *it++; ch != '0')
            lclAppendPow10(rText, ch, pow);
 
    // Two last characters
    char ten = block.size() > 1 ? *it++ : '0';
    char one = *it;
    if (ten >= '1')
    {
        if (ten >= '3')
            lclAppendDigit(rText, ten);
        else if (ten == '2')
            rText.append( TH_20 );
        rText.append( TH_10 );
    }
    if ((ten > '0') && (one == '1'))
        rText.append( TH_11 );
    else if( one > '0' )
        lclAppendDigit(rText, one);
}
 
} // namespace
 
void ScInterpreter::ScBahtText()
{
    sal_uInt8 nParamCount = GetByte();
    if ( !MustHaveParamCount( nParamCount, 1 ) )
        return;
 
    double fValue = GetDouble();
    if( nGlobalError != FormulaError::NONE )
    {
        PushError( nGlobalError);
        return;
    }
 
    OString number = rtl::math::doubleToString(std::abs(fValue), rtl_math_StringFormat_F, 2, '.');
    assert(number.getLength() > 3); // At least "0.00"
    const sal_Int32 dotPos = number.getLength() - 3;
    assert(number[dotPos] == '.');
    // split Baht and Satang
    std::string_view sBaht(number.subView(0, dotPos)), sSatang(number.subView(dotPos + 1));
    bool noBaht = sBaht == "0", noSatang = sSatang == "00";
    if (noBaht && noSatang)
    {
        return PushString(OUString::Concat(TH_0) + TH_BAHT + TH_DOT0);
    }
 
    OUStringBuffer aText;
    // sign
    if (fValue < 0.0)
        aText.append(TH_MINUS);
 
    // generate text for Baht value
    if (!noBaht)
    {
        size_t blocksize = sBaht.size() % 6;
        if (blocksize == 0)
            blocksize = 6;
        while (!sBaht.empty())
        {
            assert(blocksize <= sBaht.size());
            lclAppendBlock(aText, sBaht.substr(0, blocksize));
            sBaht.remove_prefix(blocksize);
            blocksize = 6;
            // add "million", if there will come more blocks
            if (!sBaht.empty())
                aText.append(TH_1E6);
        }
        aText.append(TH_BAHT);
    }
 
    // generate text for Satang value
    if (noSatang)
    {
        aText.append( TH_DOT0 );
    }
    else
    {
        lclAppendBlock(aText, sSatang);
        aText.append( TH_SATANG );
    }
 
    PushString(aText.makeStringAndClear());
}
 
void ScInterpreter::ScGetPivotData()
{
    sal_uInt8 nParamCount = GetByte();
 
    if (!MustHaveParamCountMin(nParamCount, 2) || (nParamCount % 2) == 1)
    {
        PushError(FormulaError::NoRef);
        return;
    }
 
    bool bOldSyntax = false;
    if (nParamCount == 2)
    {
        // if the first parameter is a ref, assume old syntax
        StackVar eFirstType = GetStackType(2);
        if (eFirstType == svSingleRef || eFirstType == svDoubleRef)
            bOldSyntax = true;
    }
 
    std::vector<sheet::DataPilotFieldFilter> aFilters;
    OUString aDataFieldName;
    ScRange aBlock;
 
    if (bOldSyntax)
    {
        aDataFieldName = GetString().getString();
 
        switch (GetStackType())
        {
            case svDoubleRef :
                PopDoubleRef(aBlock);
            break;
            case svSingleRef :
            {
                ScAddress aAddr;
                PopSingleRef(aAddr);
                aBlock = aAddr;
            }
            break;
            default:
                PushError(FormulaError::NoRef);
                return;
        }
    }
    else
    {
        // Standard syntax: separate name/value pairs
 
        sal_uInt16 nFilterCount = nParamCount / 2 - 1;
        aFilters.resize(nFilterCount);
 
        sal_uInt16 i = nFilterCount;
        while (i > 0)
        {
            --i;
            /* TODO: also, in case of numeric the entire filter match should
             * not be on a (even if locale independent) formatted string down
             * below in pDPObj->GetPivotData(). */
 
            bool bEvaluateFormatIndex;
            switch (GetRawStackType())
            {
                case svSingleRef:
                case svDoubleRef:
                    bEvaluateFormatIndex = true;
                break;
                default:
                    bEvaluateFormatIndex = false;
            }
 
            double fDouble;
            svl::SharedString aSharedString;
            bool bDouble = GetDoubleOrString( fDouble, aSharedString);
            if (nGlobalError != FormulaError::NONE)
            {
                PushError( nGlobalError);
                return;
            }
 
            if (bDouble)
            {
                sal_uInt32 nNumFormat;
                if (bEvaluateFormatIndex && nCurFmtIndex)
                    nNumFormat = nCurFmtIndex;
                else
                {
                    if (nCurFmtType == SvNumFormatType::UNDEFINED)
                        nNumFormat = 0;
                    else
                        nNumFormat = mrContext.NFGetStandardFormat( nCurFmtType, ScGlobal::eLnge);
                }
                const Color* pColor;
                mrContext.NFGetOutputString( fDouble, nNumFormat, aFilters[i].MatchValueName, &pColor);
                aFilters[i].MatchValue = ScDPCache::GetLocaleIndependentFormattedString(
                        fDouble, mrContext, nNumFormat);
            }
            else
            {
                aFilters[i].MatchValueName = aSharedString.getString();
 
                // Parse possible number from MatchValueName and format
                // locale independent as MatchValue.
                sal_uInt32 nNumFormat = 0;
                double fValue;
                if (mrContext.NFIsNumberFormat( aFilters[i].MatchValueName, nNumFormat, fValue))
                    aFilters[i].MatchValue = ScDPCache::GetLocaleIndependentFormattedString(
                            fValue, mrContext, nNumFormat);
                else
                    aFilters[i].MatchValue = aFilters[i].MatchValueName;
            }
 
            aFilters[i].FieldName = GetString().getString();
        }
 
        switch (GetStackType())
        {
            case svDoubleRef :
                PopDoubleRef(aBlock);
            break;
            case svSingleRef :
            {
                ScAddress aAddr;
                PopSingleRef(aAddr);
                aBlock = aAddr;
            }
            break;
            default:
                PushError(FormulaError::NoRef);
                return;
        }
 
        aDataFieldName = GetString().getString(); // First parameter is data field name.
    }
 
    // Early bail-out, don't grind through data pilot cache and all.
    if (nGlobalError != FormulaError::NONE)
    {
        PushError( nGlobalError);
        return;
    }
 
    // NOTE : MS Excel docs claim to use the 'most recent' which is not
    // exactly the same as what we do in ScDocument::GetDPAtBlock
    // However we do need to use GetDPABlock
    ScDPObject* pDPObj = mrDoc.GetDPAtBlock(aBlock);
    if (!pDPObj)
    {
        PushError(FormulaError::NoRef);
        return;
    }
 
    if (bOldSyntax)
    {
        OUString aFilterStr = aDataFieldName;
        std::vector<sal_Int16> aFilterFuncs;
        if (!pDPObj->ParseFilters(aDataFieldName, aFilters, aFilterFuncs, aFilterStr))
        {
            PushError(FormulaError::NoRef);
            return;
        }
 
        // TODO : For now, we ignore filter functions since we couldn't find a
        // live example of how they are supposed to be used. We'll support
        // this again once we come across a real-world example.
    }
 
    double fVal = pDPObj->GetPivotData(aDataFieldName, aFilters);
    if (std::isnan(fVal))
    {
        PushError(FormulaError::NoRef);
        return;
    }
    PushDouble(fVal);
}
 
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'padToLength' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V530 The return value of function 'append' is required to be utilized.

V547 Expression 'nGlobalError != FormulaError::NONE' is always false.

V547 Expression 'nGlobalError != FormulaError::NONE' is always false.