summaryrefslogtreecommitdiffstats
path: root/sc/source/core/tool/interpr2.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/tool/interpr2.cxx')
-rw-r--r--sc/source/core/tool/interpr2.cxx3679
1 files changed, 3679 insertions, 0 deletions
diff --git a/sc/source/core/tool/interpr2.cxx b/sc/source/core/tool/interpr2.cxx
new file mode 100644
index 000000000..6ee1de409
--- /dev/null
+++ b/sc/source/core/tool/interpr2.cxx
@@ -0,0 +1,3679 @@
+/* -*- 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 <sfx2/bindings.hxx>
+#include <sfx2/linkmgr.hxx>
+#include <sfx2/objsh.hxx>
+#include <svl/numformat.hxx>
+#include <svl/zforlist.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 <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 ::std::vector;
+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 = pFormatter->ExpandTwoDigitYear( 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 - pFormatter->GetNullDate());
+ else
+ {
+ SetError(FormulaError::NoValue);
+ return 0;
+ }
+}
+
+void ScInterpreter::ScGetActDate()
+{
+ nFuncFmtType = SvNumFormatType::DATE;
+ Date aActDate( Date::SYSTEM );
+ tools::Long nDiff = aActDate - pFormatter->GetNullDate();
+ PushDouble(static_cast<double>(nDiff));
+}
+
+void ScInterpreter::ScGetActTime()
+{
+ nFuncFmtType = SvNumFormatType::DATETIME;
+ DateTime aActTime( DateTime::SYSTEM );
+ tools::Long nDiff = aActTime - pFormatter->GetNullDate();
+ 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 = pFormatter->GetNullDate();
+ aDate.AddDays( GetInt32());
+ PushDouble( static_cast<double>(aDate.GetYear()) );
+}
+
+void ScInterpreter::ScGetMonth()
+{
+ Date aDate = pFormatter->GetNullDate();
+ aDate.AddDays( GetInt32());
+ PushDouble( static_cast<double>(aDate.GetMonth()) );
+}
+
+void ScInterpreter::ScGetDay()
+{
+ Date aDate = pFormatter->GetNullDate();
+ aDate.AddDays( GetInt32());
+ PushDouble(static_cast<double>(aDate.GetDay()));
+}
+
+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 (pFormatter->IsNumberFormat(aInputString, nFIndex, fVal))
+ {
+ SvNumFormatType eType = pFormatter->GetType(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 = pFormatter->GetNullDate();
+ aDate.AddDays( GetInt32());
+ 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 = pFormatter->GetNullDate();
+ aDate.AddDays( GetInt32());
+ 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 : GetInt16();
+
+ Date aDate = pFormatter->GetNullDate();
+ aDate.AddDays( GetInt32());
+
+ 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 = pFormatter->GetNullDate();
+ aDate.AddDays( GetInt32());
+ 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 = pFormatter->ExpandTwoDigitYear( 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_uInt32 nNullDate, vector< double >& rSortArray,
+ bool bWeekendMask[ 7 ] )
+{
+ if ( nParamCount == 4 )
+ {
+ 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_uInt32 nNullDate, 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;
+
+ vector<double> nSortArray;
+ bool bWeekendMask[ 7 ];
+ const Date& rNullDate = pFormatter->GetNullDate();
+ sal_uInt32 nNullDate = Date::DateToDays( rNullDate.GetDay(), rNullDate.GetMonth(), rNullDate.GetYear() );
+ 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_uInt32 nDate2 = GetUInt32();
+ sal_uInt32 nDate1 = GetUInt32();
+ if (nGlobalError != FormulaError::NONE || (nDate1 > SAL_MAX_UINT32 - nNullDate) || nDate2 > (SAL_MAX_UINT32 - nNullDate))
+ {
+ PushIllegalArgument();
+ return;
+ }
+ nDate2 += nNullDate;
+ nDate1 += nNullDate;
+
+ sal_Int32 nCnt = 0;
+ size_t nRef = 0;
+ bool bReverse = ( nDate1 > nDate2 );
+ if ( bReverse )
+ {
+ sal_uInt32 nTemp = nDate1;
+ nDate1 = nDate2;
+ nDate2 = nTemp;
+ }
+ 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;
+ vector<double> nSortArray;
+ bool bWeekendMask[ 7 ];
+ const Date& rNullDate = pFormatter->GetNullDate();
+ sal_uInt32 nNullDate = Date::DateToDays( rNullDate.GetDay(), rNullDate.GetMonth(), rNullDate.GetYear() );
+ FormulaError nErr = GetWeekendAndHolidayMasks_MS( nParamCount, nNullDate,
+ nSortArray, bWeekendMask, true );
+ if ( nErr != FormulaError::NONE )
+ PushError( nErr );
+ else
+ {
+ sal_Int32 nDays = GetInt32();
+ sal_uInt32 nDate = GetUInt32();
+ if (nGlobalError != FormulaError::NONE || (nDate > SAL_MAX_UINT32 - 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 || nRef >= nMax )
+ 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 = GetInt32();
+ sal_Int32 nDate1 = GetInt32();
+ 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 = pFormatter->GetNullDate();
+ aDate1.AddDays( nDate1);
+ Date aDate2 = pFormatter->GetNullDate();
+ 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 = GetInt32();
+ sal_Int32 nDate1 = GetInt32();
+
+ 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( pFormatter->GetNullDate());
+ aDate1.AddDays( nDate1);
+ y1 = aDate1.GetYear();
+ m1 = aDate1.GetMonth();
+ d1 = aDate1.GetDay();
+ Date aDate2( pFormatter->GetNullDate());
+ 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 (pFormatter->IsNumberFormat(aInputString, nFIndex, fVal, SvNumInputOptions::LAX_TIME))
+ {
+ SvNumFormatType eType = pFormatter->GetType(nFIndex);
+ if (eType == SvNumFormatType::TIME || eType == SvNumFormatType::DATETIME)
+ {
+ nFuncFmtType = SvNumFormatType::TIME;
+ double fDateVal = rtl::math::approxFloor(fVal);
+ double fTimeVal = fVal - fDateVal;
+ 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
+ {
+ sal_Int16 nDec = GetInt16();
+ double fX = GetDouble();
+ if (nGlobalError == FormulaError::NONE)
+ {
+ if ( ( eMode == rtl_math_RoundingMode_Down ||
+ eMode == rtl_math_RoundingMode_Up ) &&
+ nDec < 12 && fmod( fX, 1.0 ) != 0.0 )
+ {
+ // tdf124286 : round to 12 significant digits before rounding
+ // down or up to avoid unexpected rounding errors
+ // caused by decimal -> binary -> decimal conversion
+ double fRes;
+ RoundSignificant( fX, 12, fRes );
+ fVal = ::rtl::math::round( fRes, 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 = ::rtl::math::approxFloor( log10( std::abs(fX) ) ) + 1.0 - fDigits;
+ fRes = ::rtl::math::round( pow(10.0, -fTemp ) * fX ) * 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, mrDoc, 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 - 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, mrDoc, 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 = -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 * (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 = ((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 = 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 ) / rtl::math::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 = (fPv + fFv) / fNper;
+ else
+ {
+ if (bPayInAdvance) // payment in advance
+ fPayment = (fFv + fPv * exp( fNper * ::rtl::math::log1p(fRate) ) ) * fRate /
+ (std::expm1( (fNper + 1) * ::rtl::math::log1p(fRate) ) - fRate);
+ else // payment in arrear
+ fPayment = (fFv + fPv * exp(fNper * ::rtl::math::log1p(fRate) ) ) * fRate /
+ std::expm1( fNper * ::rtl::math::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(-(fPV + fFV)/fPmt);
+ else if (bPayInAdvance)
+ PushDouble(log(-(fRate*fFV-fPmt*(1.0+fRate))/(fRate*fPV+fPmt*(1.0+fRate)))
+ / rtl::math::log1p(fRate));
+ else
+ PushDouble(log(-(fRate*fFV-fPmt)/(fRate*fPV+fPmt)) / rtl::math::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::NoRef);
+ 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::NoRef);
+ 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() )
+ {
+ SfxObjectShell* 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_uInt32 nTmp = GetUInt32();
+ if (nGlobalError != FormulaError::NONE || 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( pChars[ nIndex ] ).append( 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( const OUString& 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 }
+ };
+
+ for (const auto & i : aConvertTable)
+ if ( aSearchUnit.equalsIgnoreAsciiCaseAscii( 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
+#define UTF8_TH_0 "\340\270\250\340\270\271\340\270\231\340\270\242\340\271\214"
+#define UTF8_TH_1 "\340\270\253\340\270\231\340\270\266\340\271\210\340\270\207"
+#define UTF8_TH_2 "\340\270\252\340\270\255\340\270\207"
+#define UTF8_TH_3 "\340\270\252\340\270\262\340\270\241"
+#define UTF8_TH_4 "\340\270\252\340\270\265\340\271\210"
+#define UTF8_TH_5 "\340\270\253\340\271\211\340\270\262"
+#define UTF8_TH_6 "\340\270\253\340\270\201"
+#define UTF8_TH_7 "\340\271\200\340\270\210\340\271\207\340\270\224"
+#define UTF8_TH_8 "\340\271\201\340\270\233\340\270\224"
+#define UTF8_TH_9 "\340\271\200\340\270\201\340\271\211\340\270\262"
+#define UTF8_TH_10 "\340\270\252\340\270\264\340\270\232"
+#define UTF8_TH_11 "\340\271\200\340\270\255\340\271\207\340\270\224"
+#define UTF8_TH_20 "\340\270\242\340\270\265\340\271\210"
+#define UTF8_TH_1E2 "\340\270\243\340\271\211\340\270\255\340\270\242"
+#define UTF8_TH_1E3 "\340\270\236\340\270\261\340\270\231"
+#define UTF8_TH_1E4 "\340\270\253\340\270\241\340\270\267\340\271\210\340\270\231"
+#define UTF8_TH_1E5 "\340\271\201\340\270\252\340\270\231"
+#define UTF8_TH_1E6 "\340\270\245\340\271\211\340\270\262\340\270\231"
+#define UTF8_TH_DOT0 "\340\270\226\340\271\211\340\270\247\340\270\231"
+#define UTF8_TH_BAHT "\340\270\232\340\270\262\340\270\227"
+#define UTF8_TH_SATANG "\340\270\252\340\270\225\340\270\262\340\270\207\340\270\204\340\271\214"
+#define UTF8_TH_MINUS "\340\270\245\340\270\232"
+
+// local functions
+namespace {
+
+void lclSplitBlock( double& rfInt, sal_Int32& rnBlock, double fValue, double fSize )
+{
+ rnBlock = static_cast< sal_Int32 >( modf( (fValue + 0.1) / fSize, &rfInt ) * fSize + 0.1 );
+}
+
+/** Appends a digit (0 to 9) to the passed string. */
+void lclAppendDigit( OStringBuffer& rText, sal_Int32 nDigit )
+{
+ switch( nDigit )
+ {
+ case 0: rText.append( UTF8_TH_0 ); break;
+ case 1: rText.append( UTF8_TH_1 ); break;
+ case 2: rText.append( UTF8_TH_2 ); break;
+ case 3: rText.append( UTF8_TH_3 ); break;
+ case 4: rText.append( UTF8_TH_4 ); break;
+ case 5: rText.append( UTF8_TH_5 ); break;
+ case 6: rText.append( UTF8_TH_6 ); break;
+ case 7: rText.append( UTF8_TH_7 ); break;
+ case 8: rText.append( UTF8_TH_8 ); break;
+ case 9: rText.append( UTF8_TH_9 ); break;
+ default: OSL_FAIL( "lclAppendDigit - illegal digit" );
+ }
+}
+
+/** 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( OStringBuffer& rText, sal_Int32 nDigit, sal_Int32 nPow10 )
+{
+ OSL_ENSURE( (1 <= nDigit) && (nDigit <= 9), "lclAppendPow10 - illegal digit" );
+ lclAppendDigit( rText, nDigit );
+ switch( nPow10 )
+ {
+ case 2: rText.append( UTF8_TH_1E2 ); break;
+ case 3: rText.append( UTF8_TH_1E3 ); break;
+ case 4: rText.append( UTF8_TH_1E4 ); break;
+ case 5: rText.append( UTF8_TH_1E5 ); break;
+ default: OSL_FAIL( "lclAppendPow10 - illegal power" );
+ }
+}
+
+/** Appends a block of 6 digits (value from 1 to 999,999) to the passed string. */
+void lclAppendBlock( OStringBuffer& rText, sal_Int32 nValue )
+{
+ OSL_ENSURE( (1 <= nValue) && (nValue <= 999999), "lclAppendBlock - illegal value" );
+ if( nValue >= 100000 )
+ {
+ lclAppendPow10( rText, nValue / 100000, 5 );
+ nValue %= 100000;
+ }
+ if( nValue >= 10000 )
+ {
+ lclAppendPow10( rText, nValue / 10000, 4 );
+ nValue %= 10000;
+ }
+ if( nValue >= 1000 )
+ {
+ lclAppendPow10( rText, nValue / 1000, 3 );
+ nValue %= 1000;
+ }
+ if( nValue >= 100 )
+ {
+ lclAppendPow10( rText, nValue / 100, 2 );
+ nValue %= 100;
+ }
+ if( nValue <= 0 )
+ return;
+
+ sal_Int32 nTen = nValue / 10;
+ sal_Int32 nOne = nValue % 10;
+ if( nTen >= 1 )
+ {
+ if( nTen >= 3 )
+ lclAppendDigit( rText, nTen );
+ else if( nTen == 2 )
+ rText.append( UTF8_TH_20 );
+ rText.append( UTF8_TH_10 );
+ }
+ if( (nTen > 0) && (nOne == 1) )
+ rText.append( UTF8_TH_11 );
+ else if( nOne > 0 )
+ lclAppendDigit( rText, nOne );
+}
+
+} // namespace
+
+void ScInterpreter::ScBahtText()
+{
+ sal_uInt8 nParamCount = GetByte();
+ if ( !MustHaveParamCount( nParamCount, 1 ) )
+ return;
+
+ double fValue = GetDouble();
+ if( nGlobalError != FormulaError::NONE )
+ {
+ PushError( nGlobalError);
+ return;
+ }
+
+ // sign
+ bool bMinus = fValue < 0.0;
+ fValue = std::abs( fValue );
+
+ // round to 2 digits after decimal point, fValue contains Satang as integer
+ fValue = ::rtl::math::approxFloor( fValue * 100.0 + 0.5 );
+
+ // split Baht and Satang
+ double fBaht = 0.0;
+ sal_Int32 nSatang = 0;
+ lclSplitBlock( fBaht, nSatang, fValue, 100.0 );
+
+ OStringBuffer aText;
+
+ // generate text for Baht value
+ if( fBaht == 0.0 )
+ {
+ if( nSatang == 0 )
+ aText.append( UTF8_TH_0 );
+ }
+ else while( fBaht > 0.0 )
+ {
+ OStringBuffer aBlock;
+ sal_Int32 nBlock = 0;
+ lclSplitBlock( fBaht, nBlock, fBaht, 1.0e6 );
+ if( nBlock > 0 )
+ lclAppendBlock( aBlock, nBlock );
+ // add leading "million", if there will come more blocks
+ if( fBaht > 0.0 )
+ aBlock.insert( 0, UTF8_TH_1E6 );
+
+ aText.insert(0, aBlock);
+ }
+ if (!aText.isEmpty())
+ aText.append( UTF8_TH_BAHT );
+
+ // generate text for Satang value
+ if( nSatang == 0 )
+ {
+ aText.append( UTF8_TH_DOT0 );
+ }
+ else
+ {
+ lclAppendBlock( aText, nSatang );
+ aText.append( UTF8_TH_SATANG );
+ }
+
+ // add the minus sign
+ if( bMinus )
+ aText.insert( 0, UTF8_TH_MINUS );
+
+ PushString( OStringToOUString(aText, RTL_TEXTENCODING_UTF8) );
+}
+
+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)
+ {
+ /* 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 = pFormatter->GetStandardFormat( nCurFmtType, ScGlobal::eLnge);
+ }
+ const Color* pColor;
+ pFormatter->GetOutputString( fDouble, nNumFormat, aFilters[i].MatchValueName, &pColor);
+ aFilters[i].MatchValue = ScDPCache::GetLocaleIndependentFormattedString(
+ fDouble, *pFormatter, 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 (pFormatter->IsNumberFormat( aFilters[i].MatchValueName, nNumFormat, fValue))
+ aFilters[i].MatchValue = ScDPCache::GetLocaleIndependentFormattedString(
+ fValue, *pFormatter, 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: */