/* -*- 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/. *
*/
/* * ScETSForecastCalculation * * Class is set up to be used with Calc's FORECAST.ETS * functions and with chart extrapolations (not yet implemented). * * Triple Exponential Smoothing (Holt-Winters method) * * Forecasting of a linear change in data over time (y=a+b*x) with * superimposed absolute or relative seasonal deviations, using additive * respectively multiplicative Holt-Winters method. * * Initialisation and forecasting calculations are based on * Engineering Statistics Handbook, 6.4.3.5 Triple Exponential Smoothing * see "http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc435.htm" * Further to the above is that initial calculation of Seasonal effect * is corrected for trend. * * Prediction Interval calculations are based on * Yar & Chatfield, Prediction Intervals for the Holt-Winters forecasting * procedure, International Journal of Forecasting, 1990, Vol.6, pp127-137 * The calculation here is a simplified numerical approximation of the above, * using random distributions. * * Double Exponential Smoothing (Holt-Winters method) * * Forecasting of a linear change in data over time (y=a+b*x), using * the Holt-Winters method. * * Initialisation and forecasting calculations are based on * Engineering Statistics Handbook, 6.4.3.3 Double Exponential Smoothing * see "http://www.itl.nist.gov/div898/handbook/pmc/section4/pmc433.htm" * * Prediction Interval calculations are based on * Statistical Methods for Forecasting, Bovas & Ledolter, 2009, 3.8 Prediction * Intervals for Future Values *
*/
namespace {
class ScETSForecastCalculation
{ private: const ScInterpreterContext& mrContext;
std::vector< DataPoint > maRange; // data (X, Y)
std::unique_ptr<double[]> mpBase; // calculated base value array
std::unique_ptr<double[]> mpTrend; // calculated trend factor array
std::unique_ptr<double[]> mpPerIdx; // calculated periodical deviation array, not used with eds
std::unique_ptr<double[]> mpForecast; // forecasted value array
SCSIZE mnSmplInPrd; // samples per period double mfStepSize; // increment of X in maRange double mfAlpha, mfBeta, mfGamma; // constants to minimize the RMSE in the ES-equations
SCSIZE mnCount; // No of data points bool mbInitialised; int mnMonthDay; // n-month X-interval, value is day of month // accuracy indicators double mfMAE; // mean absolute error double mfMASE; // mean absolute scaled error double mfMSE; // mean squared error (variation) double mfRMSE; // root mean squared error (standard deviation) double mfSMAPE; // symmetric mean absolute error
FormulaError mnErrorValue; bool bAdditive; // true: additive method, false: multiplicative method bool bEDS; // true: EDS, false: ETS
// constants used in determining best fit for alpha, beta, gamma static constexpr double cfMinABCResolution = 0.001; // minimum change of alpha, beta, gamma staticconst SCSIZE cnScenarios = 1000; // No. of scenarios to calculate for PI calculations
// maRange needs to be sorted by X for ( SCSIZE i = 0; i < mnCount; i++ )
maRange.emplace_back( rMatX->GetDouble( i ), rMatY->GetDouble( i ) );
sort( maRange.begin(), maRange.end(), lcl_SortByX );
if ( rTMat )
{ if ( eETSType != etsPIAdd && eETSType != etsPIMult )
{ if ( rTMat->GetDouble( 0 ) < maRange[ 0 ].X )
{ // target cannot be less than start of X-range
mnErrorValue = FormulaError::IllegalFPOperation; returnfalse;
}
} else
{ if ( rTMat->GetDouble( 0 ) < maRange[ mnCount - 1 ].X )
{ // target cannot be before end of X-range
mnErrorValue = FormulaError::IllegalFPOperation; returnfalse;
}
}
}
// Month intervals don't have exact stepsize, so first // detect if month interval is used. // Method: assume there is an month interval and verify. // If month interval is used, replace maRange.X with month values // for ease of calculations.
Date aNullDate = mrContext.NFGetNullDate();
Date aDate = aNullDate + static_cast< sal_Int32 >( maRange[ 0 ].X );
mnMonthDay = aDate.GetDay(); for ( SCSIZE i = 1; i < mnCount && mnMonthDay; i++ )
{
Date aDate1 = aNullDate + static_cast< sal_Int32 >( maRange[ i ].X ); if ( aDate != aDate1 )
{ if ( aDate1.GetDay() != mnMonthDay )
mnMonthDay = 0;
}
}
mfStepSize = ::std::numeric_limits<double>::max(); if ( mnMonthDay )
{ for ( SCSIZE i = 0; i < mnCount; i++ )
{
aDate = aNullDate + static_cast< sal_Int32 >( maRange[ i ].X );
maRange[ i ].X = aDate.GetYear() * 12 + aDate.GetMonth();
}
} for ( SCSIZE i = 1; i < mnCount; i++ )
{ double fStep = maRange[ i ].X - maRange[ i - 1 ].X; if ( fStep == 0.0 )
{ if ( nAggregation == 0 )
{ // identical X-values are not allowed
mnErrorValue = FormulaError::NoValue; returnfalse;
} double fTmp = maRange[ i - 1 ].Y;
SCSIZE nCounter = 1; switch ( nAggregation )
{ case 1 : // AVERAGE (default) while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X )
{
maRange.erase( maRange.begin() + i );
--mnCount;
} break; case 7 : // SUM while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X )
{
fTmp += maRange[ i ].Y;
maRange.erase( maRange.begin() + i );
--mnCount;
}
maRange[ i - 1 ].Y = fTmp; break;
case 2 : // COUNT case 3 : // COUNTA (same as COUNT as there are no non-numeric Y-values) while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X )
{
nCounter++;
maRange.erase( maRange.begin() + i );
--mnCount;
}
maRange[ i - 1 ].Y = nCounter; break;
case 4 : // MAX while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X )
{ if ( maRange[ i ].Y > fTmp )
fTmp = maRange[ i ].Y;
maRange.erase( maRange.begin() + i );
--mnCount;
}
maRange[ i - 1 ].Y = fTmp; break;
case 5 : // MEDIAN
{
std::vector< double > aTmp { maRange[ i - 1 ].Y }; while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X )
{
aTmp.push_back( maRange[ i ].Y );
nCounter++;
maRange.erase( maRange.begin() + i );
--mnCount;
}
sort( aTmp.begin(), aTmp.end() );
case 6 : // MIN while ( i < mnCount && maRange[ i ].X == maRange[ i - 1 ].X )
{ if ( maRange[ i ].Y < fTmp )
fTmp = maRange[ i ].Y;
maRange.erase( maRange.begin() + i );
--mnCount;
}
maRange[ i - 1 ].Y = fTmp; break;
} if ( i < mnCount - 1 )
fStep = maRange[ i ].X - maRange[ i - 1 ].X; else
fStep = mfStepSize;
} if ( fStep > 0 && fStep < mfStepSize )
mfStepSize = fStep;
}
// step must be constant (or gap multiple of step) bool bHasGap = false; for ( SCSIZE i = 1; i < mnCount && !bHasGap; i++ )
{ double fStep = maRange[ i ].X - maRange[ i - 1 ].X;
if ( fStep != mfStepSize )
{ if ( fmod( fStep, mfStepSize ) != 0.0 )
{ // step not constant nor multiple of mfStepSize in case of gaps
mnErrorValue = FormulaError::NoValue; returnfalse;
}
bHasGap = true;
}
}
// fill gaps with values depending on bDataCompletion if ( bHasGap )
{
SCSIZE nMissingXCount = 0; double fOriginalCount = static_cast< double >( mnCount ); if ( mnMonthDay )
aDate = aNullDate + static_cast< sal_Int32 >( maRange[ 0 ].X ); for ( SCSIZE i = 1; i < mnCount; i++ )
{ double fDist; if ( mnMonthDay )
{
Date aDate1 = aNullDate + static_cast< sal_Int32 >( maRange[ i ].X );
fDist = 12 * ( aDate1.GetYear() - aDate.GetYear() ) +
( aDate1.GetMonth() - aDate.GetMonth() );
aDate = aDate1;
} else
fDist = maRange[ i ].X - maRange[ i - 1 ].X; if ( fDist > mfStepSize )
{ // gap, insert missing data points double fYGap = ( maRange[ i ].Y + maRange[ i - 1 ].Y ) / 2.0; for ( KahanSum fXGap = maRange[ i - 1].X + mfStepSize; fXGap < maRange[ i ].X; fXGap += mfStepSize )
{
maRange.insert( maRange.begin() + i, DataPoint( fXGap.get(), ( bDataCompletion ? fYGap : 0.0 ) ) );
i++;
mnCount++;
nMissingXCount++; if ( static_cast< double >( nMissingXCount ) / fOriginalCount > 0.3 )
{ // maximum of 30% missing points exceeded
mnErrorValue = FormulaError::NoValue; returnfalse;
}
}
}
}
}
if ( nSmplInPrd != 1 )
mnSmplInPrd = nSmplInPrd; else
{
mnSmplInPrd = CalcPeriodLen(); if ( mnSmplInPrd == 1 )
bEDS = true; // period length 1 means no periodic data: EDS suffices
}
if ( !initData() ) returnfalse; // note: mnErrorValue is set in called function(s)
returntrue;
}
bool ScETSForecastCalculation::initData( )
{ // give various vectors size and initial value
mpBase.reset( newdouble[ mnCount ] );
mpTrend.reset( newdouble[ mnCount ] ); if ( !bEDS )
mpPerIdx.reset( newdouble[ mnCount ] );
mpForecast.reset( newdouble[ mnCount ] );
mpForecast[ 0 ] = maRange[ 0 ].Y;
if ( prefillTrendData() )
{ if ( prefillPerIdx() )
{
prefillBaseData(); returntrue;
}
} returnfalse;
}
bool ScETSForecastCalculation::prefillTrendData()
{ if ( bEDS )
mpTrend[ 0 ] = ( maRange[ mnCount - 1 ].Y - maRange[ 0 ].Y ) / static_cast< double >( mnCount - 1 ); else
{ // we need at least 2 periods in the data range if ( mnCount < 2 * mnSmplInPrd )
{
mnErrorValue = FormulaError::NoValue; returnfalse;
}
KahanSum fSum = 0.0; for ( SCSIZE i = 0; i < mnSmplInPrd; i++ )
{
fSum += maRange[ i + mnSmplInPrd ].Y;
fSum -= maRange[ i ].Y;
} double fTrend = fSum.get() / static_cast< double >( mnSmplInPrd * mnSmplInPrd );
mpTrend[ 0 ] = fTrend;
}
returntrue;
}
bool ScETSForecastCalculation::prefillPerIdx()
{ if ( !bEDS )
{ // use as many complete periods as available if ( mnSmplInPrd == 0 )
{ // should never happen; if mnSmplInPrd equals 0, bEDS is true
mnErrorValue = FormulaError::UnknownState; returnfalse;
}
SCSIZE nPeriods = mnCount / mnSmplInPrd;
std::vector< KahanSum > aPeriodAverage( nPeriods, 0.0 ); for ( SCSIZE i = 0; i < nPeriods ; i++ )
{ for ( SCSIZE j = 0; j < mnSmplInPrd; j++ )
aPeriodAverage[ i ] += maRange[ i * mnSmplInPrd + j ].Y;
aPeriodAverage[ i ] /= static_cast< double >( mnSmplInPrd ); if ( aPeriodAverage[ i ] == 0.0 )
{
SAL_WARN( "sc.core", "prefillPerIdx(), average of 0 will cause divide by zero error, quitting calculation" );
mnErrorValue = FormulaError::DivisionByZero; returnfalse;
}
}
for ( SCSIZE j = 0; j < mnSmplInPrd; j++ )
{
KahanSum fI = 0.0; for ( SCSIZE i = 0; i < nPeriods ; i++ )
{ // adjust average value for position within period if ( bAdditive )
fI += maRange[ i * mnSmplInPrd + j ].Y -
( aPeriodAverage[ i ].get() + ( static_cast< double >( j ) - 0.5 * ( mnSmplInPrd - 1 ) ) *
mpTrend[ 0 ] ); else
fI += maRange[ i * mnSmplInPrd + j ].Y /
( aPeriodAverage[ i ].get() + ( static_cast< double >( j ) - 0.5 * ( mnSmplInPrd - 1 ) ) *
mpTrend[ 0 ] );
}
mpPerIdx[ j ] = fI.get() / nPeriods;
} if (mnSmplInPrd < mnCount)
mpPerIdx[mnSmplInPrd] = 0.0;
} returntrue;
}
/* * CalcPeriodLen() calculates the most likely length of a period. * * Method used: for all possible values (between mnCount/2 and 2) compare for * each (sample-previous sample) with next period and calculate mean error. * Use as much samples as possible for each period length and the most recent samples * Return the period length with the lowest mean error.
*/
SCSIZE ScETSForecastCalculation::CalcPeriodLen()
{
SCSIZE nBestVal = mnCount; double fBestME = ::std::numeric_limits<double>::max();
double ScETSForecastCalculation::RandDev()
{ // return a random deviation given the standard deviation return ( mfRMSE * ScInterpreter::gaussinv(
::comphelper::rng::uniform_real_distribution( 0.5, 1.0 ) ) );
}
switch ( eETSType )
{ case etsAdd : case etsMult :
{
SCSIZE nC, nR;
pTMat->GetDimensions( nC, nR );
ScMatrixRef pFcMat = GetNewMat( nC, nR, /*bEmpty*/true );
aETSCalc.GetForecastRange( pTMat, pFcMat ); if (aETSCalc.GetError() != FormulaError::NONE)
PushError( aETSCalc.GetError()); // explicitly push error, PushMatrix() does not else
PushMatrix( pFcMat );
} break; case etsPIAdd : case etsPIMult :
{
SCSIZE nC, nR;
pTMat->GetDimensions( nC, nR );
ScMatrixRef pPIMat = GetNewMat( nC, nR, /*bEmpty*/true ); if ( nSmplInPrd == 0 )
{
aETSCalc.GetEDSPredictionIntervals( pTMat, pPIMat, fPILevel );
} else
{
aETSCalc.GetETSPredictionIntervals( pTMat, pPIMat, fPILevel );
} if (aETSCalc.GetError() != FormulaError::NONE)
PushError( aETSCalc.GetError()); // explicitly push error, PushMatrix() does not else
PushMatrix( pPIMat );
} break; case etsStatAdd : case etsStatMult :
{
SCSIZE nC, nR;
pTypeMat->GetDimensions( nC, nR );
ScMatrixRef pStatMat = GetNewMat( nC, nR, /*bEmpty*/true );
aETSCalc.GetStatisticValue( pTypeMat, pStatMat ); if (aETSCalc.GetError() != FormulaError::NONE)
PushError( aETSCalc.GetError()); // explicitly push error, PushMatrix() does not else
PushMatrix( pStatMat );
} break; case etsSeason :
{ double rVal;
aETSCalc.GetSamplesInPeriod( rVal );
SetError( aETSCalc.GetError() );
PushDouble( rVal );
} break;
}
}
void ScInterpreter::ScConcat_MS()
{
OUStringBuffer aResBuf; short nParamCount = GetByte();
//reverse order of parameter stack to simplify concatenation:
ReverseStack( nParamCount );
size_t nRefInList = 0; while ( nParamCount-- > 0 && nGlobalError == FormulaError::NONE )
{ switch ( GetStackType() )
{ case svString: case svDouble:
{
OUString aStr = GetString().getString(); if (CheckStringResultLen(aResBuf, aStr.getLength()))
aResBuf.append(aStr);
} break; case svSingleRef :
{
ScAddress aAdr;
PopSingleRef( aAdr ); if ( nGlobalError != FormulaError::NONE ) break;
ScRefCellValue aCell( mrDoc, aAdr ); if (!aCell.hasEmptyValue())
{
svl::SharedString aSS;
GetCellString( aSS, aCell); const OUString& rStr = aSS.getString(); if (CheckStringResultLen(aResBuf, rStr.getLength()))
aResBuf.append( rStr);
}
} break; case svDoubleRef : case svRefList :
{
ScRange aRange;
PopDoubleRef( aRange, nParamCount, nRefInList); if ( nGlobalError != FormulaError::NONE ) break; // we need to read row for row, so we can't use ScCellIter
SCCOL nCol1, nCol2;
SCROW nRow1, nRow2;
SCTAB nTab1, nTab2;
aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); if ( nTab1 != nTab2 )
{
SetError( FormulaError::IllegalParameter); break;
}
PutInOrder( nRow1, nRow2 );
PutInOrder( nCol1, nCol2 );
ScAddress aAdr;
aAdr.SetTab( nTab1 ); for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ )
{ for ( SCCOL nCol = nCol1; nCol <= nCol2; nCol++ )
{
aAdr.SetRow( nRow );
aAdr.SetCol( nCol );
ScRefCellValue aCell( mrDoc, aAdr ); if (!aCell.hasEmptyValue() )
{
svl::SharedString aSS;
GetCellString( aSS, aCell); const OUString& rStr = aSS.getString(); if (CheckStringResultLen(aResBuf, rStr.getLength()))
aResBuf.append( rStr);
}
}
}
} break; case svMatrix : case svExternalSingleRef: case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix(); if (pMat)
{
SCSIZE nC, nR;
pMat->GetDimensions(nC, nR); if (nC == 0 || nR == 0)
SetError(FormulaError::IllegalArgument); else
{ for (SCSIZE k = 0; k < nR; ++k)
{ for (SCSIZE j = 0; j < nC; ++j)
{ if ( pMat->IsStringOrEmpty( j, k ) )
{
OUString aStr = pMat->GetString( j, k ).getString(); if (CheckStringResultLen(aResBuf, aStr.getLength()))
aResBuf.append(aStr);
} else
{ if ( pMat->IsValue( j, k ) )
{
OUString aStr = pMat->GetString( mrContext, j, k ).getString(); if (CheckStringResultLen(aResBuf, aStr.getLength()))
aResBuf.append(aStr);
}
}
}
}
}
}
} break; default:
PopError();
SetError( FormulaError::IllegalArgument); break;
}
}
PushString( aResBuf.makeStringAndClear() );
}
void ScInterpreter::ScTextJoin_MS()
{ short nParamCount = GetByte();
if ( !MustHaveParamCountMin( nParamCount, 3 ) ) return;
//reverse order of parameter stack to simplify processing
ReverseStack( nParamCount );
// get aDelimiters and bSkipEmpty
std::vector< OUString > aDelimiters;
size_t nRefInList = 0; switch ( GetStackType() )
{ case svString: case svDouble:
aDelimiters.push_back( GetString().getString() ); break; case svSingleRef :
{
ScAddress aAdr;
PopSingleRef( aAdr ); if ( nGlobalError != FormulaError::NONE ) break;
ScRefCellValue aCell( mrDoc, aAdr ); if (aCell.hasEmptyValue())
aDelimiters.emplace_back(""); else
{
svl::SharedString aSS;
GetCellString( aSS, aCell);
aDelimiters.push_back( aSS.getString());
}
} break; case svDoubleRef : case svRefList :
{
ScRange aRange;
PopDoubleRef( aRange, nParamCount, nRefInList); if ( nGlobalError != FormulaError::NONE ) break; // we need to read row for row, so we can't use ScCellIterator
SCCOL nCol1, nCol2;
SCROW nRow1, nRow2;
SCTAB nTab1, nTab2;
aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); if ( nTab1 != nTab2 )
{
SetError( FormulaError::IllegalParameter); break;
}
PutInOrder( nRow1, nRow2 );
PutInOrder( nCol1, nCol2 );
ScAddress aAdr;
aAdr.SetTab( nTab1 ); for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ )
{ for ( SCCOL nCol = nCol1; nCol <= nCol2; nCol++ )
{
aAdr.SetRow( nRow );
aAdr.SetCol( nCol );
ScRefCellValue aCell( mrDoc, aAdr ); if (aCell.hasEmptyValue())
aDelimiters.emplace_back(""); else
{
svl::SharedString aSS;
GetCellString( aSS, aCell);
aDelimiters.push_back( aSS.getString());
}
}
}
} break; case svMatrix : case svExternalSingleRef: case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix(); if (pMat)
{
SCSIZE nC, nR;
pMat->GetDimensions(nC, nR); if (nC == 0 || nR == 0)
SetError(FormulaError::IllegalArgument); else
{ for (SCSIZE k = 0; k < nR; ++k)
{ for (SCSIZE j = 0; j < nC; ++j)
{ if (pMat->IsEmpty( j, k ))
aDelimiters.emplace_back(""); elseif (pMat->IsStringOrEmpty( j, k ))
aDelimiters.push_back( pMat->GetString( j, k ).getString() ); elseif (pMat->IsValue( j, k ))
aDelimiters.push_back( pMat->GetString( mrContext, j, k ).getString() ); else
{
assert(!"should this really happen?");
aDelimiters.emplace_back("");
}
}
}
}
}
} break; default:
PopError();
SetError( FormulaError::IllegalArgument); break;
} if ( aDelimiters.empty() )
{
PushIllegalArgument(); return;
}
SCSIZE nSize = aDelimiters.size(); bool bSkipEmpty = static_cast< bool >( GetDouble() );
nParamCount -= 2;
OUStringBuffer aResBuf; bool bFirst = true;
SCSIZE nIdx = 0;
nRefInList = 0; // get the strings to be joined while ( nParamCount-- > 0 && nGlobalError == FormulaError::NONE )
{ switch ( GetStackType() )
{ case svString: case svDouble:
{
OUString aStr = GetString().getString(); if ( !aStr.isEmpty() || !bSkipEmpty )
{ if ( !bFirst )
{
aResBuf.append( aDelimiters[ nIdx ] ); if ( nSize > 1 )
{ if ( ++nIdx >= nSize )
nIdx = 0;
}
} else
bFirst = false; if (CheckStringResultLen(aResBuf, aStr.getLength()))
aResBuf.append( aStr );
}
} break; case svSingleRef :
{
ScAddress aAdr;
PopSingleRef( aAdr ); if ( nGlobalError != FormulaError::NONE ) break;
ScRefCellValue aCell( mrDoc, aAdr );
OUString aStr; if (!aCell.hasEmptyValue())
{
svl::SharedString aSS;
GetCellString( aSS, aCell);
aStr = aSS.getString();
} if ( !aStr.isEmpty() || !bSkipEmpty )
{ if ( !bFirst )
{
aResBuf.append( aDelimiters[ nIdx ] ); if ( nSize > 1 )
{ if ( ++nIdx >= nSize )
nIdx = 0;
}
} else
bFirst = false; if (CheckStringResultLen(aResBuf, aStr.getLength()))
aResBuf.append( aStr );
}
} break; case svDoubleRef : case svRefList :
{
ScRange aRange;
PopDoubleRef( aRange, nParamCount, nRefInList); if ( nGlobalError != FormulaError::NONE ) break; // we need to read row for row, so we can't use ScCellIterator
SCCOL nCol1, nCol2;
SCROW nRow1, nRow2;
SCTAB nTab1, nTab2;
aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 ); if ( nTab1 != nTab2 )
{
SetError( FormulaError::IllegalParameter); break;
}
PutInOrder( nRow1, nRow2 );
PutInOrder( nCol1, nCol2 );
ScAddress aAdr;
aAdr.SetTab( nTab1 );
OUString aStr; for ( SCROW nRow = nRow1; nRow <= nRow2; nRow++ )
{ for ( SCCOL nCol = nCol1; nCol <= nCol2; nCol++ )
{
aAdr.SetRow( nRow );
aAdr.SetCol( nCol );
ScRefCellValue aCell( mrDoc, aAdr ); if (aCell.hasEmptyValue())
aStr.clear(); else
{
svl::SharedString aSS;
GetCellString( aSS, aCell);
aStr = aSS.getString();
} if ( !aStr.isEmpty() || !bSkipEmpty )
{ if ( !bFirst )
{
aResBuf.append( aDelimiters[ nIdx ] ); if ( nSize > 1 )
{ if ( ++nIdx >= nSize )
nIdx = 0;
}
} else
bFirst = false; if (CheckStringResultLen(aResBuf, aStr.getLength()))
aResBuf.append( aStr );
}
}
}
} break; case svMatrix : case svExternalSingleRef: case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix(); if (pMat)
{
SCSIZE nC, nR;
pMat->GetDimensions(nC, nR); if (nC == 0 || nR == 0)
SetError(FormulaError::IllegalArgument); else
{
OUString aStr; for (SCSIZE k = 0; k < nR; ++k)
{ for (SCSIZE j = 0; j < nC; ++j)
{ if (pMat->IsEmpty( j, k ) )
aStr.clear(); elseif (pMat->IsStringOrEmpty( j, k ))
aStr = pMat->GetString( j, k ).getString(); elseif (pMat->IsValue( j, k ))
aStr = pMat->GetString( mrContext, j, k ).getString(); else
{
assert(!"should this really happen?");
aStr.clear();
} if ( !aStr.isEmpty() || !bSkipEmpty )
{ if ( !bFirst )
{
aResBuf.append( aDelimiters[ nIdx ] ); if ( nSize > 1 )
{ if ( ++nIdx >= nSize )
nIdx = 0;
}
} else
bFirst = false; if (CheckStringResultLen(aResBuf, aStr.getLength()))
aResBuf.append( aStr );
}
}
}
}
}
} break; case svMissing :
{ if ( !bSkipEmpty )
{ if ( !bFirst )
{
aResBuf.append( aDelimiters[ nIdx ] ); if ( nSize > 1 )
{ if ( ++nIdx >= nSize )
nIdx = 0;
}
} else
bFirst = false;
}
} break; default:
PopError();
SetError( FormulaError::IllegalArgument); break;
}
}
PushString( aResBuf.makeStringAndClear() );
}
void ScInterpreter::ScIfs_MS()
{ short nParamCount = GetByte();
ReverseStack( nParamCount );
nGlobalError = FormulaError::NONE; // propagate only for condition or active result path bool bFinished = false; while ( nParamCount > 0 && !bFinished && nGlobalError == FormulaError::NONE )
{ bool bVal = GetBool();
nParamCount--; if ( bVal )
{ // TRUE if ( nParamCount < 1 )
{ // no parameter given for THEN
PushParameterExpected(); return;
}
bFinished = true;
} else
{ // FALSE if ( nParamCount >= 3 )
{ // ELSEIF path
Pop();
nParamCount--;
} else
{ // no parameter given for ELSE
PushNA(); return;
}
}
}
if ( nGlobalError != FormulaError::NONE || !bFinished )
{ if ( !bFinished )
PushNA(); // no true expression found if ( nGlobalError != FormulaError::NONE )
PushNoValue(); // expression returned something other than true or false return;
}
//push result :
FormulaConstTokenRef xToken( PopToken() ); if ( xToken )
{ // Remove unused arguments of IFS from the stack before pushing the result. while ( nParamCount > 1 )
{
Pop();
nParamCount--;
}
PushTokenRef( xToken );
} else
PushError( FormulaError::UnknownStackVariable );
}
void ScInterpreter::ScSwitch_MS()
{ short nParamCount = GetByte();
if (!MustHaveParamCountMin( nParamCount, 3)) return;
ReverseStack( nParamCount );
nGlobalError = FormulaError::NONE; // propagate only for match or active result path bool isValue = false; double fRefVal = 0;
svl::SharedString aRefStr; switch ( GetStackType() )
{ case svDouble:
isValue = true;
fRefVal = GetDouble(); break; case svString:
isValue = false;
aRefStr = GetString(); break; case svSingleRef : case svDoubleRef :
{
ScAddress aAdr; if (!PopDoubleRefOrSingleRef( aAdr )) break;
ScRefCellValue aCell( mrDoc, aAdr );
isValue = !( aCell.hasString() || aCell.hasEmptyValue() || aCell.isEmpty() ); if ( isValue )
fRefVal = GetCellValue( aAdr, aCell); else
GetCellString( aRefStr, aCell);
} break; case svExternalSingleRef: case svExternalDoubleRef: case svMatrix:
isValue = ScMatrix::IsValueType( GetDoubleOrStringFromMatrix( fRefVal, aRefStr ) ); break; default :
PopError();
PushIllegalArgument(); return;
}
nParamCount--; bool bFinished = false; while ( nParamCount > 1 && !bFinished && nGlobalError == FormulaError::NONE )
{ double fVal = 0;
svl::SharedString aStr;
--> --------------------
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.