/* -*- 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 <tools/solar.h>
#include <stdlib.h>
#include <interpre.hxx>
#include <global.hxx>
#include <document.hxx>
#include <dociter.hxx>
#include <matrixoperators.hxx>
#include <scmatrix.hxx>
#include <columniterator.hxx>
#include <unotools/collatorwrapper.hxx>
#include <cassert>
#include <cmath>
#include <memory>
#include <set>
#include <vector>
#include <algorithm>
#include <comphelper/random.hxx>
#include <o3tl/float_int_conversion.hxx>
#include <osl/diagnose.h>
using ::std::vector;
using namespace formula;
/// Two columns of data should be sortable with GetSortArray() and QuickSort()
// This is an arbitrary limit.
static size_t MAX_COUNT_DOUBLE_FOR_SORT(
const ScSheetLimits& rSheetLimits)
{
return rSheetLimits.GetMaxRowCount() *
2 ;
}
const double ScInterpreter::fMaxGammaArgument =
171 .
624376956302 ;
// found experimental
const double fMachEps = ::std::numeric_limits<
double >::epsilon();
namespace {
class ScDistFunc
{
public :
virtual double GetValue(
double x)
const =
0 ;
protected :
~ScDistFunc() {}
};
}
// iteration for inverse distributions
//template< class T > double lcl_IterateInverse( const T& rFunction, double x0, double x1, bool& rConvError )
/** u*w<0.0 fails for values near zero */
static bool lcl_HasChangeOfSign(
double u,
double w )
{
return (u <
0 .
0 && w >
0 .
0 ) || (u >
0 .
0 && w <
0 .
0 );
}
static double lcl_IterateInverse(
const ScDistFunc& rFunction,
double fAx,
double fBx
, bool & rConvError )
{
rConvError = false ;
const double fYEps = 1 .0 E-307 ;
const double fXEps = ::std::numeric_limits<double >::epsilon();
OSL_ENSURE(fAx<fBx, "IterateInverse: wrong interval" );
// find enclosing interval
KahanSum fkAx = fAx;
KahanSum fkBx = fBx;
double fAy = rFunction.GetValue(fAx);
double fBy = rFunction.GetValue(fBx);
KahanSum fTemp;
unsigned short nCount;
for (nCount = 0 ; nCount < 1000 && !lcl_HasChangeOfSign(fAy,fBy); nCount++)
{
if (std::abs(fAy) <= std::abs(fBy))
{
fTemp = fkAx;
fkAx += (fkAx - fkBx) * 2 .0 ;
if (fkAx < 0 .0 )
fkAx = 0 .0 ;
fkBx = fTemp;
fBy = fAy;
fAy = rFunction.GetValue(fkAx.get());
}
else
{
fTemp = fkBx;
fkBx += (fkBx - fkAx) * 2 .0 ;
fkAx = fTemp;
fAy = fBy;
fBy = rFunction.GetValue(fkBx.get());
}
}
fAx = fkAx.get();
fBx = fkBx.get();
if (fAy == 0 .0 )
return fAx;
if (fBy == 0 .0 )
return fBx;
if (!lcl_HasChangeOfSign( fAy, fBy))
{
rConvError = true ;
return 0 .0 ;
}
// inverse quadric interpolation with additional brackets
// set three points
double fPx = fAx;
double fPy = fAy;
double fQx = fBx;
double fQy = fBy;
double fRx = fAx;
double fRy = fAy;
double fSx = 0 .5 * (fAx + fBx); // potential next point
bool bHasToInterpolate = true ;
nCount = 0 ;
while ( nCount < 500 && std::abs(fRy) > fYEps &&
(fBx-fAx) > ::std::max( std::abs(fAx), std::abs(fBx)) * fXEps )
{
if (bHasToInterpolate)
{
if (fPy!=fQy && fQy!=fRy && fRy!=fPy)
{
fSx = fPx * fRy * fQy / (fRy-fPy) / (fQy-fPy)
+ fRx * fQy * fPy / (fQy-fRy) / (fPy-fRy)
+ fQx * fPy * fRy / (fPy-fQy) / (fRy-fQy);
bHasToInterpolate = (fAx < fSx) && (fSx < fBx); // inside the brackets?
}
else
bHasToInterpolate = false ;
}
if (!bHasToInterpolate)
{
fSx = 0 .5 * (fAx + fBx);
// reset points
fQx = fBx; fQy = fBy;
bHasToInterpolate = true ;
}
// shift points for next interpolation
fPx = fQx; fQx = fRx; fRx = fSx;
fPy = fQy; fQy = fRy; fRy = rFunction.GetValue(fSx);
// update brackets
if (lcl_HasChangeOfSign( fAy, fRy))
{
fBx = fRx; fBy = fRy;
}
else
{
fAx = fRx; fAy = fRy;
}
// if last iteration brought too small advance, then do bisection next
// time, for safety
bHasToInterpolate = bHasToInterpolate && (std::abs(fRy) * 2 .0 <= std::abs(fQy));
++nCount;
}
return fRx;
}
// General functions
void ScInterpreter::ScNoName()
{
PushError(FormulaError::NoName);
}
void ScInterpreter::ScBadName()
{
short nParamCount = GetByte();
while (nParamCount-- > 0 )
{
PopError();
}
PushError( FormulaError::NoName);
}
double ScInterpreter::phi(double x)
{
return 0 .39894228040143268 * exp(-(x * x) / 2 .0 );
}
double ScInterpreter::integralPhi(double x)
{ // Using gauss(x)+0.5 has severe cancellation errors for x<-4
return 0 .5 * std::erfc(-x * M_SQRT1_2);
}
double ScInterpreter::taylor(const double * pPolynom, sal_uInt16 nMax, double x)
{
KahanSum nVal = pPolynom[nMax];
for (short i = nMax-1 ; i >= 0 ; i--)
{
nVal = (nVal * x) + pPolynom[i];
}
return nVal.get();
}
double ScInterpreter::gauss(double x)
{
double xAbs = std::abs(x);
sal_uInt16 xShort = static_cast <sal_uInt16>(::rtl::math::approxFloor(xAbs));
double nVal = 0 .0 ;
if (xShort == 0 )
{
static const double t0[] =
{ 0 .39894228040143268 , -0 .06649038006690545 , 0 .00997355701003582 ,
-0 .00118732821548045 , 0 .00011543468761616 , -0 .00000944465625950 ,
0 .00000066596935163 , -0 .00000004122667415 , 0 .00000000227352982 ,
0 .00000000011301172 , 0 .00000000000511243 , -0 .00000000000021218 };
nVal = taylor(t0, 11 , (xAbs * xAbs)) * xAbs;
}
else if (xShort <= 2 )
{
static const double t2[] =
{ 0 .47724986805182079 , 0 .05399096651318805 , -0 .05399096651318805 ,
0 .02699548325659403 , -0 .00449924720943234 , -0 .00224962360471617 ,
0 .00134977416282970 , -0 .00011783742691370 , -0 .00011515930357476 ,
0 .00003704737285544 , 0 .00000282690796889 , -0 .00000354513195524 ,
0 .00000037669563126 , 0 .00000019202407921 , -0 .00000005226908590 ,
-0 .00000000491799345 , 0 .00000000366377919 , -0 .00000000015981997 ,
-0 .00000000017381238 , 0 .00000000002624031 , 0 .00000000000560919 ,
-0 .00000000000172127 , -0 .00000000000008634 , 0 .00000000000007894 };
nVal = taylor(t2, 23 , (xAbs - 2 .0 ));
}
else if (xShort <= 4 )
{
static const double t4[] =
{ 0 .49996832875816688 , 0 .00013383022576489 , -0 .00026766045152977 ,
0 .00033457556441221 , -0 .00028996548915725 , 0 .00018178605666397 ,
-0 .00008252863922168 , 0 .00002551802519049 , -0 .00000391665839292 ,
-0 .00000074018205222 , 0 .00000064422023359 , -0 .00000017370155340 ,
0 .00000000909595465 , 0 .00000000944943118 , -0 .00000000329957075 ,
0 .00000000029492075 , 0 .00000000011874477 , -0 .00000000004420396 ,
0 .00000000000361422 , 0 .00000000000143638 , -0 .00000000000045848 };
nVal = taylor(t4, 20 , (xAbs - 4 .0 ));
}
else
{
static const double asympt[] = { -1 .0 , 1 .0 , -3 .0 , 15 .0 , -105 .0 };
nVal = 0 .5 + phi(xAbs) * taylor(asympt, 4 , 1 .0 / (xAbs * xAbs)) / xAbs;
}
if (x < 0 .0 )
return -nVal;
else
return nVal;
}
// #i26836# new gaussinv implementation by Martin Eitzenberger <m.eitzenberger@unix.net>
double ScInterpreter::gaussinv(double x)
{
double q,t,z;
q=x-0 .5 ;
if (std::abs(q)<=.425 )
{
t=0 .180625 -q*q;
z=
q*
(
(
(
(
(
(
(
t*2509 .0809287301226727 +33430 .575583588128105
)
*t+67265 .770927008700853
)
*t+45921 .953931549871457
)
*t+13731 .693765509461125
)
*t+1971 .5909503065514427
)
*t+133 .14166789178437745
)
*t+3 .387132872796366608
)
/
(
(
(
(
(
(
(
t*5226 .495278852854561 +28729 .085735721942674
)
*t+39307 .89580009271061
)
*t+21213 .794301586595867
)
*t+5394 .1960214247511077
)
*t+687 .1870074920579083
)
*t+42 .313330701600911252
)
*t+1 .0
);
}
else
{
if (q>0 ) t=1 -x;
else t=x;
t=sqrt(-log(t));
if (t<=5 .0 )
{
t+=-1 .6 ;
z=
(
(
(
(
(
(
(
t*7 .7454501427834140764 e-4 +0 .0227238449892691845833
)
*t+0 .24178072517745061177
)
*t+1 .27045825245236838258
)
*t+3 .64784832476320460504
)
*t+5 .7694972214606914055
)
*t+4 .6303378461565452959
)
*t+1 .42343711074968357734
)
/
(
(
(
(
(
(
(
t*1 .05075007164441684324 e-9 +5 .475938084995344946 e-4
)
*t+0 .0151986665636164571966
)
*t+0 .14810397642748007459
)
*t+0 .68976733498510000455
)
*t+1 .6763848301838038494
)
*t+2 .05319162663775882187
)
*t+1 .0
);
}
else
{
t+=-5 .0 ;
z=
(
(
(
(
(
(
(
t*2 .01033439929228813265 e-7 +2 .71155556874348757815 e-5
)
*t+0 .0012426609473880784386
)
*t+0 .026532189526576123093
)
*t+0 .29656057182850489123
)
*t+1 .7848265399172913358
)
*t+5 .4637849111641143699
)
*t+6 .6579046435011037772
)
/
(
(
(
(
(
(
(
t*2 .04426310338993978564 e-15 +1 .4215117583164458887 e-7
)
*t+1 .8463183175100546818 e-5
)
*t+7 .868691311456132591 e-4
)
*t+0 .0148753612908506148525
)
*t+0 .13692988092273580531
)
*t+0 .59983220655588793769
)
*t+1 .0
);
}
if (q<0 .0 ) z=-z;
}
return z;
}
double ScInterpreter::Fakultaet(double x)
{
x = ::rtl::math::approxFloor(x);
if (x < 0 .0 )
return 0 .0 ;
else if (x == 0 .0 )
return 1 .0 ;
else if (x <= 170 .0 )
{
double fTemp = x;
while (fTemp > 2 .0 )
{
fTemp--;
x *= fTemp;
}
}
else
SetError(FormulaError::NoValue);
return x;
}
double ScInterpreter::BinomCoeff(double n, double k)
{
// this method has been duplicated as BinomialCoefficient()
// in scaddins/source/analysis/analysishelper.cxx
double nVal = 0 .0 ;
k = ::rtl::math::approxFloor(k);
if (n < k)
nVal = 0 .0 ;
else if (k == 0 .0 )
nVal = 1 .0 ;
else
{
nVal = n/k;
n--;
k--;
while (k > 0 .0 )
{
nVal *= n/k;
k--;
n--;
}
}
return nVal;
}
// The algorithm is based on lanczos13m53 in lanczos.hpp
// in math library from http://www.boost.org
/** you must ensure fZ>0
Uses a variant of the Lanczos sum with a rational function. */
static double lcl_getLanczosSum(double fZ)
{
static const double fNum[13 ] ={
23531376880 .41075968857200767445163675473 ,
42919803642 .64909876895789904700198885093 ,
35711959237 .35566804944018545154716670596 ,
17921034426 .03720969991975575445893111267 ,
6039542586 .35202800506429164430729792107 ,
1439720407 .311721673663223072794912393972 ,
248874557 .8620541565114603864132294232163 ,
31426415 .58540019438061423162831820536287 ,
2876370 .628935372441225409051620849613599 ,
186056 .2653952234950402949897160456992822 ,
8071 .672002365816210638002902272250613822 ,
210 .8242777515793458725097339207133627117 ,
2 .506628274631000270164908177133837338626
};
static const double fDenom[13 ] = {
0 ,
39916800 ,
120543840 ,
150917976 ,
105258076 ,
45995730 ,
13339535 ,
2637558 ,
357423 ,
32670 ,
1925 ,
66 ,
1
};
// Horner scheme
double fSumNum;
double fSumDenom;
int nI;
if (fZ<=1 .0 )
{
fSumNum = fNum[12 ];
fSumDenom = fDenom[12 ];
for (nI = 11 ; nI >= 0 ; --nI)
{
fSumNum *= fZ;
fSumNum += fNum[nI];
fSumDenom *= fZ;
fSumDenom += fDenom[nI];
}
}
else
// Cancel down with fZ^12; Horner scheme with reverse coefficients
{
double fZInv = 1 /fZ;
fSumNum = fNum[0 ];
fSumDenom = fDenom[0 ];
for (nI = 1 ; nI <=12 ; ++nI)
{
fSumNum *= fZInv;
fSumNum += fNum[nI];
fSumDenom *= fZInv;
fSumDenom += fDenom[nI];
}
}
return fSumNum/fSumDenom;
}
// The algorithm is based on tgamma in gamma.hpp
// in math library from http://www.boost.org
/** You must ensure fZ>0; fZ>171.624376956302 will overflow. */
static double lcl_GetGammaHelper(double fZ)
{
double fGamma = lcl_getLanczosSum(fZ);
const double fg = 6 .024680040776729583740234375 ;
double fZgHelp = fZ + fg - 0 .5 ;
// avoid intermediate overflow
double fHalfpower = pow( fZgHelp, fZ / 2 - 0 .25 );
fGamma *= fHalfpower;
fGamma /= exp(fZgHelp);
fGamma *= fHalfpower;
if (fZ <= 20 .0 && fZ == ::rtl::math::approxFloor(fZ))
fGamma = ::rtl::math::round(fGamma);
return fGamma;
}
// The algorithm is based on tgamma in gamma.hpp
// in math library from http://www.boost.org
/** You must ensure fZ>0 */
static double lcl_GetLogGammaHelper(double fZ)
{
const double fg = 6 .024680040776729583740234375 ;
double fZgHelp = fZ + fg - 0 .5 ;
return log( lcl_getLanczosSum(fZ)) + (fZ-0 .5 ) * log(fZgHelp) - fZgHelp;
}
/** You must ensure non integer arguments for fZ<1 */
double ScInterpreter::GetGamma(double fZ)
{
const double fLogPi = log(M_PI);
const double fLogDblMax = log( ::std::numeric_limits<double >::max());
if (fZ > fMaxGammaArgument)
{
SetError(FormulaError::IllegalFPOperation);
return HUGE_VAL;
}
if (fZ >= 1 .0 )
return lcl_GetGammaHelper(fZ);
if (fZ >= 0 .5 ) // shift to x>=1 using Gamma(x)=Gamma(x+1)/x
return lcl_GetGammaHelper(fZ+1 ) / fZ;
if (fZ >= -0 .5 ) // shift to x>=1, might overflow
{
double fLogTest = lcl_GetLogGammaHelper(fZ+2 ) - std::log1p(fZ) - log( std::abs(fZ));
if (fLogTest >= fLogDblMax)
{
SetError( FormulaError::IllegalFPOperation);
return HUGE_VAL;
}
return lcl_GetGammaHelper(fZ+2 ) / (fZ+1 ) / fZ;
}
// fZ<-0.5
// Use Euler's reflection formula: gamma(x)= pi/ ( gamma(1-x)*sin(pi*x) )
double fLogDivisor = lcl_GetLogGammaHelper(1 -fZ) + log( std::abs( ::rtl::math::sin( M_PI*fZ)));
if (fLogDivisor - fLogPi >= fLogDblMax) // underflow
return 0 .0 ;
if (fLogDivisor<0 .0 )
if (fLogPi - fLogDivisor > fLogDblMax) // overflow
{
SetError(FormulaError::IllegalFPOperation);
return HUGE_VAL;
}
return exp( fLogPi - fLogDivisor) * ((::rtl::math::sin( M_PI*fZ) < 0 .0 ) ? -1 .0 : 1 .0 );
}
/** You must ensure fZ>0 */
double ScInterpreter::GetLogGamma(double fZ)
{
if (fZ >= fMaxGammaArgument)
return lcl_GetLogGammaHelper(fZ);
if (fZ >= 1 .0 )
return log(lcl_GetGammaHelper(fZ));
if (fZ >= 0 .5 )
return log( lcl_GetGammaHelper(fZ+1 ) / fZ);
return lcl_GetLogGammaHelper(fZ+2 ) - std::log1p(fZ) - log(fZ);
}
double ScInterpreter::GetFDist(double x, double fF1, double fF2)
{
double arg = fF2/(fF2+fF1*x);
double alpha = fF2/2 .0 ;
double beta = fF1/2 .0 ;
return GetBetaDist(arg, alpha, beta);
}
double ScInterpreter::GetTDist( double T, double fDF, int nType )
{
switch ( nType )
{
case 1 : // 1-tailed T-distribution
return 0 .5 * GetBetaDist( fDF / ( fDF + T * T ), fDF / 2 .0 , 0 .5 );
case 2 : // 2-tailed T-distribution
return GetBetaDist( fDF / ( fDF + T * T ), fDF / 2 .0 , 0 .5 );
case 3 : // left-tailed T-distribution (probability density function)
return pow( 1 + ( T * T / fDF ), -( fDF + 1 ) / 2 ) / ( sqrt( fDF ) * GetBeta( 0 .5 , fDF / 2 .0 ) );
case 4 : // left-tailed T-distribution (cumulative distribution function)
double X = fDF / ( T * T + fDF );
double R = 0 .5 * GetBetaDist( X, 0 .5 * fDF, 0 .5 );
return ( T < 0 ? R : 1 - R );
}
SetError( FormulaError::IllegalArgument );
return HUGE_VAL;
}
// for LEGACY.CHIDIST, returns right tail, fDF=degrees of freedom
/** You must ensure fDF>0.0 */
double ScInterpreter::GetChiDist(double fX, double fDF)
{
if (fX <= 0 .0 )
return 1 .0 ; // see ODFF
else
return GetUpRegIGamma( fDF/2 .0 , fX/2 .0 );
}
// ready for ODF 1.2
// for ODF CHISQDIST; cumulative distribution function, fDF=degrees of freedom
// returns left tail
/** You must ensure fDF>0.0 */
double ScInterpreter::GetChiSqDistCDF(double fX, double fDF)
{
if (fX <= 0 .0 )
return 0 .0 ; // see ODFF
else
return GetLowRegIGamma( fDF/2 .0 , fX/2 .0 );
}
double ScInterpreter::GetChiSqDistPDF(double fX, double fDF)
{
// you must ensure fDF is positive integer
double fValue;
if (fX <= 0 .0 )
return 0 .0 ; // see ODFF
if (fDF*fX > 1391000 .0 )
{
// intermediate invalid values, use log
fValue = exp((0 .5 *fDF - 1 ) * log(fX*0 .5 ) - 0 .5 * fX - log(2 .0 ) - GetLogGamma(0 .5 *fDF));
}
else // fDF is small in most cases, we can iterate
{
double fCount;
if (fmod(fDF,2 .0 )<0 .5 )
{
// even
fValue = 0 .5 ;
fCount = 2 .0 ;
}
else
{
fValue = 1 /sqrt(fX*2 *M_PI);
fCount = 1 .0 ;
}
while ( fCount < fDF)
{
fValue *= (fX / fCount);
fCount += 2 .0 ;
}
if (fX>=1425 .0 ) // underflow in e^(-x/2)
fValue = exp(log(fValue)-fX/2 );
else
fValue *= exp(-fX/2 );
}
return fValue;
}
void ScInterpreter::ScChiSqDist()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 2 , 3 ) )
return ;
bool bCumulative;
if (nParamCount == 3 )
bCumulative = GetBool();
else
bCumulative = true ;
double fDF = ::rtl::math::approxFloor(GetDouble());
if (fDF < 1 .0 )
PushIllegalArgument();
else
{
double fX = GetDouble();
if (bCumulative)
PushDouble(GetChiSqDistCDF(fX,fDF));
else
PushDouble(GetChiSqDistPDF(fX,fDF));
}
}
void ScInterpreter::ScChiSqDist_MS()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 3 , 3 ) )
return ;
bool bCumulative = GetBool();
double fDF = ::rtl::math::approxFloor( GetDouble() );
if ( fDF < 1 .0 || fDF > 1 E10 )
PushIllegalArgument();
else
{
double fX = GetDouble();
if ( fX < 0 )
PushIllegalArgument();
else
{
if ( bCumulative )
PushDouble( GetChiSqDistCDF( fX, fDF ) );
else
PushDouble( GetChiSqDistPDF( fX, fDF ) );
}
}
}
void ScInterpreter::ScGamma()
{
double x = GetDouble();
if (x <= 0 .0 && x == ::rtl::math::approxFloor(x))
PushIllegalArgument();
else
{
double fResult = GetGamma(x);
if (nGlobalError != FormulaError::NONE)
{
PushError( nGlobalError);
return ;
}
PushDouble(fResult);
}
}
void ScInterpreter::ScLogGamma()
{
double x = GetDouble();
if (x > 0 .0 ) // constraint from ODFF
PushDouble( GetLogGamma(x));
else
PushIllegalArgument();
}
double ScInterpreter::GetBeta(double fAlpha, double fBeta)
{
double fA;
double fB;
if (fAlpha > fBeta)
{
fA = fAlpha; fB = fBeta;
}
else
{
fA = fBeta; fB = fAlpha;
}
if (fA+fB < fMaxGammaArgument) // simple case
return GetGamma(fA)/GetGamma(fA+fB)*GetGamma(fB);
// need logarithm
// GetLogGamma is not accurate enough, back to Lanczos for all three
// GetGamma and arrange factors newly.
const double fg = 6 .024680040776729583740234375 ; //see GetGamma
double fgm = fg - 0 .5 ;
double fLanczos = lcl_getLanczosSum(fA);
fLanczos /= lcl_getLanczosSum(fA+fB);
fLanczos *= lcl_getLanczosSum(fB);
double fABgm = fA+fB+fgm;
fLanczos *= sqrt((fABgm/(fA+fgm))/(fB+fgm));
double fTempA = fB/(fA+fgm); // (fA+fgm)/fABgm = 1 / ( 1 + fB/(fA+fgm))
double fTempB = fA/(fB+fgm);
double fResult = exp(-fA * std::log1p(fTempA)
-fB * std::log1p(fTempB)-fgm);
fResult *= fLanczos;
return fResult;
}
// Same as GetBeta but with logarithm
double ScInterpreter::GetLogBeta(double fAlpha, double fBeta)
{
double fA;
double fB;
if (fAlpha > fBeta)
{
fA = fAlpha; fB = fBeta;
}
else
{
fA = fBeta; fB = fAlpha;
}
const double fg = 6 .024680040776729583740234375 ; //see GetGamma
double fgm = fg - 0 .5 ;
double fLanczos = lcl_getLanczosSum(fA);
fLanczos /= lcl_getLanczosSum(fA+fB);
fLanczos *= lcl_getLanczosSum(fB);
double fLogLanczos = log(fLanczos);
double fABgm = fA+fB+fgm;
fLogLanczos += 0 .5 *(log(fABgm)-log(fA+fgm)-log(fB+fgm));
double fTempA = fB/(fA+fgm); // (fA+fgm)/fABgm = 1 / ( 1 + fB/(fA+fgm))
double fTempB = fA/(fB+fgm);
double fResult = -fA * std::log1p(fTempA)
-fB * std::log1p(fTempB)-fgm;
fResult += fLogLanczos;
return fResult;
}
// beta distribution probability density function
double ScInterpreter::GetBetaDistPDF(double fX, double fA, double fB)
{
// special cases
if (fA == 1 .0 ) // result b*(1-x)^(b-1)
{
if (fB == 1 .0 )
return 1 .0 ;
if (fB == 2 .0 )
return -2 .0 *fX + 2 .0 ;
if (fX == 1 .0 && fB < 1 .0 )
{
SetError(FormulaError::IllegalArgument);
return HUGE_VAL;
}
if (fX <= 0 .01 )
return fB + fB * std::expm1((fB-1 .0 ) * std::log1p(-fX));
else
return fB * pow(0 .5 -fX+0 .5 ,fB-1 .0 );
}
if (fB == 1 .0 ) // result a*x^(a-1)
{
if (fA == 2 .0 )
return fA * fX;
if (fX == 0 .0 && fA < 1 .0 )
{
SetError(FormulaError::IllegalArgument);
return HUGE_VAL;
}
return fA * pow(fX,fA-1 );
}
if (fX <= 0 .0 )
{
if (fA < 1 .0 && fX == 0 .0 )
{
SetError(FormulaError::IllegalArgument);
return HUGE_VAL;
}
else
return 0 .0 ;
}
if (fX >= 1 .0 )
{
if (fB < 1 .0 && fX == 1 .0 )
{
SetError(FormulaError::IllegalArgument);
return HUGE_VAL;
}
else
return 0 .0 ;
}
// normal cases; result x^(a-1)*(1-x)^(b-1)/Beta(a,b)
const double fLogDblMax = log( ::std::numeric_limits<double >::max());
const double fLogDblMin = log( ::std::numeric_limits<double >::min());
double fLogY = (fX < 0 .1 ) ? std::log1p(-fX) : log(0 .5 -fX+0 .5 );
double fLogX = log(fX);
double fAm1LogX = (fA-1 .0 ) * fLogX;
double fBm1LogY = (fB-1 .0 ) * fLogY;
double fLogBeta = GetLogBeta(fA,fB);
// check whether parts over- or underflow
if ( fAm1LogX < fLogDblMax && fAm1LogX > fLogDblMin
&& fBm1LogY < fLogDblMax && fBm1LogY > fLogDblMin
&& fLogBeta < fLogDblMax && fLogBeta > fLogDblMin
&& fAm1LogX + fBm1LogY < fLogDblMax && fAm1LogX + fBm1LogY > fLogDblMin)
return pow(fX,fA-1 .0 ) * pow(0 .5 -fX+0 .5 ,fB-1 .0 ) / GetBeta(fA,fB);
else // need logarithm;
// might overflow as a whole, but seldom, not worth to pre-detect it
return exp( fAm1LogX + fBm1LogY - fLogBeta);
}
/*
x ^ a * ( 1 - x ) ^ b
I_x ( a , b ) = - - - - - - - - - - - - - - - - * result of ContFrac
a * Beta ( a , b )
*/
static double lcl_GetBetaHelperContFrac(double fX, double fA, double fB)
{ // like old version
double a1, b1, a2, b2, fnorm, cfnew, cf;
a1 = 1 .0 ; b1 = 1 .0 ;
b2 = 1 .0 - (fA+fB)/(fA+1 .0 )*fX;
if (b2 == 0 .0 )
{
a2 = 0 .0 ;
fnorm = 1 .0 ;
cf = 1 .0 ;
}
else
{
a2 = 1 .0 ;
fnorm = 1 .0 /b2;
cf = a2*fnorm;
}
cfnew = 1 .0 ;
double rm = 1 .0 ;
const double fMaxIter = 50000 .0 ;
// loop security, normal cases converge in less than 100 iterations.
// FIXME: You will get so much iterations for fX near mean,
// I do not know a better algorithm.
bool bfinished = false ;
do
{
const double apl2m = fA + 2 .0 *rm;
const double d2m = rm*(fB-rm)*fX/((apl2m-1 .0 )*apl2m);
const double d2m1 = -(fA+rm)*(fA+fB+rm)*fX/(apl2m*(apl2m+1 .0 ));
a1 = (a2+d2m*a1)*fnorm;
b1 = (b2+d2m*b1)*fnorm;
a2 = a1 + d2m1*a2*fnorm;
b2 = b1 + d2m1*b2*fnorm;
if (b2 != 0 .0 )
{
fnorm = 1 .0 /b2;
cfnew = a2*fnorm;
bfinished = (std::abs(cf-cfnew) < std::abs(cf)*fMachEps);
}
cf = cfnew;
rm += 1 .0 ;
}
while (rm < fMaxIter && !bfinished);
return cf;
}
// cumulative distribution function, normalized
double ScInterpreter::GetBetaDist(double fXin, double fAlpha, double fBeta)
{
// special cases
if (fXin <= 0 .0 ) // values are valid, see spec
return 0 .0 ;
if (fXin >= 1 .0 ) // values are valid, see spec
return 1 .0 ;
if (fBeta == 1 .0 )
return pow(fXin, fAlpha);
if (fAlpha == 1 .0 )
// 1.0 - pow(1.0-fX,fBeta) is not accurate enough
return -std::expm1(fBeta * std::log1p(-fXin));
//FIXME: need special algorithm for fX near fP for large fA,fB
double fResult;
// I use always continued fraction, power series are neither
// faster nor more accurate.
double fY = (0 .5 -fXin)+0 .5 ;
double flnY = std::log1p(-fXin);
double fX = fXin;
double flnX = log(fXin);
double fA = fAlpha;
double fB = fBeta;
bool bReflect = fXin > fAlpha/(fAlpha+fBeta);
if (bReflect)
{
fA = fBeta;
fB = fAlpha;
fX = fY;
fY = fXin;
flnX = flnY;
flnY = log(fXin);
}
fResult = lcl_GetBetaHelperContFrac(fX,fA,fB);
fResult = fResult/fA;
double fP = fA/(fA+fB);
double fQ = fB/(fA+fB);
double fTemp;
if (fA > 1 .0 && fB > 1 .0 && fP < 0 .97 && fQ < 0 .97 ) //found experimental
fTemp = GetBetaDistPDF(fX,fA,fB)*fX*fY;
else
fTemp = exp(fA*flnX + fB*flnY - GetLogBeta(fA,fB));
fResult *= fTemp;
if (bReflect)
fResult = 0 .5 - fResult + 0 .5 ;
if (fResult > 1 .0 ) // ensure valid range
fResult = 1 .0 ;
if (fResult < 0 .0 )
fResult = 0 .0 ;
return fResult;
}
void ScInterpreter::ScBetaDist()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 3 , 6 ) ) // expanded, see #i91547#
return ;
double fLowerBound, fUpperBound;
double alpha, beta, x;
bool bIsCumulative;
if (nParamCount == 6 )
bIsCumulative = GetBool();
else
bIsCumulative = true ;
if (nParamCount >= 5 )
fUpperBound = GetDouble();
else
fUpperBound = 1 .0 ;
if (nParamCount >= 4 )
fLowerBound = GetDouble();
else
fLowerBound = 0 .0 ;
beta = GetDouble();
alpha = GetDouble();
x = GetDouble();
double fScale = fUpperBound - fLowerBound;
if (fScale <= 0 .0 || alpha <= 0 .0 || beta <= 0 .0 )
{
PushIllegalArgument();
return ;
}
if (bIsCumulative) // cumulative distribution function
{
// special cases
if (x < fLowerBound)
{
PushDouble(0 .0 ); return ; //see spec
}
if (x > fUpperBound)
{
PushDouble(1 .0 ); return ; //see spec
}
// normal cases
x = (x-fLowerBound)/fScale; // convert to standard form
PushDouble(GetBetaDist(x, alpha, beta));
return ;
}
else // probability density function
{
if (x < fLowerBound || x > fUpperBound)
{
PushDouble(0 .0 );
return ;
}
x = (x-fLowerBound)/fScale;
PushDouble(GetBetaDistPDF(x, alpha, beta)/fScale);
return ;
}
}
/**
Microsoft version has parameters in different order
Also , upper and lowerbound are optional and have default values
and different constraints apply .
Basically , function is identical with ScInterpreter : : ScBetaDist ( )
*/
void ScInterpreter::ScBetaDist_MS()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 4 , 6 ) )
return ;
double fLowerBound, fUpperBound;
double alpha, beta, x;
bool bIsCumulative;
if (nParamCount == 6 )
fUpperBound = GetDouble();
else
fUpperBound = 1 .0 ;
if (nParamCount >= 5 )
fLowerBound = GetDouble();
else
fLowerBound = 0 .0 ;
bIsCumulative = GetBool();
beta = GetDouble();
alpha = GetDouble();
x = GetDouble();
if (alpha <= 0 .0 || beta <= 0 .0 || x < fLowerBound || x > fUpperBound)
{
PushIllegalArgument();
return ;
}
double fScale = fUpperBound - fLowerBound;
if (bIsCumulative) // cumulative distribution function
{
x = (x-fLowerBound)/fScale; // convert to standard form
PushDouble(GetBetaDist(x, alpha, beta));
return ;
}
else // probability density function
{
x = (x-fLowerBound)/fScale;
PushDouble(GetBetaDistPDF(x, alpha, beta)/fScale);
return ;
}
}
void ScInterpreter::ScPhi()
{
PushDouble(phi(GetDouble()));
}
void ScInterpreter::ScGauss()
{
PushDouble(gauss(GetDouble()));
}
void ScInterpreter::ScFisher()
{
double fVal = GetDouble();
if (std::abs(fVal) >= 1 .0 )
PushIllegalArgument();
else
PushDouble(::atanh(fVal));
}
void ScInterpreter::ScFisherInv()
{
PushDouble( tanh( GetDouble()));
}
void ScInterpreter::ScFact()
{
double nVal = GetDouble();
if (nVal < 0 .0 )
PushIllegalArgument();
else
PushDouble(Fakultaet(nVal));
}
void ScInterpreter::ScCombin()
{
if ( MustHaveParamCount( GetByte(), 2 ) )
{
double k = ::rtl::math::approxFloor(GetDouble());
double n = ::rtl::math::approxFloor(GetDouble());
if (k < 0 .0 || n < 0 .0 || k > n)
PushIllegalArgument();
else
PushDouble(BinomCoeff(n, k));
}
}
void ScInterpreter::ScCombinA()
{
if ( MustHaveParamCount( GetByte(), 2 ) )
{
double k = ::rtl::math::approxFloor(GetDouble());
double n = ::rtl::math::approxFloor(GetDouble());
if (k < 0 .0 || n < 0 .0 || k > n)
PushIllegalArgument();
else
PushDouble(BinomCoeff(n + k - 1 , k));
}
}
void ScInterpreter::ScPermut()
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
double k = ::rtl::math::approxFloor(GetDouble());
double n = ::rtl::math::approxFloor(GetDouble());
if (n < 0 .0 || k < 0 .0 || k > n)
PushIllegalArgument();
else if (k == 0 .0 )
PushInt(1 ); // (n! / (n - 0)!) == 1
else
{
double nVal = n;
for (sal_uLong i = static_cast <sal_uLong>(k)-1 ; i >= 1 ; i--)
nVal *= n-static_cast <double >(i);
PushDouble(nVal);
}
}
void ScInterpreter::ScPermutationA()
{
if ( MustHaveParamCount( GetByte(), 2 ) )
{
double k = ::rtl::math::approxFloor(GetDouble());
double n = ::rtl::math::approxFloor(GetDouble());
if (n < 0 .0 || k < 0 .0 )
PushIllegalArgument();
else
PushDouble(pow(n,k));
}
}
double ScInterpreter::GetBinomDistPMF(double x, double n, double p)
// used in ScB and ScBinomDist
// preconditions: 0.0 <= x <= n, 0.0 < p < 1.0; x,n integral although double
{
double q = (0 .5 - p) + 0 .5 ;
double fFactor = pow(q, n);
if (fFactor <=::std::numeric_limits<double >::min())
{
fFactor = pow(p, n);
if (fFactor <= ::std::numeric_limits<double >::min())
return GetBetaDistPDF(p, x+1 .0 , n-x+1 .0 )/(n+1 .0 );
else
{
sal_uInt32 max = static_cast <sal_uInt32>(n - x);
for (sal_uInt32 i = 0 ; i < max && fFactor > 0 .0 ; i++)
fFactor *= (n-i)/(i+1 )*q/p;
return fFactor;
}
}
else
{
sal_uInt32 max = static_cast <sal_uInt32>(x);
for (sal_uInt32 i = 0 ; i < max && fFactor > 0 .0 ; i++)
fFactor *= (n-i)/(i+1 )*p/q;
return fFactor;
}
}
static double lcl_GetBinomDistRange(double n, double xs,double xe,
double fFactor /* q^n */, double p, double q)
//preconditions: 0.0 <= xs < xe <= n; xs,xe,n integral although double
{
sal_uInt32 i;
// skip summands index 0 to xs-1, start sum with index xs
sal_uInt32 nXs = static_cast <sal_uInt32>( xs );
for (i = 1 ; i <= nXs && fFactor > 0 .0 ; i++)
fFactor *= (n-i+1 )/i * p/q;
KahanSum fSum = fFactor; // Summand xs
sal_uInt32 nXe = static_cast <sal_uInt32>(xe);
for (i = nXs+1 ; i <= nXe && fFactor > 0 .0 ; i++)
{
fFactor *= (n-i+1 )/i * p/q;
fSum += fFactor;
}
return std::min(fSum.get(), 1 .0 );
}
void ScInterpreter::ScB()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 3 , 4 ) )
return ;
if (nParamCount == 3 ) // mass function
{
double x = ::rtl::math::approxFloor(GetDouble());
double p = GetDouble();
double n = ::rtl::math::approxFloor(GetDouble());
if (n < 0 .0 || x < 0 .0 || x > n || p < 0 .0 || p > 1 .0 )
PushIllegalArgument();
else if (p == 0 .0 )
PushDouble( (x == 0 .0 ) ? 1 .0 : 0 .0 );
else if ( p == 1 .0 )
PushDouble( (x == n) ? 1 .0 : 0 .0 );
else
PushDouble(GetBinomDistPMF(x,n,p));
}
else
{ // nParamCount == 4
double xe = ::rtl::math::approxFloor(GetDouble());
double xs = ::rtl::math::approxFloor(GetDouble());
double p = GetDouble();
double n = ::rtl::math::approxFloor(GetDouble());
double q = (0 .5 - p) + 0 .5 ;
bool bIsValidX = ( 0 .0 <= xs && xs <= xe && xe <= n);
if ( bIsValidX && 0 .0 < p && p < 1 .0 )
{
if (xs == xe) // mass function
PushDouble(GetBinomDistPMF(xs,n,p));
else
{
double fFactor = pow(q, n);
if (fFactor > ::std::numeric_limits<double >::min())
PushDouble(lcl_GetBinomDistRange(n,xs,xe,fFactor,p,q));
else
{
fFactor = pow(p, n);
if (fFactor > ::std::numeric_limits<double >::min())
{
// sum from j=xs to xe {(n choose j) * p^j * q^(n-j)}
// = sum from i = n-xe to n-xs { (n choose i) * q^i * p^(n-i)}
PushDouble(lcl_GetBinomDistRange(n,n-xe,n-xs,fFactor,q,p));
}
else
PushDouble(GetBetaDist(q,n-xe,xe+1 .0 )-GetBetaDist(q,n-xs+1 ,xs) );
}
}
}
else
{
if ( bIsValidX ) // not(0<p<1)
{
if ( p == 0 .0 )
PushDouble( (xs == 0 .0 ) ? 1 .0 : 0 .0 );
else if ( p == 1 .0 )
PushDouble( (xe == n) ? 1 .0 : 0 .0 );
else
PushIllegalArgument();
}
else
PushIllegalArgument();
}
}
}
void ScInterpreter::ScBinomDist()
{
if ( !MustHaveParamCount( GetByte(), 4 ) )
return ;
bool bIsCum = GetBool(); // false=mass function; true=cumulative
double p = GetDouble();
double n = ::rtl::math::approxFloor(GetDouble());
double x = ::rtl::math::approxFloor(GetDouble());
double q = (0 .5 - p) + 0 .5 ; // get one bit more for p near 1.0
if (n < 0 .0 || x < 0 .0 || x > n || p < 0 .0 || p > 1 .0 )
{
PushIllegalArgument();
return ;
}
if ( p == 0 .0 )
{
PushDouble( (x==0 .0 || bIsCum) ? 1 .0 : 0 .0 );
return ;
}
if ( p == 1 .0 )
{
PushDouble( (x==n) ? 1 .0 : 0 .0 );
return ;
}
if (!bIsCum)
PushDouble( GetBinomDistPMF(x,n,p));
else
{
if (x == n)
PushDouble(1 .0 );
else
{
double fFactor = pow(q, n);
if (x == 0 .0 )
PushDouble(fFactor);
else if (fFactor <= ::std::numeric_limits<double >::min())
{
fFactor = pow(p, n);
if (fFactor <= ::std::numeric_limits<double >::min())
PushDouble(GetBetaDist(q,n-x,x+1 .0 ));
else
{
if (fFactor > fMachEps)
{
double fSum = 1 .0 - fFactor;
sal_uInt32 max = static_cast <sal_uInt32> (n - x) - 1 ;
for (sal_uInt32 i = 0 ; i < max && fFactor > 0 .0 ; i++)
{
fFactor *= (n-i)/(i+1 )*q/p;
fSum -= fFactor;
}
PushDouble( (fSum < 0 .0 ) ? 0 .0 : fSum );
}
else
PushDouble(lcl_GetBinomDistRange(n,n-x,n,fFactor,q,p));
}
}
else
PushDouble( lcl_GetBinomDistRange(n,0 .0 ,x,fFactor,p,q)) ;
}
}
}
void ScInterpreter::ScCritBinom()
{
if ( !MustHaveParamCount( GetByte(), 3 ) )
return ;
double alpha = GetDouble();
double p = GetDouble();
double n = ::rtl::math::approxFloor(GetDouble());
if (n < 0 .0 || alpha < 0 .0 || alpha > 1 .0 || p < 0 .0 || p > 1 .0 )
PushIllegalArgument();
else if ( alpha == 0 .0 )
PushDouble( 0 .0 );
else if ( alpha == 1 .0 )
PushDouble( p == 0 ? 0 .0 : n );
else
{
double fFactor;
double q = (0 .5 - p) + 0 .5 ; // get one bit more for p near 1.0
if ( q > p ) // work from the side where the cumulative curve is
{
// work from 0 upwards
fFactor = pow(q,n);
if (fFactor > ::std::numeric_limits<double >::min())
{
KahanSum fSum = fFactor;
sal_uInt32 max = static_cast <sal_uInt32> (n), i;
for (i = 0 ; i < max && fSum < alpha; i++)
{
fFactor *= (n-i)/(i+1 )*p/q;
fSum += fFactor;
}
PushDouble(i);
}
else
{
// accumulate BinomDist until accumulated BinomDist reaches alpha
KahanSum fSum = 0 .0 ;
sal_uInt32 max = static_cast <sal_uInt32> (n), i;
for (i = 0 ; i < max && fSum < alpha; i++)
{
const double x = GetBetaDistPDF( p, ( i + 1 ), ( n - i + 1 ) )/( n + 1 );
if ( nGlobalError == FormulaError::NONE )
fSum += x;
else
{
PushNoValue();
return ;
}
}
assert(i > 0 && "coverity 2023.12.2" );
PushDouble( i - 1 );
}
}
else
{
// work from n backwards
fFactor = pow(p, n);
if (fFactor > ::std::numeric_limits<double >::min())
{
KahanSum fSum = 1 .0 - fFactor;
sal_uInt32 max = static_cast <sal_uInt32> (n), i;
for (i = 0 ; i < max && fSum >= alpha; i++)
{
fFactor *= (n-i)/(i+1 )*q/p;
fSum -= fFactor;
}
PushDouble(n-i);
}
else
{
// accumulate BinomDist until accumulated BinomDist reaches alpha
KahanSum fSum = 0 .0 ;
sal_uInt32 max = static_cast <sal_uInt32> (n), i;
alpha = 1 - alpha;
for (i = 0 ; i < max && fSum < alpha; i++)
{
const double x = GetBetaDistPDF( q, ( i + 1 ), ( n - i + 1 ) )/( n + 1 );
if ( nGlobalError == FormulaError::NONE )
fSum += x;
else
{
PushNoValue();
return ;
}
}
PushDouble( n - i + 1 );
}
}
}
}
void ScInterpreter::ScNegBinomDist()
{
if ( !MustHaveParamCount( GetByte(), 3 ) )
return ;
double p = GetDouble(); // probability
double s = ::rtl::math::approxFloor(GetDouble()); // No of successes
double f = ::rtl::math::approxFloor(GetDouble()); // No of failures
if ((f + s) <= 1 .0 || p < 0 .0 || p > 1 .0 )
PushIllegalArgument();
else
{
double q = 1 .0 - p;
double fFactor = pow(p,s);
for (double i = 0 .0 ; i < f; i++)
fFactor *= (i+s)/(i+1 .0 )*q;
PushDouble(fFactor);
}
}
void ScInterpreter::ScNegBinomDist_MS()
{
if ( !MustHaveParamCount( GetByte(), 4 ) )
return ;
bool bCumulative = GetBool();
double p = GetDouble(); // probability
double s = ::rtl::math::approxFloor(GetDouble()); // No of successes
double f = ::rtl::math::approxFloor(GetDouble()); // No of failures
if ( s < 1 .0 || f < 0 .0 || p < 0 .0 || p > 1 .0 )
PushIllegalArgument();
else
{
double q = 1 .0 - p;
if ( bCumulative )
PushDouble( 1 .0 - GetBetaDist( q, f + 1 , s ) );
else
{
double fFactor = pow( p, s );
for ( double i = 0 .0 ; i < f; i++ )
fFactor *= ( i + s ) / ( i + 1 .0 ) * q;
PushDouble( fFactor );
}
}
}
void ScInterpreter::ScNormDist( int nMinParamCount )
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, nMinParamCount, 4 ) )
return ;
bool bCumulative = nParamCount != 4 || GetBool();
double sigma = GetDouble(); // standard deviation
double mue = GetDouble(); // mean
double x = GetDouble(); // x
if (sigma <= 0 .0 )
{
PushIllegalArgument();
return ;
}
if (bCumulative)
PushDouble(integralPhi((x-mue)/sigma));
else
PushDouble(phi((x-mue)/sigma)/sigma);
}
void ScInterpreter::ScLogNormDist( int nMinParamCount ) //expanded, see #i100119# and fdo72158
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, nMinParamCount, 4 ) )
return ;
bool bCumulative = nParamCount != 4 || GetBool(); // cumulative
double sigma = nParamCount >= 3 ? GetDouble() : 1 .0 ; // standard deviation
double mue = nParamCount >= 2 ? GetDouble() : 0 .0 ; // mean
double x = GetDouble(); // x
if (sigma <= 0 .0 )
{
PushIllegalArgument();
return ;
}
if (bCumulative)
{ // cumulative
if (x <= 0 .0 )
PushDouble(0 .0 );
else
PushDouble(integralPhi((log(x)-mue)/sigma));
}
else
{ // density
if (x <= 0 .0 )
PushIllegalArgument();
else
PushDouble(phi((log(x)-mue)/sigma)/sigma/x);
}
}
void ScInterpreter::ScStdNormDist()
{
PushDouble(integralPhi(GetDouble()));
}
void ScInterpreter::ScStdNormDist_MS()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 2 ) )
return ;
bool bCumulative = GetBool(); // cumulative
double x = GetDouble(); // x
if ( bCumulative )
PushDouble( integralPhi( x ) );
else
PushDouble( exp( - pow( x, 2 ) / 2 ) / sqrt( 2 * M_PI ) );
}
void ScInterpreter::ScExpDist()
{
if ( !MustHaveParamCount( GetByte(), 3 ) )
return ;
double kum = GetDouble(); // 0 or 1
double lambda = GetDouble(); // lambda
double x = GetDouble(); // x
if (lambda <= 0 .0 )
PushIllegalArgument();
else if (kum == 0 .0 ) // density
{
if (x >= 0 .0 )
PushDouble(lambda * exp(-lambda*x));
else
PushInt(0 );
}
else // distribution
{
if (x > 0 .0 )
PushDouble(1 .0 - exp(-lambda*x));
else
PushInt(0 );
}
}
void ScInterpreter::ScTDist()
{
if ( !MustHaveParamCount( GetByte(), 3 ) )
return ;
double fFlag = ::rtl::math::approxFloor(GetDouble());
double fDF = ::rtl::math::approxFloor(GetDouble());
double T = GetDouble();
if (fDF < 1 .0 || T < 0 .0 || (fFlag != 1 .0 && fFlag != 2 .0 ) )
{
PushIllegalArgument();
return ;
}
PushDouble( GetTDist( T, fDF, static_cast <int >(fFlag) ) );
}
void ScInterpreter::ScTDist_T( int nTails )
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
double fDF = ::rtl::math::approxFloor( GetDouble() );
double fT = GetDouble();
if ( fDF < 1 .0 || ( nTails == 2 && fT < 0 .0 ) )
{
PushIllegalArgument();
return ;
}
double fRes = GetTDist( fT, fDF, nTails );
if ( nTails == 1 && fT < 0 .0 )
PushDouble( 1 .0 - fRes ); // tdf#105937, right tail, negative X
else
PushDouble( fRes );
}
void ScInterpreter::ScTDist_MS()
{
if ( !MustHaveParamCount( GetByte(), 3 ) )
return ;
bool bCumulative = GetBool();
double fDF = ::rtl::math::approxFloor( GetDouble() );
double T = GetDouble();
if ( fDF < 1 .0 )
{
PushIllegalArgument();
return ;
}
PushDouble( GetTDist( T, fDF, ( bCumulative ? 4 : 3 ) ) );
}
void ScInterpreter::ScFDist()
{
if ( !MustHaveParamCount( GetByte(), 3 ) )
return ;
double fF2 = ::rtl::math::approxFloor(GetDouble());
double fF1 = ::rtl::math::approxFloor(GetDouble());
double fF = GetDouble();
if (fF < 0 .0 || fF1 < 1 .0 || fF2 < 1 .0 || fF1 >= 1 .0 E10 || fF2 >= 1 .0 E10)
{
PushIllegalArgument();
return ;
}
PushDouble(GetFDist(fF, fF1, fF2));
}
void ScInterpreter::ScFDist_LT()
{
int nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 3 , 4 ) )
return ;
bool bCum;
if ( nParamCount == 3 )
bCum = true ;
else if ( IsMissing() )
{
bCum = true ;
Pop();
}
else
bCum = GetBool();
double fF2 = ::rtl::math::approxFloor( GetDouble() );
double fF1 = ::rtl::math::approxFloor( GetDouble() );
double fF = GetDouble();
if ( fF < 0 .0 || fF1 < 1 .0 || fF2 < 1 .0 || fF1 >= 1 .0 E10 || fF2 >= 1 .0 E10 )
{
PushIllegalArgument();
return ;
}
if ( bCum )
{
// left tail cumulative distribution
PushDouble( 1 .0 - GetFDist( fF, fF1, fF2 ) );
}
else
{
// probability density function
PushDouble( pow( fF1 / fF2, fF1 / 2 ) * pow( fF, ( fF1 / 2 ) - 1 ) /
( pow( ( 1 + ( fF * fF1 / fF2 ) ), ( fF1 + fF2 ) / 2 ) *
GetBeta( fF1 / 2 , fF2 / 2 ) ) );
}
}
void ScInterpreter::ScChiDist( bool bODFF )
{
double fResult;
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
double fDF = ::rtl::math::approxFloor(GetDouble());
double fChi = GetDouble();
if ( fDF < 1 .0 // x<=0 returns 1, see ODFF1.2 6.18.11
|| ( !bODFF && fChi < 0 ) ) // Excel does not accept negative fChi
{
PushIllegalArgument();
return ;
}
fResult = GetChiDist( fChi, fDF);
if (nGlobalError != FormulaError::NONE)
{
PushError( nGlobalError);
return ;
}
PushDouble(fResult);
}
void ScInterpreter::ScWeibull()
{
if ( !MustHaveParamCount( GetByte(), 4 ) )
return ;
double kum = GetDouble(); // 0 or 1
double beta = GetDouble(); // beta
double alpha = GetDouble(); // alpha
double x = GetDouble(); // x
if (alpha <= 0 .0 || beta <= 0 .0 || x < 0 .0 )
PushIllegalArgument();
else if (kum == 0 .0 ) // Density
PushDouble(alpha/pow(beta,alpha)*pow(x,alpha-1 .0 )*
exp(-pow(x/beta,alpha)));
else // Distribution
PushDouble(1 .0 - exp(-pow(x/beta,alpha)));
}
void ScInterpreter::ScPoissonDist( bool bODFF )
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, ( bODFF ? 2 : 3 ), 3 ) )
return ;
bool bCumulative = nParamCount != 3 || GetBool(); // default cumulative
double lambda = GetDouble(); // Mean
double x = ::rtl::math::approxFloor(GetDouble()); // discrete distribution
if (lambda <= 0 .0 || x < 0 .0 )
PushIllegalArgument();
else if (!bCumulative) // Probability mass function
{
if (lambda >712 .0 ) // underflow in exp(-lambda)
{ // accuracy 11 Digits
PushDouble( exp(x*log(lambda)-lambda-GetLogGamma(x+1 .0 )));
}
else
{
double fPoissonVar = 1 .0 ;
for ( double f = 0 .0 ; f < x; ++f )
fPoissonVar *= lambda / ( f + 1 .0 );
PushDouble( fPoissonVar * exp( -lambda ) );
}
}
else // Cumulative distribution function
{
if (lambda > 712 .0 ) // underflow in exp(-lambda)
{ // accuracy 12 Digits
PushDouble(GetUpRegIGamma(x+1 .0 ,lambda));
}
else
{
if (x >= 936 .0 ) // result is always indistinguishable from 1
PushDouble (1 .0 );
else
{
double fSummand = std::exp(-lambda);
KahanSum fSum = fSummand;
int nEnd = sal::static_int_cast<int >( x );
for (int i = 1 ; i <= nEnd; i++)
{
fSummand = (fSummand * lambda)/static_cast <double >(i);
fSum += fSummand;
}
PushDouble(fSum.get());
}
}
}
}
/** Local function used in the calculation of the hypergeometric distribution.
*/
static void lcl_PutFactorialElements( ::std::vector< double >& cn, double fLower, double fUpper, double fBase )
{
for ( double i = fLower; i <= fUpper; ++i )
{
double fVal = fBase - i;
if ( fVal > 1 .0 )
cn.push_back( fVal );
}
}
/** Calculates a value of the hypergeometric distribution.
@ see # i47296 #
This function has an extra argument bCumulative ,
which only calculates the non - cumulative distribution and
which is optional in Calc and mandatory with Excel ' s HYPGEOM . DIST ( )
@ see fdo # 71722
@ see tdf # 102948 , make Calc function ODFF1 . 2 - compliant
@ see tdf # 117041 , implement note at bottom of ODFF1 . 2 par . 6 . 18 . 37
*/
void ScInterpreter::ScHypGeomDist( int nMinParamCount )
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, nMinParamCount, 5 ) )
return ;
bool bCumulative = ( nParamCount == 5 && GetBool() );
double N = ::rtl::math::approxFloor(GetDouble());
double M = ::rtl::math::approxFloor(GetDouble());
double n = ::rtl::math::approxFloor(GetDouble());
double x = ::rtl::math::approxFloor(GetDouble());
if ( (x < 0 .0 ) || (n < x) || (N < n) || (N < M) || (M < 0 .0 ) )
{
PushIllegalArgument();
return ;
}
KahanSum fVal = 0 .0 ;
for ( int i = ( bCumulative ? 0 : x ); i <= x && nGlobalError == FormulaError::NONE; i++ )
{
if ( (n - i <= N - M) && (i <= M) )
fVal += GetHypGeomDist( i, n, M, N );
}
PushDouble( fVal.get() );
}
/** Calculates a value of the hypergeometric distribution.
The algorithm is designed to avoid unnecessary multiplications and division
by expanding all factorial elements ( 9 of them ) . It is done by excluding
those ranges that overlap in the numerator and the denominator . This allows
for a fast calculation for large values which would otherwise cause an overflow
in the intermediate values .
@ see # i47296 #
*/
double ScInterpreter::GetHypGeomDist( double x, double n, double M, double N )
{
const size_t nMaxArraySize = 500000 ; // arbitrary max array size
std::vector<double > cnNumer, cnDenom;
size_t nEstContainerSize = static_cast <size_t>( x + ::std::min( n, M ) );
size_t nMaxSize = ::std::min( cnNumer.max_size(), nMaxArraySize );
if ( nEstContainerSize > nMaxSize )
{
PushNoValue();
return 0 ;
}
cnNumer.reserve( nEstContainerSize + 10 );
cnDenom.reserve( nEstContainerSize + 10 );
// Trim coefficient C first
double fCNumVarUpper = N - n - M + x - 1 .0 ;
double fCDenomVarLower = 1 .0 ;
if ( N - n - M + x >= M - x + 1 .0 )
{
fCNumVarUpper = M - x - 1 .0 ;
fCDenomVarLower = N - n - 2 .0 *(M - x) + 1 .0 ;
}
double fCNumLower = N - n - fCNumVarUpper;
double fCDenomUpper = N - n - M + x + 1 .0 - fCDenomVarLower;
double fDNumVarLower = n - M;
if ( n >= M + 1 .0 )
{
if ( N - M < n + 1 .0 )
{
// Case 1
if ( N - n < n + 1 .0 )
{
// no overlap
lcl_PutFactorialElements( cnNumer, 0 .0 , fCNumVarUpper, N - n );
lcl_PutFactorialElements( cnDenom, 0 .0 , N - n - 1 .0 , N );
}
else
{
// overlap
OSL_ENSURE( fCNumLower < n + 1 .0 , "ScHypGeomDist: wrong assertion" );
lcl_PutFactorialElements( cnNumer, N - 2 .0 *n, fCNumVarUpper, N - n );
lcl_PutFactorialElements( cnDenom, 0 .0 , n - 1 .0 , N );
}
OSL_ENSURE( fCDenomUpper <= N - M, "ScHypGeomDist: wrong assertion" );
if ( fCDenomUpper < n - x + 1 .0 )
// no overlap
lcl_PutFactorialElements( cnNumer, 1 .0 , N - M - n + x, N - M + 1 .0 );
else
{
// overlap
lcl_PutFactorialElements( cnNumer, 1 .0 , N - M - fCDenomUpper, N - M + 1 .0 );
fCDenomUpper = n - x;
fCDenomVarLower = N - M - 2 .0 *(n - x) + 1 .0 ;
}
}
else
{
// Case 2
if ( n > M - 1 .0 )
{
// no overlap
lcl_PutFactorialElements( cnNumer, 0 .0 , fCNumVarUpper, N - n );
lcl_PutFactorialElements( cnDenom, 0 .0 , M - 1 .0 , N );
}
else
{
lcl_PutFactorialElements( cnNumer, M - n, fCNumVarUpper, N - n );
lcl_PutFactorialElements( cnDenom, 0 .0 , n - 1 .0 , N );
}
OSL_ENSURE( fCDenomUpper <= n, "ScHypGeomDist: wrong assertion" );
if ( fCDenomUpper < n - x + 1 .0 )
// no overlap
lcl_PutFactorialElements( cnNumer, N - M - n + 1 .0 , N - M - n + x, N - M + 1 .0 );
else
{
lcl_PutFactorialElements( cnNumer, N - M - n + 1 .0 , N - M - fCDenomUpper, N - M + 1 .0 );
fCDenomUpper = n - x;
fCDenomVarLower = N - M - 2 .0 *(n - x) + 1 .0 ;
}
}
OSL_ENSURE( fCDenomUpper <= M, "ScHypGeomDist: wrong assertion" );
}
else
{
if ( N - M < M + 1 .0 )
{
// Case 3
if ( N - n < M + 1 .0 )
{
// No overlap
lcl_PutFactorialElements( cnNumer, 0 .0 , fCNumVarUpper, N - n );
lcl_PutFactorialElements( cnDenom, 0 .0 , N - M - 1 .0 , N );
}
else
{
lcl_PutFactorialElements( cnNumer, N - n - M, fCNumVarUpper, N - n );
lcl_PutFactorialElements( cnDenom, 0 .0 , n - 1 .0 , N );
}
if ( n - x + 1 .0 > fCDenomUpper )
// No overlap
lcl_PutFactorialElements( cnNumer, 1 .0 , N - M - n + x, N - M + 1 .0 );
else
{
// Overlap
lcl_PutFactorialElements( cnNumer, 1 .0 , N - M - fCDenomUpper, N - M + 1 .0 );
fCDenomVarLower = N - M - 2 .0 *(n - x) + 1 .0 ;
fCDenomUpper = n - x;
}
}
else
{
// Case 4
OSL_ENSURE( M >= n - x, "ScHypGeomDist: wrong assertion" );
OSL_ENSURE( M - x <= N - M + 1 .0 , "ScHypGeomDist: wrong assertion" );
if ( N - n < N - M + 1 .0 )
{
// No overlap
lcl_PutFactorialElements( cnNumer, 0 .0 , fCNumVarUpper, N - n );
lcl_PutFactorialElements( cnDenom, 0 .0 , M - 1 .0 , N );
}
else
{
// Overlap
OSL_ENSURE( fCNumLower <= N - M + 1 .0 , "ScHypGeomDist: wrong assertion" );
lcl_PutFactorialElements( cnNumer, M - n, fCNumVarUpper, N - n );
lcl_PutFactorialElements( cnDenom, 0 .0 , n - 1 .0 , N );
}
if ( n - x + 1 .0 > fCDenomUpper )
// No overlap
lcl_PutFactorialElements( cnNumer, N - 2 .0 *M + 1 .0 , N - M - n + x, N - M + 1 .0 );
else if ( M >= fCDenomUpper )
{
lcl_PutFactorialElements( cnNumer, N - 2 .0 *M + 1 .0 , N - M - fCDenomUpper, N - M + 1 .0 );
fCDenomUpper = n - x;
fCDenomVarLower = N - M - 2 .0 *(n - x) + 1 .0 ;
}
else
{
OSL_ENSURE( M <= fCDenomUpper, "ScHypGeomDist: wrong assertion" );
lcl_PutFactorialElements( cnDenom, fCDenomVarLower, N - n - 2 .0 *M + x,
N - n - M + x + 1 .0 );
fCDenomUpper = n - x;
fCDenomVarLower = N - M - 2 .0 *(n - x) + 1 .0 ;
}
}
OSL_ENSURE( fCDenomUpper <= n, "ScHypGeomDist: wrong assertion" );
fDNumVarLower = 0 .0 ;
}
double nDNumVarUpper = fCDenomUpper < x + 1 .0 ? n - x - 1 .0 : n - fCDenomUpper - 1 .0 ;
double nDDenomVarLower = fCDenomUpper < x + 1 .0 ? fCDenomVarLower : N - n - M + 1 .0 ;
lcl_PutFactorialElements( cnNumer, fDNumVarLower, nDNumVarUpper, n );
lcl_PutFactorialElements( cnDenom, nDDenomVarLower, N - n - M + x, N - n - M + x + 1 .0 );
::std::sort( cnNumer.begin(), cnNumer.end() );
::std::sort( cnDenom.begin(), cnDenom.end() );
auto it1 = cnNumer.rbegin(), it1End = cnNumer.rend();
auto it2 = cnDenom.rbegin(), it2End = cnDenom.rend();
double fFactor = 1 .0 ;
for ( ; it1 != it1End || it2 != it2End; )
{
double fEnum = 1 .0 , fDenom = 1 .0 ;
if ( it1 != it1End )
fEnum = *it1++;
if ( it2 != it2End )
fDenom = *it2++;
fFactor *= fEnum / fDenom;
}
return fFactor;
}
void ScInterpreter::ScGammaDist( bool bODFF )
{
sal_uInt8 nMinParamCount = ( bODFF ? 3 : 4 );
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, nMinParamCount, 4 ) )
return ;
bool bCumulative;
if (nParamCount == 4 )
bCumulative = GetBool();
else
bCumulative = true ;
double fBeta = GetDouble(); // scale
double fAlpha = GetDouble(); // shape
double fX = GetDouble(); // x
if ((!bODFF && fX < 0 ) || fAlpha <= 0 .0 || fBeta <= 0 .0 )
PushIllegalArgument();
else
{
if (bCumulative) // distribution
PushDouble( GetGammaDist( fX, fAlpha, fBeta));
else // density
PushDouble( GetGammaDistPDF( fX, fAlpha, fBeta));
}
}
void ScInterpreter::ScNormInv()
{
if ( MustHaveParamCount( GetByte(), 3 ) )
{
double sigma = GetDouble();
double mue = GetDouble();
double x = GetDouble();
if (sigma <= 0 .0 || x < 0 .0 || x > 1 .0 )
PushIllegalArgument();
else if (x == 0 .0 || x == 1 .0 )
PushNoValue();
else
PushDouble(gaussinv(x)*sigma + mue);
}
}
void ScInterpreter::ScSNormInv()
{
double x = GetDouble();
if (x < 0 .0 || x > 1 .0 )
PushIllegalArgument();
else if (x == 0 .0 || x == 1 .0 )
PushNoValue();
else
PushDouble(gaussinv(x));
}
void ScInterpreter::ScLogNormInv()
{
sal_uInt8 nParamCount = GetByte();
if ( MustHaveParamCount( nParamCount, 1 , 3 ) )
{
double fSigma = ( nParamCount == 3 ? GetDouble() : 1 .0 ); // Stddev
double fMue = ( nParamCount >= 2 ? GetDouble() : 0 .0 ); // Mean
double fP = GetDouble(); // p
if ( fSigma <= 0 .0 || fP <= 0 .0 || fP >= 1 .0 )
PushIllegalArgument();
else
PushDouble( exp( fMue + fSigma * gaussinv( fP ) ) );
}
}
class ScGammaDistFunction : public ScDistFunc
{
ScInterpreter& rInt;
double fp, fAlpha, fBeta;
public :
ScGammaDistFunction( ScInterpreter& rI, double fpVal, double fAlphaVal, double fBetaVal ) :
rInt(rI), fp(fpVal), fAlpha(fAlphaVal), fBeta(fBetaVal) {}
virtual ~ScGammaDistFunction() {}
double GetValue( double x ) const override { return fp - rInt.GetGammaDist(x, fAlpha, fBeta); }
};
void ScInterpreter::ScGammaInv()
{
if ( !MustHaveParamCount( GetByte(), 3 ) )
return ;
double fBeta = GetDouble();
double fAlpha = GetDouble();
double fP = GetDouble();
if (fAlpha <= 0 .0 || fBeta <= 0 .0 || fP < 0 .0 || fP >= 1 .0 )
{
PushIllegalArgument();
return ;
}
if (fP == 0 .0 )
PushInt(0 );
else
{
bool bConvError;
ScGammaDistFunction aFunc( *this , fP, fAlpha, fBeta );
double fStart = fAlpha * fBeta;
double fVal = lcl_IterateInverse( aFunc, fStart*0 .5 , fStart, bConvError );
if (bConvError)
SetError(FormulaError::NoConvergence);
PushDouble(fVal);
}
}
class ScBetaDistFunction : public ScDistFunc
{
ScInterpreter& rInt;
double fp, fAlpha, fBeta;
public :
ScBetaDistFunction( ScInterpreter& rI, double fpVal, double fAlphaVal, double fBetaVal ) :
rInt(rI), fp(fpVal), fAlpha(fAlphaVal), fBeta(fBetaVal) {}
virtual ~ScBetaDistFunction() {}
double GetValue( double x ) const override { return fp - rInt.GetBetaDist(x, fAlpha, fBeta); }
};
void ScInterpreter::ScBetaInv()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 3 , 5 ) )
return ;
double fP, fA, fB, fAlpha, fBeta;
if (nParamCount == 5 )
fB = GetDouble();
else
fB = 1 .0 ;
if (nParamCount >= 4 )
fA = GetDouble();
else
fA = 0 .0 ;
fBeta = GetDouble();
fAlpha = GetDouble();
fP = GetDouble();
if (fP < 0 .0 || fP > 1 .0 || fA >= fB || fAlpha <= 0 .0 || fBeta <= 0 .0 )
{
PushIllegalArgument();
return ;
}
bool bConvError;
ScBetaDistFunction aFunc( *this , fP, fAlpha, fBeta );
// 0..1 as range for iteration so it isn't extended beyond the valid range
double fVal = lcl_IterateInverse( aFunc, 0 .0 , 1 .0 , bConvError );
if (bConvError)
PushError( FormulaError::NoConvergence);
else
PushDouble(fA + fVal*(fB-fA)); // scale to (A,B)
}
// Note: T, F, and Chi are
// monotonically decreasing,
// therefore 1-Dist as function
class ScTDistFunction : public ScDistFunc
{
ScInterpreter& rInt;
double fp, fDF;
int nT;
public :
ScTDistFunction( ScInterpreter& rI, double fpVal, double fDFVal, int nType ) :
rInt( rI ), fp( fpVal ), fDF( fDFVal ), nT( nType ) {}
virtual ~ScTDistFunction() {}
double GetValue( double x ) const override { return fp - rInt.GetTDist( x, fDF, nT ); }
};
void ScInterpreter::ScTInv( int nType )
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
double fDF = ::rtl::math::approxFloor(GetDouble());
double fP = GetDouble();
if (fDF < 1 .0 || fP <= 0 .0 || fP > 1 .0 )
{
PushIllegalArgument();
return ;
}
if ( nType == 4 ) // left-tailed cumulative t-distribution
{
if ( fP == 1 .0 )
PushIllegalArgument();
else if ( fP < 0 .5 )
PushDouble( -GetTInv( 1 - fP, fDF, nType ) );
else
PushDouble( GetTInv( fP, fDF, nType ) );
}
else
PushDouble( GetTInv( fP, fDF, nType ) );
};
double ScInterpreter::GetTInv( double fAlpha, double fSize, int nType )
{
bool bConvError;
ScTDistFunction aFunc( *this , fAlpha, fSize, nType );
double fVal = lcl_IterateInverse( aFunc, fSize * 0 .5 , fSize, bConvError );
if (bConvError)
SetError(FormulaError::NoConvergence);
return fVal;
}
class ScFDistFunction : public ScDistFunc
{
ScInterpreter& rInt;
double fp, fF1, fF2;
public :
ScFDistFunction( ScInterpreter& rI, double fpVal, double fF1Val, double fF2Val ) :
rInt(rI), fp(fpVal), fF1(fF1Val), fF2(fF2Val) {}
virtual ~ScFDistFunction() {}
double GetValue( double x ) const override { return fp - rInt.GetFDist(x, fF1, fF2); }
};
void ScInterpreter::ScFInv()
{
if ( !MustHaveParamCount( GetByte(), 3 ) )
return ;
double fF2 = ::rtl::math::approxFloor(GetDouble());
double fF1 = ::rtl::math::approxFloor(GetDouble());
double fP = GetDouble();
if (fP <= 0 .0 || fF1 < 1 .0 || fF2 < 1 .0 || fF1 >= 1 .0 E10 || fF2 >= 1 .0 E10 || fP > 1 .0 )
{
PushIllegalArgument();
return ;
}
bool bConvError;
ScFDistFunction aFunc( *this , fP, fF1, fF2 );
double fVal = lcl_IterateInverse( aFunc, fF1*0 .5 , fF1, bConvError );
if (bConvError)
SetError(FormulaError::NoConvergence);
PushDouble(fVal);
}
void ScInterpreter::ScFInv_LT()
{
if ( !MustHaveParamCount( GetByte(), 3 ) )
return ;
double fF2 = ::rtl::math::approxFloor(GetDouble());
double fF1 = ::rtl::math::approxFloor(GetDouble());
double fP = GetDouble();
if (fP <= 0 .0 || fF1 < 1 .0 || fF2 < 1 .0 || fF1 >= 1 .0 E10 || fF2 >= 1 .0 E10 || fP > 1 .0 )
{
PushIllegalArgument();
return ;
}
bool bConvError;
ScFDistFunction aFunc( *this , ( 1 .0 - fP ), fF1, fF2 );
double fVal = lcl_IterateInverse( aFunc, fF1*0 .5 , fF1, bConvError );
if (bConvError)
SetError(FormulaError::NoConvergence);
PushDouble(fVal);
}
class ScChiDistFunction : public ScDistFunc
{
ScInterpreter& rInt;
double fp, fDF;
public :
ScChiDistFunction( ScInterpreter& rI, double fpVal, double fDFVal ) :
rInt(rI), fp(fpVal), fDF(fDFVal) {}
virtual ~ScChiDistFunction() {}
double GetValue( double x ) const override { return fp - rInt.GetChiDist(x, fDF); }
};
void ScInterpreter::ScChiInv()
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
double fDF = ::rtl::math::approxFloor(GetDouble());
double fP = GetDouble();
if (fDF < 1 .0 || fP <= 0 .0 || fP > 1 .0 )
{
PushIllegalArgument();
return ;
}
bool bConvError;
ScChiDistFunction aFunc( *this , fP, fDF );
double fVal = lcl_IterateInverse( aFunc, fDF*0 .5 , fDF, bConvError );
if (bConvError)
SetError(FormulaError::NoConvergence);
PushDouble(fVal);
}
/***********************************************/
class ScChiSqDistFunction : public ScDistFunc
{
ScInterpreter& rInt;
double fp, fDF;
public :
ScChiSqDistFunction( ScInterpreter& rI, double fpVal, double fDFVal ) :
rInt(rI), fp(fpVal), fDF(fDFVal) {}
virtual ~ScChiSqDistFunction() {}
double GetValue( double x ) const override { return fp - rInt.GetChiSqDistCDF(x, fDF); }
};
void ScInterpreter::ScChiSqInv()
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
double fDF = ::rtl::math::approxFloor(GetDouble());
double fP = GetDouble();
if (fDF < 1 .0 || fP < 0 .0 || fP >= 1 .0 )
{
PushIllegalArgument();
return ;
}
bool bConvError;
ScChiSqDistFunction aFunc( *this , fP, fDF );
double fVal = lcl_IterateInverse( aFunc, fDF*0 .5 , fDF, bConvError );
if (bConvError)
SetError(FormulaError::NoConvergence);
PushDouble(fVal);
}
void ScInterpreter::ScConfidence()
{
if ( MustHaveParamCount( GetByte(), 3 ) )
{
double n = ::rtl::math::approxFloor(GetDouble());
double sigma = GetDouble();
double alpha = GetDouble();
if (sigma <= 0 .0 || alpha <= 0 .0 || alpha >= 1 .0 || n < 1 .0 )
PushIllegalArgument();
else
PushDouble( gaussinv(1 .0 -alpha/2 .0 ) * sigma/sqrt(n) );
}
}
void ScInterpreter::ScConfidenceT()
{
if ( MustHaveParamCount( GetByte(), 3 ) )
{
double n = ::rtl::math::approxFloor(GetDouble());
double sigma = GetDouble();
double alpha = GetDouble();
if (sigma <= 0 .0 || alpha <= 0 .0 || alpha >= 1 .0 || n < 1 .0 )
PushIllegalArgument();
else if (n == 1 .0 ) // for interoperability with Excel
PushError(FormulaError::DivisionByZero);
else
PushDouble( sigma * GetTInv( alpha, n - 1 , 2 ) / sqrt( n ) );
}
}
void ScInterpreter::ScZTest()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 2 , 3 ) )
return ;
double sigma = 0 .0 , x;
if (nParamCount == 3 )
{
sigma = GetDouble();
if (sigma <= 0 .0 )
{
PushIllegalArgument();
return ;
}
}
x = GetDouble();
KahanSum fSum = 0 .0 ;
KahanSum fSumSqr = 0 .0 ;
double fVal;
double rValCount = 0 .0 ;
switch (GetStackType())
{
case svDouble :
{
fVal = GetDouble();
fSum += fVal;
fSumSqr += fVal*fVal;
rValCount++;
}
break ;
case svSingleRef :
{
ScAddress aAdr;
PopSingleRef( aAdr );
ScRefCellValue aCell(mrDoc, aAdr);
if (aCell.hasNumeric())
{
fVal = GetCellValue(aAdr, aCell);
fSum += fVal;
fSumSqr += fVal*fVal;
rValCount++;
}
}
break ;
case svRefList :
case svDoubleRef :
{
short nParam = 1 ;
size_t nRefInList = 0 ;
while (nParam-- > 0 )
{
ScRange aRange;
FormulaError nErr = FormulaError::NONE;
PopDoubleRef( aRange, nParam, nRefInList);
ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags );
if (aValIter.GetFirst(fVal, nErr))
{
fSum += fVal;
fSumSqr += fVal*fVal;
rValCount++;
while ((nErr == FormulaError::NONE) && aValIter.GetNext(fVal, nErr))
{
fSum += fVal;
fSumSqr += fVal*fVal;
rValCount++;
}
SetError(nErr);
}
}
}
break ;
case svMatrix :
case svExternalSingleRef:
case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix();
if (pMat)
{
SCSIZE nCount = pMat->GetElementCount();
if (pMat->IsNumeric())
{
for ( SCSIZE i = 0 ; i < nCount; i++ )
{
fVal= pMat->GetDouble(i);
fSum += fVal;
fSumSqr += fVal * fVal;
rValCount++;
}
}
else
{
for (SCSIZE i = 0 ; i < nCount; i++)
if (!pMat->IsStringOrEmpty(i))
{
fVal= pMat->GetDouble(i);
fSum += fVal;
fSumSqr += fVal * fVal;
rValCount++;
}
}
}
}
break ;
default : SetError(FormulaError::IllegalParameter); break ;
}
if (rValCount <= 1 .0 )
PushError( FormulaError::DivisionByZero);
else
{
double mue = fSum.get()/rValCount;
if (nParamCount != 3 )
{
sigma = (fSumSqr - fSum*fSum/rValCount).get()/(rValCount-1 .0 );
if (sigma == 0 .0 )
{
PushError(FormulaError::DivisionByZero);
return ;
}
PushDouble(0 .5 - gauss((mue-x)/sqrt(sigma/rValCount)));
}
else
PushDouble(0 .5 - gauss((mue-x)*sqrt(rValCount)/sigma));
}
}
bool ScInterpreter::CalculateTest(bool _bTemplin
,const SCSIZE nC1, const SCSIZE nC2,const SCSIZE nR1,const SCSIZE nR2
,const ScMatrixRef& pMat1,const ScMatrixRef& pMat2
,double & fT,double & fF)
{
double fCount1 = 0 .0 ;
double fCount2 = 0 .0 ;
KahanSum fSum1 = 0 .0 ;
KahanSum fSumSqr1 = 0 .0 ;
KahanSum fSum2 = 0 .0 ;
KahanSum fSumSqr2 = 0 .0 ;
double fVal;
SCSIZE i,j;
for (i = 0 ; i < nC1; i++)
for (j = 0 ; j < nR1; j++)
{
if (!pMat1->IsStringOrEmpty(i,j))
{
fVal = pMat1->GetDouble(i,j);
fSum1 += fVal;
fSumSqr1 += fVal * fVal;
fCount1++;
}
}
for (i = 0 ; i < nC2; i++)
for (j = 0 ; j < nR2; j++)
{
if (!pMat2->IsStringOrEmpty(i,j))
{
fVal = pMat2->GetDouble(i,j);
fSum2 += fVal;
fSumSqr2 += fVal * fVal;
fCount2++;
}
}
if (fCount1 < 2 .0 || fCount2 < 2 .0 )
{
PushNoValue();
return false ;
} // if (fCount1 < 2.0 || fCount2 < 2.0)
if ( _bTemplin )
{
double fS1 = (fSumSqr1-fSum1*fSum1/fCount1).get() / (fCount1-1 .0 ) / fCount1;
double fS2 = (fSumSqr2-fSum2*fSum2/fCount2).get() / (fCount2-1 .0 ) / fCount2;
if (fS1 + fS2 == 0 .0 )
{
PushNoValue();
return false ;
}
fT = std::abs(( fSum1/fCount1 - fSum2/fCount2 ).get())/sqrt(fS1+fS2);
double c = fS1/(fS1+fS2);
// GetTDist is calculated via GetBetaDist and also works with non-integral
// degrees of freedom. The result matches Excel
fF = 1 .0 /(c*c/(fCount1-1 .0 )+(1 .0 -c)*(1 .0 -c)/(fCount2-1 .0 ));
}
else
{
// according to Bronstein-Semendjajew
double fS1 = (fSumSqr1 - fSum1*fSum1/fCount1).get() / (fCount1 - 1 .0 ); // Variance
double fS2 = (fSumSqr2 - fSum2*fSum2/fCount2).get() / (fCount2 - 1 .0 );
fT = std::abs( fSum1.get()/fCount1 - fSum2.get()/fCount2 ) /
sqrt( (fCount1-1 .0 )*fS1 + (fCount2-1 .0 )*fS2 ) *
sqrt( fCount1*fCount2*(fCount1+fCount2-2 )/(fCount1+fCount2) );
fF = fCount1 + fCount2 - 2 ;
}
return true ;
}
void ScInterpreter::ScTTest()
{
if ( !MustHaveParamCount( GetByte(), 4 ) )
return ;
double fTyp = ::rtl::math::approxFloor(GetDouble());
double fTails = ::rtl::math::approxFloor(GetDouble());
if (fTails != 1 .0 && fTails != 2 .0 )
{
PushIllegalArgument();
return ;
}
ScMatrixRef pMat2 = GetMatrix();
ScMatrixRef pMat1 = GetMatrix();
if (!pMat1 || !pMat2)
{
PushIllegalParameter();
return ;
}
double fT, fF;
SCSIZE nC1, nC2;
SCSIZE nR1, nR2;
SCSIZE i, j;
pMat1->GetDimensions(nC1, nR1);
pMat2->GetDimensions(nC2, nR2);
if (fTyp == 1 .0 )
{
if (nC1 != nC2 || nR1 != nR2)
{
PushIllegalArgument();
return ;
}
double fCount = 0 .0 ;
KahanSum fSum1 = 0 .0 ;
KahanSum fSum2 = 0 .0 ;
KahanSum fSumSqrD = 0 .0 ;
double fVal1, fVal2;
for (i = 0 ; i < nC1; i++)
for (j = 0 ; j < nR1; j++)
{
if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j))
{
fVal1 = pMat1->GetDouble(i,j);
fVal2 = pMat2->GetDouble(i,j);
fSum1 += fVal1;
fSum2 += fVal2;
fSumSqrD += (fVal1 - fVal2)*(fVal1 - fVal2);
fCount++;
}
}
if (fCount < 1 .0 )
{
PushNoValue();
return ;
}
KahanSum fSumD = fSum1 - fSum2;
double fDivider = ( fSumSqrD*fCount - fSumD*fSumD ).get();
if ( fDivider == 0 .0 )
{
PushError(FormulaError::DivisionByZero);
return ;
}
fT = std::abs(fSumD.get()) * sqrt((fCount-1 .0 ) / fDivider);
fF = fCount - 1 .0 ;
}
else if (fTyp == 2 .0 )
{
if (!CalculateTest(false ,nC1, nC2,nR1, nR2,pMat1,pMat2,fT,fF))
return ; // error was pushed
}
else if (fTyp == 3 .0 )
{
if (!CalculateTest(true ,nC1, nC2,nR1, nR2,pMat1,pMat2,fT,fF))
return ; // error was pushed
}
else
{
PushIllegalArgument();
return ;
}
PushDouble( GetTDist( fT, fF, static_cast <int >(fTails) ) );
}
void ScInterpreter::ScFTest()
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
ScMatrixRef pMat2 = GetMatrix();
ScMatrixRef pMat1 = GetMatrix();
if (!pMat1 || !pMat2)
{
PushIllegalParameter();
return ;
}
auto aVal1 = pMat1->CollectKahan(sc::op::kOpSumAndSumSquare);
auto aVal2 = pMat2->CollectKahan(sc::op::kOpSumAndSumSquare);
double fCount1 = aVal1.mnCount;
double fCount2 = aVal2.mnCount;
KahanSum fSum1 = aVal1.maAccumulator[0 ];
KahanSum fSumSqr1 = aVal1.maAccumulator[1 ];
KahanSum fSum2 = aVal2.maAccumulator[0 ];
KahanSum fSumSqr2 = aVal2.maAccumulator[1 ];
if (fCount1 < 2 .0 || fCount2 < 2 .0 )
{
PushNoValue();
return ;
}
double fS1 = (fSumSqr1-fSum1*fSum1/fCount1).get() / (fCount1-1 .0 );
double fS2 = (fSumSqr2-fSum2*fSum2/fCount2).get() / (fCount2-1 .0 );
if (fS1 == 0 .0 || fS2 == 0 .0 )
{
PushNoValue();
return ;
}
double fF, fF1, fF2;
if (fS1 > fS2)
{
fF = fS1/fS2;
fF1 = fCount1-1 .0 ;
fF2 = fCount2-1 .0 ;
}
else
{
fF = fS2/fS1;
fF1 = fCount2-1 .0 ;
fF2 = fCount1-1 .0 ;
}
double fFcdf = GetFDist(fF, fF1, fF2);
PushDouble(2 .0 *std::min(fFcdf, 1 .0 - fFcdf));
}
void ScInterpreter::ScChiTest()
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
ScMatrixRef pMat2 = GetMatrix();
ScMatrixRef pMat1 = GetMatrix();
if (!pMat1 || !pMat2)
{
PushIllegalParameter();
return ;
}
SCSIZE nC1, nC2;
SCSIZE nR1, nR2;
pMat1->GetDimensions(nC1, nR1);
pMat2->GetDimensions(nC2, nR2);
if (nR1 != nR2 || nC1 != nC2)
{
PushIllegalArgument();
return ;
}
KahanSum fChi = 0 .0 ;
bool bEmpty = true ;
for (SCSIZE i = 0 ; i < nC1; i++)
{
for (SCSIZE j = 0 ; j < nR1; j++)
{
if (!(pMat1->IsEmpty(i,j) || pMat2->IsEmpty(i,j)))
{
bEmpty = false ;
if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j))
{
double fValX = pMat1->GetDouble(i,j);
double fValE = pMat2->GetDouble(i,j);
if ( fValE == 0 .0 )
{
PushError(FormulaError::DivisionByZero);
return ;
}
// These fTemp values guard against a failure when compiled
// with optimization (using g++ 4.8.2 on tinderbox 71-TDF),
// where ((fValX - fValE) * (fValX - fValE)) with
// fValE==1e+308 should had produced Infinity but did
// not, instead the result of divide() then was 1e+308.
volatile double fTemp1 = (fValX - fValE) * (fValX - fValE);
double fTemp2 = fTemp1;
if (std::isinf(fTemp2))
{
PushError(FormulaError::NoConvergence);
return ;
}
fChi += sc::divide( fTemp2, fValE);
}
else
{
PushIllegalArgument();
return ;
}
}
}
}
if ( bEmpty )
{
// not in ODFF1.2, but for interoperability with Excel
PushIllegalArgument();
return ;
}
double fDF;
if (nC1 == 1 || nR1 == 1 )
{
fDF = static_cast <double >(nC1*nR1 - 1 );
if (fDF == 0 .0 )
{
PushNoValue();
return ;
}
}
else
fDF = static_cast <double >(nC1-1 )*static_cast <double >(nR1-1 );
PushDouble(GetChiDist(fChi.get(), fDF));
}
void ScInterpreter::ScKurt()
{
KahanSum fSum;
double fCount;
std::vector<double > values;
if ( !CalculateSkew(fSum, fCount, values) )
return ;
// ODF 1.2 constraints: # of numbers >= 4
if (fCount < 4 .0 )
{
// for interoperability with Excel
PushError( FormulaError::DivisionByZero);
return ;
}
KahanSum vSum;
double fMean = fSum.get() / fCount;
for (double v : values)
vSum += (v - fMean) * (v - fMean);
double fStdDev = sqrt(vSum.get() / (fCount - 1 .0 ));
if (fStdDev == 0 .0 )
{
PushError( FormulaError::DivisionByZero);
return ;
}
KahanSum xpower4 = 0 .0 ;
for (double v : values)
{
double dx = (v - fMean) / fStdDev;
xpower4 += (dx * dx) * (dx * dx);
}
double k_d = (fCount - 2 .0 ) * (fCount - 3 .0 );
double k_l = fCount * (fCount + 1 .0 ) / ((fCount - 1 .0 ) * k_d);
double k_t = 3 .0 * (fCount - 1 .0 ) * (fCount - 1 .0 ) / k_d;
PushDouble(xpower4.get() * k_l - k_t);
}
void ScInterpreter::ScHarMean()
{
short nParamCount = GetByte();
KahanSum nVal = 0 .0 ;
double nValCount = 0 .0 ;
ScAddress aAdr;
ScRange aRange;
size_t nRefInList = 0 ;
while ((nGlobalError == FormulaError::NONE) && (nParamCount-- > 0 ))
{
switch (GetStackType())
{
case svDouble :
{
double x = GetDouble();
if (x > 0 .0 )
{
nVal += 1 .0 /x;
nValCount++;
}
else
SetError( FormulaError::IllegalArgument);
break ;
}
case svSingleRef :
{
PopSingleRef( aAdr );
ScRefCellValue aCell(mrDoc, aAdr);
if (aCell.hasNumeric())
{
double x = GetCellValue(aAdr, aCell);
if (x > 0 .0 )
{
nVal += 1 .0 /x;
nValCount++;
}
else
SetError( FormulaError::IllegalArgument);
}
break ;
}
case svDoubleRef :
case svRefList :
{
FormulaError nErr = FormulaError::NONE;
PopDoubleRef( aRange, nParamCount, nRefInList);
double nCellVal;
ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags );
if (aValIter.GetFirst(nCellVal, nErr))
{
if (nCellVal > 0 .0 )
{
nVal += 1 .0 /nCellVal;
nValCount++;
}
else
SetError( FormulaError::IllegalArgument);
SetError(nErr);
while ((nErr == FormulaError::NONE) && aValIter.GetNext(nCellVal, nErr))
{
if (nCellVal > 0 .0 )
{
nVal += 1 .0 /nCellVal;
nValCount++;
}
else
SetError( FormulaError::IllegalArgument);
}
SetError(nErr);
}
}
break ;
case svMatrix :
case svExternalSingleRef:
case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix();
if (pMat)
{
SCSIZE nCount = pMat->GetElementCount();
if (pMat->IsNumeric())
{
for (SCSIZE nElem = 0 ; nElem < nCount; nElem++)
{
double x = pMat->GetDouble(nElem);
if (x > 0 .0 )
{
nVal += 1 .0 /x;
nValCount++;
}
else
SetError( FormulaError::IllegalArgument);
}
}
else
{
for (SCSIZE nElem = 0 ; nElem < nCount; nElem++)
if (!pMat->IsStringOrEmpty(nElem))
{
double x = pMat->GetDouble(nElem);
if (x > 0 .0 )
{
nVal += 1 .0 /x;
nValCount++;
}
else
SetError( FormulaError::IllegalArgument);
}
}
}
}
break ;
default : SetError(FormulaError::IllegalParameter); break ;
}
}
if (nGlobalError == FormulaError::NONE)
PushDouble( nValCount / nVal.get() );
else
PushError( nGlobalError);
}
void ScInterpreter::ScGeoMean()
{
short nParamCount = GetByte();
KahanSum nVal = 0 .0 ;
double nValCount = 0 .0 ;
ScAddress aAdr;
ScRange aRange;
size_t nRefInList = 0 ;
while ((nGlobalError == FormulaError::NONE) && (nParamCount-- > 0 ))
{
switch (GetStackType())
{
case svDouble :
{
double x = GetDouble();
if (x > 0 .0 )
{
nVal += log(x);
nValCount++;
}
else if ( x == 0 .0 )
{
// value of 0 means that function result will be 0
while ( nParamCount-- > 0 )
PopError();
PushDouble( 0 .0 );
return ;
}
else
SetError( FormulaError::IllegalArgument);
break ;
}
case svSingleRef :
{
PopSingleRef( aAdr );
ScRefCellValue aCell(mrDoc, aAdr);
if (aCell.hasNumeric())
{
double x = GetCellValue(aAdr, aCell);
if (x > 0 .0 )
{
nVal += log(x);
nValCount++;
}
else if ( x == 0 .0 )
{
// value of 0 means that function result will be 0
while ( nParamCount-- > 0 )
PopError();
PushDouble( 0 .0 );
return ;
}
else
SetError( FormulaError::IllegalArgument);
}
break ;
}
case svDoubleRef :
case svRefList :
{
FormulaError nErr = FormulaError::NONE;
PopDoubleRef( aRange, nParamCount, nRefInList);
double nCellVal;
ScValueIterator aValIter(mrContext, aRange, mnSubTotalFlags);
if (aValIter.GetFirst(nCellVal, nErr))
{
if (nCellVal > 0 .0 )
{
nVal += log(nCellVal);
nValCount++;
}
else if ( nCellVal == 0 .0 )
{
// value of 0 means that function result will be 0
while ( nParamCount-- > 0 )
PopError();
PushDouble( 0 .0 );
return ;
}
else
SetError( FormulaError::IllegalArgument);
SetError(nErr);
while ((nErr == FormulaError::NONE) && aValIter.GetNext(nCellVal, nErr))
{
if (nCellVal > 0 .0 )
{
nVal += log(nCellVal);
nValCount++;
}
else if ( nCellVal == 0 .0 )
{
// value of 0 means that function result will be 0
while ( nParamCount-- > 0 )
PopError();
PushDouble( 0 .0 );
return ;
}
else
SetError( FormulaError::IllegalArgument);
}
SetError(nErr);
}
}
break ;
case svMatrix :
case svExternalSingleRef:
case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix();
if (pMat)
{
SCSIZE nCount = pMat->GetElementCount();
if (pMat->IsNumeric())
{
for (SCSIZE ui = 0 ; ui < nCount; ui++)
{
double x = pMat->GetDouble(ui);
if (x > 0 .0 )
{
nVal += log(x);
nValCount++;
}
else if ( x == 0 .0 )
{
// value of 0 means that function result will be 0
while ( nParamCount-- > 0 )
PopError();
PushDouble( 0 .0 );
return ;
}
else
SetError( FormulaError::IllegalArgument);
}
}
else
{
for (SCSIZE ui = 0 ; ui < nCount; ui++)
{
if (!pMat->IsStringOrEmpty(ui))
{
double x = pMat->GetDouble(ui);
if (x > 0 .0 )
{
nVal += log(x);
nValCount++;
}
else if ( x == 0 .0 )
{
// value of 0 means that function result will be 0
while ( nParamCount-- > 0 )
PopError();
PushDouble( 0 .0 );
return ;
}
else
SetError( FormulaError::IllegalArgument);
}
}
}
}
}
break ;
default : SetError(FormulaError::IllegalParameter); break ;
}
}
if (nGlobalError == FormulaError::NONE)
PushDouble(exp(nVal.get() / nValCount));
else
PushError( nGlobalError);
}
void ScInterpreter::ScStandard()
{
if ( MustHaveParamCount( GetByte(), 3 ) )
{
double sigma = GetDouble();
double mue = GetDouble();
double x = GetDouble();
if (sigma < 0 .0 )
PushError( FormulaError::IllegalArgument);
else if (sigma == 0 .0 )
PushError( FormulaError::DivisionByZero);
else
PushDouble((x-mue)/sigma);
}
}
bool ScInterpreter::CalculateSkew(KahanSum& fSum, double & fCount, std::vector<double >& values)
{
short nParamCount = GetByte();
if ( !MustHaveParamCountMin( nParamCount, 1 ) )
return false ;
fSum = 0 .0 ;
fCount = 0 .0 ;
double fVal = 0 .0 ;
ScAddress aAdr;
ScRange aRange;
size_t nRefInList = 0 ;
while (nParamCount-- > 0 )
{
switch (GetStackType())
{
case svDouble :
{
fVal = GetDouble();
fSum += fVal;
values.push_back(fVal);
fCount++;
}
break ;
case svSingleRef :
{
PopSingleRef( aAdr );
ScRefCellValue aCell(mrDoc, aAdr);
if (aCell.hasNumeric())
{
fVal = GetCellValue(aAdr, aCell);
fSum += fVal;
values.push_back(fVal);
fCount++;
}
}
break ;
case svDoubleRef :
case svRefList :
{
PopDoubleRef( aRange, nParamCount, nRefInList);
FormulaError nErr = FormulaError::NONE;
ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags );
if (aValIter.GetFirst(fVal, nErr))
{
fSum += fVal;
values.push_back(fVal);
fCount++;
SetError(nErr);
while ((nErr == FormulaError::NONE) && aValIter.GetNext(fVal, nErr))
{
fSum += fVal;
values.push_back(fVal);
fCount++;
}
SetError(nErr);
}
}
break ;
case svMatrix :
case svExternalSingleRef:
case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix();
if (pMat)
{
SCSIZE nCount = pMat->GetElementCount();
if (pMat->IsNumeric())
{
for (SCSIZE nElem = 0 ; nElem < nCount; nElem++)
{
fVal = pMat->GetDouble(nElem);
fSum += fVal;
values.push_back(fVal);
fCount++;
}
}
else
{
for (SCSIZE nElem = 0 ; nElem < nCount; nElem++)
if (!pMat->IsStringOrEmpty(nElem))
{
fVal = pMat->GetDouble(nElem);
fSum += fVal;
values.push_back(fVal);
fCount++;
}
}
}
}
break ;
default :
SetError(FormulaError::IllegalParameter);
break ;
}
}
if (nGlobalError != FormulaError::NONE)
{
PushError( nGlobalError);
return false ;
} // if (nGlobalError != FormulaError::NONE)
return true ;
}
void ScInterpreter::CalculateSkewOrSkewp( bool bSkewp )
{
KahanSum fSum;
double fCount;
std::vector<double > values;
if (!CalculateSkew( fSum, fCount, values))
return ;
// SKEW/SKEWP's constraints: they require at least three numbers
if (fCount < 3 .0 )
{
// for interoperability with Excel
PushError(FormulaError::DivisionByZero);
return ;
}
KahanSum vSum;
double fMean = fSum.get() / fCount;
for (double v : values)
vSum += (v - fMean) * (v - fMean);
double fStdDev = sqrt( vSum.get() / (bSkewp ? fCount : (fCount - 1 .0 )));
if (fStdDev == 0 )
{
PushIllegalArgument();
return ;
}
KahanSum xcube = 0 .0 ;
for (double v : values)
{
double dx = (v - fMean) / fStdDev;
xcube += dx * dx * dx;
}
if (bSkewp)
PushDouble( xcube.get() / fCount );
else
PushDouble( ((xcube.get() * fCount) / (fCount - 1 .0 )) / (fCount - 2 .0 ) );
}
void ScInterpreter::ScSkew()
{
CalculateSkewOrSkewp( false );
}
void ScInterpreter::ScSkewp()
{
CalculateSkewOrSkewp( true );
}
double ScInterpreter::GetMedian( vector<double > & rArray )
{
size_t nSize = rArray.size();
if (nSize == 0 || nGlobalError != FormulaError::NONE)
{
SetError( FormulaError::NoValue);
return 0 .0 ;
}
// Upper median.
size_t nMid = nSize / 2 ;
vector<double >::iterator iMid = rArray.begin() + nMid;
::std::nth_element( rArray.begin(), iMid, rArray.end());
if (nSize & 1 )
return *iMid; // Lower and upper median are equal.
else
{
double fUp = *iMid;
// Lower median.
iMid = ::std::max_element( rArray.begin(), rArray.begin() + nMid);
return (fUp + *iMid) / 2 ;
}
}
void ScInterpreter::ScMedian()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCountMin( nParamCount, 1 ) )
return ;
vector<double > aArray;
GetNumberSequenceArray( nParamCount, aArray, false );
PushDouble( GetMedian( aArray));
}
double ScInterpreter::GetPercentile( vector<double > & rArray, double fPercentile )
{
size_t nSize = rArray.size();
if (nSize == 1 )
return rArray[0 ];
else
{
size_t nIndex = static_cast <size_t>(::rtl::math::approxFloor( fPercentile * (nSize-1 )));
double fDiff = fPercentile * (nSize-1 ) - ::rtl::math::approxFloor( fPercentile * (nSize-1 ));
OSL_ENSURE(nIndex < nSize, "GetPercentile: wrong index(1)" );
vector<double >::iterator iter = rArray.begin() + nIndex;
::std::nth_element( rArray.begin(), iter, rArray.end());
if (fDiff <= 0 .0 )
{
// Note: neg fDiff seen with forum-mso-en4-719754.xlsx with
// fPercentile of near 1 where approxFloor gave nIndex of nSize-1
// resulting in a non-zero tiny negative fDiff.
return *iter;
}
else
{
OSL_ENSURE(nIndex < nSize-1 , "GetPercentile: wrong index(2)" );
double fVal = *iter;
iter = ::std::min_element( rArray.begin() + nIndex + 1 , rArray.end());
return fVal + fDiff * (*iter - fVal);
}
}
}
double ScInterpreter::GetPercentileExclusive( vector<double > & rArray, double fPercentile )
{
size_t nSize1 = rArray.size() + 1 ;
if ( rArray.empty() || nSize1 == 1 || nGlobalError != FormulaError::NONE)
{
SetError( FormulaError::NoValue );
return 0 .0 ;
}
if ( fPercentile * nSize1 < 1 .0 || fPercentile * nSize1 > static_cast <double >( nSize1 - 1 ) )
{
SetError( FormulaError::IllegalParameter );
return 0 .0 ;
}
size_t nIndex = static_cast <size_t>(::rtl::math::approxFloor( fPercentile * nSize1 - 1 ));
double fDiff = fPercentile * nSize1 - 1 - ::rtl::math::approxFloor( fPercentile * nSize1 - 1 );
OSL_ENSURE(nIndex < ( nSize1 - 1 ), "GetPercentile: wrong index(1)" );
vector<double >::iterator iter = rArray.begin() + nIndex;
::std::nth_element( rArray.begin(), iter, rArray.end());
if (fDiff == 0 .0 )
return *iter;
else
{
OSL_ENSURE(nIndex < nSize1, "GetPercentile: wrong index(2)" );
double fVal = *iter;
iter = ::std::min_element( rArray.begin() + nIndex + 1 , rArray.end());
return fVal + fDiff * (*iter - fVal);
}
}
void ScInterpreter::ScPercentile( bool bInclusive )
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
double alpha = GetDouble();
if ( bInclusive ? ( alpha < 0 .0 || alpha > 1 .0 ) : ( alpha <= 0 .0 || alpha >= 1 .0 ) )
{
PushIllegalArgument();
return ;
}
vector<double > aArray;
GetNumberSequenceArray( 1 , aArray, false );
if ( aArray.empty() || nGlobalError != FormulaError::NONE )
{
PushNoValue();
return ;
}
if ( bInclusive )
PushDouble( GetPercentile( aArray, alpha ));
else
PushDouble( GetPercentileExclusive( aArray, alpha ));
}
void ScInterpreter::ScQuartile( bool bInclusive )
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
double fFlag = ::rtl::math::approxFloor(GetDouble());
if ( bInclusive ? ( fFlag < 0 .0 || fFlag > 4 .0 ) : ( fFlag <= 0 .0 || fFlag >= 4 .0 ) )
{
PushIllegalArgument();
return ;
}
vector<double > aArray;
GetNumberSequenceArray( 1 , aArray, false );
if ( aArray.empty() || nGlobalError != FormulaError::NONE )
{
PushNoValue();
return ;
}
if ( bInclusive )
PushDouble( fFlag == 2 .0 ? GetMedian( aArray ) : GetPercentile( aArray, 0 .25 * fFlag ) );
else
PushDouble( fFlag == 2 .0 ? GetMedian( aArray ) : GetPercentileExclusive( aArray, 0 .25 * fFlag ) );
}
void ScInterpreter::ScModalValue()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCountMin( nParamCount, 1 ) )
return ;
vector<double > aSortArray;
GetSortArray( nParamCount, aSortArray, nullptr, false , false );
SCSIZE nSize = aSortArray.size();
if (nSize == 0 || nGlobalError != FormulaError::NONE)
PushNoValue();
else
{
SCSIZE nMaxIndex = 0 , nMax = 1 , nCount = 1 ;
double nOldVal = aSortArray[0 ];
SCSIZE i;
for ( i = 1 ; i < nSize; i++)
{
if (aSortArray[i] == nOldVal)
nCount++;
else
{
nOldVal = aSortArray[i];
if (nCount > nMax)
{
nMax = nCount;
nMaxIndex = i-1 ;
}
nCount = 1 ;
}
}
if (nCount > nMax)
{
nMax = nCount;
nMaxIndex = i-1 ;
}
if (nMax == 1 && nCount == 1 )
PushNoValue();
else if (nMax == 1 )
PushDouble(nOldVal);
else
PushDouble(aSortArray[nMaxIndex]);
}
}
void ScInterpreter::ScModalValue_MS( bool bSingle )
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCountMin( nParamCount, 1 ) )
return ;
vector<double > aArray;
GetNumberSequenceArray( nParamCount, aArray, false );
vector< double > aSortArray( aArray );
QuickSort( aSortArray, nullptr );
SCSIZE nSize = aSortArray.size();
if ( nSize == 0 || nGlobalError != FormulaError::NONE )
PushNoValue();
else
{
SCSIZE nMax = 1 , nCount = 1 ;
double nOldVal = aSortArray[ 0 ];
vector< double > aResultArray( 1 );
SCSIZE i;
for ( i = 1 ; i < nSize; i++ )
{
if ( aSortArray[ i ] == nOldVal )
nCount++;
else
{
if ( nCount >= nMax && nCount > 1 )
{
if ( nCount > nMax )
{
nMax = nCount;
if ( aResultArray.size() != 1 )
vector< double >( 1 ).swap( aResultArray );
aResultArray[ 0 ] = nOldVal;
}
else
aResultArray.emplace_back( nOldVal );
}
nOldVal = aSortArray[ i ];
nCount = 1 ;
}
}
if ( nCount >= nMax && nCount > 1 )
{
if ( nCount > nMax )
vector< double >().swap( aResultArray );
aResultArray.emplace_back( nOldVal );
}
if ( nMax == 1 && nCount == 1 )
PushNoValue();
else if ( nMax == 1 )
PushDouble( nOldVal ); // there is only 1 result, no reordering needed
else
{
// sort resultArray according to ordering of aArray
vector< vector< double > > aOrder;
aOrder.resize( aResultArray.size(), vector< double >( 2 ) );
for ( i = 0 ; i < aResultArray.size(); i++ )
{
for ( SCSIZE j = 0 ; j < nSize; j++ )
{
if ( aArray[ j ] == aResultArray[ i ] )
{
aOrder[ i ][ 0 ] = aResultArray[ i ];
aOrder[ i ][ 1 ] = j;
break ;
}
}
}
sort( aOrder.begin(), aOrder.end(), []( const std::vector< double >& lhs,
const std::vector< double >& rhs )
{ return lhs[ 1 ] < rhs[ 1 ]; } );
if ( bSingle )
PushDouble( aOrder[ 0 ][ 0 ] );
else
{
// put result in correct order in aResultArray
for ( i = 0 ; i < aResultArray.size(); i++ )
aResultArray[ i ] = aOrder[ i ][ 0 ];
ScMatrixRef pResMatrix = GetNewMat( 1 , aResultArray.size(), true );
pResMatrix->PutDoubleVector( aResultArray, 0 , 0 );
PushMatrix( pResMatrix );
}
}
}
}
void ScInterpreter::CalculateSmallLarge(bool bSmall)
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
SCSIZE nCol = 0 , nRow = 0 ;
const auto aArray = GetRankNumberArray(nCol, nRow);
const size_t nRankArraySize = aArray.size();
if (nRankArraySize == 0 || nGlobalError != FormulaError::NONE)
{
PushNoValue();
return ;
}
assert(nRankArraySize == nCol * nRow);
std::vector<SCSIZE> aRankArray;
aRankArray.reserve(nRankArraySize);
std::transform(aArray.begin(), aArray.end(), std::back_inserter(aRankArray),
[bSmall](double f) {
f = (bSmall ? rtl::math::approxFloor(f) : rtl::math::approxCeil(f));
// Valid ranks are >= 1.
if (f < 1 .0 || !o3tl::convertsToAtMost(f, std::numeric_limits<SCSIZE>::max()))
return static_cast <SCSIZE>(0 );
return static_cast <SCSIZE>(f);
});
vector<double > aSortArray;
GetNumberSequenceArray(1 , aSortArray, false );
const SCSIZE nSize = aSortArray.size();
if (nSize == 0 || nGlobalError != FormulaError::NONE)
PushNoValue();
else if (nRankArraySize == 1 )
{
const SCSIZE k = aRankArray[0 ];
if (k < 1 || nSize < k)
{
if (!std::isfinite(aArray[0 ]))
PushDouble(aArray[0 ]); // propagates error
else
PushNoValue();
}
else
{
vector<double >::iterator iPos = aSortArray.begin() + (bSmall ? k-1 : nSize-k);
::std::nth_element( aSortArray.begin(), iPos, aSortArray.end());
PushDouble( *iPos);
}
}
else
{
std::set<SCSIZE> aIndices;
for (SCSIZE n : aRankArray)
{
if (1 <= n && n <= nSize)
aIndices.insert(bSmall ? n-1 : nSize-n);
}
// We can spare sorting when the total number of ranks is small enough.
// Find only the elements at given indices if, arbitrarily, the index size is
// smaller than 1/3 of the haystack array's size; just sort it squarely, otherwise.
if (aIndices.size() < nSize/3 )
{
auto itBegin = aSortArray.begin();
for (SCSIZE i : aIndices)
{
auto it = aSortArray.begin() + i;
std::nth_element(itBegin, it, aSortArray.end());
itBegin = ++it;
}
}
else
std::sort(aSortArray.begin(), aSortArray.end());
std::vector<double > aResultArray;
aResultArray.reserve(nRankArraySize);
for (size_t i = 0 ; i < nRankArraySize; ++i)
{
const SCSIZE n = aRankArray[i];
if (1 <= n && n <= nSize)
aResultArray.push_back( aSortArray[bSmall ? n-1 : nSize-n]);
else if (!std::isfinite( aArray[i]))
aResultArray.push_back( aArray[i]); // propagate error
else
aResultArray.push_back( CreateDoubleError( FormulaError::IllegalArgument));
}
ScMatrixRef pResult = GetNewMat(nCol, nRow, aResultArray);
PushMatrix(pResult);
}
}
void ScInterpreter::ScLarge()
{
CalculateSmallLarge(false );
}
void ScInterpreter::ScSmall()
{
CalculateSmallLarge(true );
}
void ScInterpreter::ScPercentrank( bool bInclusive )
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 2 , 3 ) )
return ;
double fSignificance = ( nParamCount == 3 ? ::rtl::math::approxFloor( GetDouble() ) : 3 .0 );
if ( fSignificance < 1 .0 )
{
PushIllegalArgument();
return ;
}
double fNum = GetDouble();
vector<double > aSortArray;
GetSortArray( 1 , aSortArray, nullptr, false , false );
SCSIZE nSize = aSortArray.size();
if ( nSize == 0 || nGlobalError != FormulaError::NONE )
PushNoValue();
else
{
if ( fNum < aSortArray[ 0 ] || fNum > aSortArray[ nSize - 1 ] )
PushNoValue();
else
{
double fRes;
if ( nSize == 1 )
fRes = 1 .0 ; // fNum == aSortArray[ 0 ], see test above
else
fRes = GetPercentrank( aSortArray, fNum, bInclusive );
if ( fRes != 0 .0 )
{
double fExp = ::rtl::math::approxFloor( log10( fRes ) ) + 1 .0 - fSignificance;
fRes = ::rtl::math::round( fRes * pow( 10 , -fExp ) ) / pow( 10 , -fExp );
}
PushDouble( fRes );
}
}
}
double ScInterpreter::GetPercentrank( ::std::vector<double > & rArray, double fVal, bool bInclusive )
{
SCSIZE nSize = rArray.size();
double fRes;
if ( fVal == rArray[ 0 ] )
{
if ( bInclusive )
fRes = 0 .0 ;
else
fRes = 1 .0 / static_cast <double >( nSize + 1 );
}
else
{
SCSIZE nOldCount = 0 ;
double fOldVal = rArray[ 0 ];
SCSIZE i;
for ( i = 1 ; i < nSize && rArray[ i ] < fVal; i++ )
{
if ( rArray[ i ] != fOldVal )
{
nOldCount = i;
fOldVal = rArray[ i ];
}
}
if ( rArray[ i ] != fOldVal )
nOldCount = i;
if ( fVal == rArray[ i ] )
{
if ( bInclusive )
fRes = div( nOldCount, nSize - 1 );
else
fRes = static_cast <double >( i + 1 ) / static_cast <double >( nSize + 1 );
}
else
{
// nOldCount is the count of smaller entries
// fVal is between rArray[ nOldCount - 1 ] and rArray[ nOldCount ]
// use linear interpolation to find a position between the entries
if ( nOldCount == 0 )
{
OSL_FAIL( "should not happen" );
fRes = 0 .0 ;
}
else
{
double fFract = ( fVal - rArray[ nOldCount - 1 ] ) /
( rArray[ nOldCount ] - rArray[ nOldCount - 1 ] );
if ( bInclusive )
fRes = div( static_cast <double >( nOldCount - 1 ) + fFract, nSize - 1 );
else
fRes = ( static_cast <double >(nOldCount) + fFract ) / static_cast <double >( nSize + 1 );
}
}
}
return fRes;
}
void ScInterpreter::ScTrimMean()
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
double alpha = GetDouble();
if (alpha < 0 .0 || alpha >= 1 .0 )
{
PushIllegalArgument();
return ;
}
vector<double > aSortArray;
GetSortArray( 1 , aSortArray, nullptr, false , false );
SCSIZE nSize = aSortArray.size();
if (nSize == 0 || nGlobalError != FormulaError::NONE)
PushNoValue();
else
{
sal_uLong nIndex = static_cast <sal_uLong>(::rtl::math::approxFloor(alpha*static_cast <double >(nSize)));
if (nIndex % 2 != 0 )
nIndex--;
nIndex /= 2 ;
OSL_ENSURE(nIndex < nSize, "ScTrimMean: wrong index" );
KahanSum fSum = 0 .0 ;
for (SCSIZE i = nIndex; i < nSize-nIndex; i++)
fSum += aSortArray[i];
PushDouble(fSum.get()/static_cast <double >(nSize-2 *nIndex));
}
}
std::vector<double > ScInterpreter::GetRankNumberArray( SCSIZE& rCol, SCSIZE& rRow )
{
std::vector<double > aArray;
switch (GetStackType())
{
case svDouble:
aArray.push_back(PopDouble());
rCol = rRow = 1 ;
break ;
case svSingleRef:
{
ScAddress aAdr;
PopSingleRef(aAdr);
ScRefCellValue aCell(mrDoc, aAdr);
if (aCell.hasNumeric())
{
aArray.push_back(GetCellValue(aAdr, aCell));
rCol = rRow = 1 ;
}
}
break ;
case svDoubleRef:
{
ScRange aRange;
PopDoubleRef(aRange, true );
if (nGlobalError != FormulaError::NONE)
break ;
// give up unless the start and end are in the same sheet
if (aRange.aStart.Tab() != aRange.aEnd.Tab())
{
SetError(FormulaError::IllegalParameter);
break ;
}
// the range already is in order
assert(aRange.aStart.Col() <= aRange.aEnd.Col());
assert(aRange.aStart.Row() <= aRange.aEnd.Row());
rCol = aRange.aEnd.Col() - aRange.aStart.Col() + 1 ;
rRow = aRange.aEnd.Row() - aRange.aStart.Row() + 1 ;
aArray.reserve(rCol * rRow);
FormulaError nErr = FormulaError::NONE;
double fCellVal;
ScValueIterator aValIter(mrContext, aRange, mnSubTotalFlags);
if (aValIter.GetFirst(fCellVal, nErr))
{
do
aArray.push_back(fCellVal);
while (aValIter.GetNext(fCellVal, nErr) && nErr == FormulaError::NONE);
}
// Note that SMALL() and LARGE() rank parameters (2nd) have
// ParamClass::Value, so in array mode this is never hit and
// argument was converted to matrix instead, but for normal
// evaluation any non-numeric value including empty cell will
// result in error anyway, so just clear and propagate an existing
// error here already.
if (aArray.size() != rCol * rRow)
{
aArray.clear();
SetError(nErr);
}
}
break ;
case svMatrix:
case svExternalSingleRef:
case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix();
if (!pMat)
break ;
const SCSIZE nCount = pMat->GetElementCount();
aArray.reserve(nCount);
// Do not propagate errors from matrix elements as global error.
pMat->SetErrorInterpreter(nullptr);
if (pMat->IsNumeric())
{
for (SCSIZE i = 0 ; i < nCount; ++i)
aArray.push_back(pMat->GetDouble(i));
}
else
{
for (SCSIZE i = 0 ; i < nCount; ++i)
{
if (pMat->IsValue(i))
aArray.push_back( pMat->GetDouble(i));
else
aArray.push_back( CreateDoubleError( FormulaError::NoValue));
}
}
pMat->GetDimensions(rCol, rRow);
}
break ;
default :
PopError();
SetError(FormulaError::IllegalParameter);
break ;
}
return aArray;
}
void ScInterpreter::GetNumberSequenceArray( sal_uInt8 nParamCount, vector<double >& rArray, bool bConvertTextInArray )
{
ScAddress aAdr;
ScRange aRange;
const bool bIgnoreErrVal = bool (mnSubTotalFlags & SubtotalFlags::IgnoreErrVal);
short nParam = nParamCount;
size_t nRefInList = 0 ;
ReverseStack( nParamCount );
while (nParam-- > 0 )
{
const StackVar eStackType = GetStackType();
switch (eStackType)
{
case svDouble :
rArray.push_back( PopDouble());
break ;
case svSingleRef :
{
PopSingleRef( aAdr );
ScRefCellValue aCell(mrDoc, aAdr);
if (bIgnoreErrVal && aCell.hasError())
; // nothing
else if (aCell.hasNumeric())
rArray.push_back(GetCellValue(aAdr, aCell));
}
break ;
case svDoubleRef :
case svRefList :
{
PopDoubleRef( aRange, nParam, nRefInList);
if (nGlobalError != FormulaError::NONE)
break ;
aRange.PutInOrder();
SCSIZE nCellCount = aRange.aEnd.Col() - aRange.aStart.Col() + 1 ;
nCellCount *= aRange.aEnd.Row() - aRange.aStart.Row() + 1 ;
rArray.reserve( rArray.size() + nCellCount);
FormulaError nErr = FormulaError::NONE;
double fCellVal;
ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags );
if (aValIter.GetFirst( fCellVal, nErr))
{
if (bIgnoreErrVal)
{
if (nErr == FormulaError::NONE)
rArray.push_back( fCellVal);
while (aValIter.GetNext( fCellVal, nErr))
{
if (nErr == FormulaError::NONE)
rArray.push_back( fCellVal);
}
}
else
{
rArray.push_back( fCellVal);
SetError(nErr);
while ((nErr == FormulaError::NONE) && aValIter.GetNext( fCellVal, nErr))
rArray.push_back( fCellVal);
SetError(nErr);
}
}
}
break ;
case svMatrix :
case svExternalSingleRef:
case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix();
if (!pMat)
break ;
SCSIZE nCount = pMat->GetElementCount();
rArray.reserve( rArray.size() + nCount);
if (pMat->IsNumeric())
{
if (bIgnoreErrVal)
{
for (SCSIZE i = 0 ; i < nCount; ++i)
{
const double fVal = pMat->GetDouble(i);
if (nGlobalError == FormulaError::NONE)
rArray.push_back( fVal);
else
nGlobalError = FormulaError::NONE;
}
}
else
{
for (SCSIZE i = 0 ; i < nCount; ++i)
rArray.push_back( pMat->GetDouble(i));
}
}
else if (bConvertTextInArray && eStackType == svMatrix)
{
for (SCSIZE i = 0 ; i < nCount; ++i)
{
if ( pMat->IsValue( i ) )
{
if (bIgnoreErrVal)
{
const double fVal = pMat->GetDouble(i);
if (nGlobalError == FormulaError::NONE)
rArray.push_back( fVal);
else
nGlobalError = FormulaError::NONE;
}
else
rArray.push_back( pMat->GetDouble(i));
}
else
{
// tdf#88547 try to convert string to (date)value
OUString aStr = pMat->GetString( i ).getString();
if ( aStr.getLength() > 0 )
{
FormulaError nErr = nGlobalError;
nGlobalError = FormulaError::NONE;
double fVal = ConvertStringToValue( aStr );
if ( nGlobalError == FormulaError::NONE )
{
rArray.push_back( fVal );
nGlobalError = nErr;
}
else
{
if (!bIgnoreErrVal)
rArray.push_back( CreateDoubleError( FormulaError::NoValue));
// Propagate previous error if any, else
// the current #VALUE! error, unless
// ignoring error values.
if (nErr != FormulaError::NONE)
nGlobalError = nErr;
else if (!bIgnoreErrVal)
nGlobalError = FormulaError::NoValue;
else
nGlobalError = FormulaError::NONE;
}
}
}
}
}
else
{
if (bIgnoreErrVal)
{
for (SCSIZE i = 0 ; i < nCount; ++i)
{
if (pMat->IsValue(i))
{
const double fVal = pMat->GetDouble(i);
if (nGlobalError == FormulaError::NONE)
rArray.push_back( fVal);
else
nGlobalError = FormulaError::NONE;
}
}
}
else
{
for (SCSIZE i = 0 ; i < nCount; ++i)
{
if (pMat->IsValue(i))
rArray.push_back( pMat->GetDouble(i));
}
}
}
}
break ;
default :
PopError();
SetError( FormulaError::IllegalParameter);
break ;
}
if (nGlobalError != FormulaError::NONE)
break ; // while
}
// nParam > 0 in case of error, clean stack environment and obtain earlier
// error if there was one.
while (nParam-- > 0 )
PopError();
}
void ScInterpreter::DecoladeRow( ScSortInfoArray* pArray, SCROW nRow1, SCROW nRow2 )
{
SCROW nRow;
int nMax = nRow2 - nRow1;
for (SCROW i = nRow1; (i + 4 ) <= nRow2; i += 4 )
{
nRow = comphelper::rng::uniform_int_distribution(0 , nMax - 1 );
pArray->Swap(i, nRow1 + nRow);
}
}
std::unique_ptr<ScSortInfoArray> ScInterpreter::CreateFastSortInfoArray(
const ScSortParam& rSortParam, bool bMatrix, SCCOLROW nInd1, SCCOLROW nInd2 )
{
sal_uInt16 nUsedSorts = 1 ;
while (nUsedSorts < rSortParam.GetSortKeyCount() && rSortParam.maKeyState[nUsedSorts].bDoSort)
nUsedSorts++;
std::unique_ptr<ScSortInfoArray> pArray(new ScSortInfoArray(nUsedSorts, nInd1, nInd2));
if (rSortParam.bByRow)
{
for (sal_uInt16 nSort = 0 ; nSort < nUsedSorts; nSort++)
{
if (!bMatrix)
{
SCCOL nCol = static_cast <SCCOL>(rSortParam.maKeyState[nSort].nField);
std::optional<sc::ColumnIterator> pIter = mrDoc.GetColumnIterator(rSortParam.nSourceTab, nCol, nInd1, nInd2);
assert(pIter->hasCell());
for (SCROW nRow = nInd1; nRow <= nInd2; nRow++, pIter->next())
{
ScSortInfo& rInfo = pArray->Get(nSort, nRow);
rInfo.maCell = pIter->getCell();
rInfo.nOrg = nRow;
}
}
else
{
for (SCROW nRow = nInd1; nRow <= nInd2; nRow++)
{
ScSortInfo& rInfo = pArray->Get(nSort, nRow);
rInfo.nOrg = nRow;
}
}
}
}
else
{
for (sal_uInt16 nSort = 0 ; nSort < nUsedSorts; nSort++)
{
if (!bMatrix)
{
SCROW nRow = rSortParam.maKeyState[nSort].nField;
for (SCCOL nCol = static_cast <SCCOL>(nInd1);
nCol <= static_cast <SCCOL>(nInd2); nCol++)
{
ScSortInfo& rInfo = pArray->Get(nSort, nCol);
rInfo.maCell = mrDoc.GetRefCellValue(ScAddress(nCol, nRow, rSortParam.nSourceTab));
rInfo.nOrg = nCol;
}
}
else
{
for (SCCOL nCol = static_cast <SCCOL>(nInd1);
nCol <= static_cast <SCCOL>(nInd2); nCol++)
{
ScSortInfo& rInfo = pArray->Get(nSort, nCol);
rInfo.nOrg = nCol;
}
}
}
}
return pArray;
}
std::vector<SCCOLROW> ScInterpreter::GetSortOrder( const ScSortParam& rSortParam, const ScMatrixRef& pMatSrc )
{
std::vector<SCCOLROW> aOrderIndices;
aSortParam = rSortParam;
if (rSortParam.bByRow)
{
const SCROW nLastRow = rSortParam.nRow2;
const SCROW nRow1 = (rSortParam.bHasHeader ? rSortParam.nRow1 + 1 : rSortParam.nRow1);
if (nRow1 < nLastRow)
{
std::unique_ptr<ScSortInfoArray> pArray(CreateFastSortInfoArray(
aSortParam, (pMatSrc != nullptr), nRow1, nLastRow));
if (nLastRow - nRow1 > 255 )
DecoladeRow(pArray.get(), nRow1, nLastRow);
QuickSort(pArray.get(), pMatSrc, nRow1, nLastRow);
aOrderIndices = pArray->GetOrderIndices();
}
}
else
{
const SCCOL nLastCol = rSortParam.nCol2;
const SCCOL nCol1 = (rSortParam.bHasHeader ? rSortParam.nCol1 + 1 : rSortParam.nCol1);
if (nCol1 < nLastCol)
{
std::unique_ptr<ScSortInfoArray> pArray(CreateFastSortInfoArray(
aSortParam, (pMatSrc != nullptr), nCol1, nLastCol));
QuickSort(pArray.get(), pMatSrc, nCol1, nLastCol);
aOrderIndices = pArray->GetOrderIndices();
}
}
return aOrderIndices;
}
ScMatrixRef ScInterpreter::CreateSortedMatrix( const ScSortParam& rSortParam, const ScMatrixRef& pMatSrc,
const ScRange& rSourceRange, const std::vector<SCCOLROW>& rSortArray, SCSIZE nsC, SCSIZE nsR )
{
SCCOLROW nStartPos = (!rSortParam.bByRow ? rSortParam.nCol1 : rSortParam.nRow1);
size_t nCount = rSortArray.size();
std::vector<SCCOLROW> aPosTable(nCount);
for (size_t i = 0 ; i < nCount; ++i)
aPosTable[rSortArray[i] - nStartPos] = i;
ScMatrixRef pResMat = nullptr;
if (!rSortArray.empty())
{
pResMat = GetNewMat(nsC, nsR, /*bEmpty*/true);
if (!pMatSrc)
{
ScCellIterator aCellIter(mrDoc, rSourceRange);
for (bool bHas = aCellIter.first(); bHas; bHas = aCellIter.next())
{
SCSIZE nThisCol = static_cast <SCSIZE>(aCellIter.GetPos().Col() - rSourceRange.aStart.Col());
SCSIZE nThisRow = static_cast <SCSIZE>(aCellIter.GetPos().Row() - rSourceRange.aStart.Row());
ScRefCellValue aCell = aCellIter.getRefCellValue();
if (aCell.hasNumeric())
{
if (rSortParam.bByRow)
pResMat->PutDouble(GetCellValue(aCellIter.GetPos(), aCell), nThisCol, aPosTable[nThisRow]);
else
pResMat->PutDouble(GetCellValue(aCellIter.GetPos(), aCell), aPosTable[nThisCol], nThisRow);
}
else
{
svl::SharedString aStr;
GetCellString(aStr, aCell);
if (rSortParam.bByRow)
pResMat->PutString(aStr, nThisCol, aPosTable[nThisRow]);
else
pResMat->PutString(aStr, aPosTable[nThisCol], nThisRow);
}
}
}
else
{
for (SCCOL ci = rSourceRange.aStart.Col(); ci <= rSourceRange.aEnd.Col(); ci++)
{
for (SCROW rj = rSourceRange.aStart.Row(); rj <= rSourceRange.aEnd.Row(); rj++)
{
if (pMatSrc->IsEmptyCell(ci, rj))
{
if (rSortParam.bByRow)
pResMat->PutEmpty(ci, aPosTable[rj]);
else
pResMat->PutEmpty(aPosTable[ci], rj);
}
else if (pMatSrc->IsStringOrEmpty(ci, rj))
{
if (rSortParam.bByRow)
pResMat->PutString(pMatSrc->GetString(ci, rj), ci, aPosTable[rj]);
else
pResMat->PutString(pMatSrc->GetString(ci, rj), aPosTable[ci], rj);
}
else
{
if (rSortParam.bByRow)
pResMat->PutDouble(pMatSrc->GetDouble(ci, rj), ci, aPosTable[rj]);
else
pResMat->PutDouble(pMatSrc->GetDouble(ci, rj), aPosTable[ci], rj);
}
}
}
}
}
return pResMat;
}
void ScInterpreter::QuickSort( ScSortInfoArray* pArray, const ScMatrixRef& pMatSrc, SCCOLROW nLo, SCCOLROW nHi )
{
if ((nHi - nLo) == 1 )
{
if (Compare(pArray, pMatSrc, nLo, nHi) > 0 )
pArray->Swap( nLo, nHi );
}
else
{
SCCOLROW ni = nLo;
SCCOLROW nj = nHi;
do
{
while ((ni <= nHi) && (Compare(pArray, pMatSrc, ni, nLo)) < 0 )
ni++;
while ((nj >= nLo) && (Compare(pArray, pMatSrc, nLo, nj)) < 0 )
nj--;
if (ni <= nj)
{
if (ni != nj)
pArray->Swap( ni, nj );
ni++;
nj--;
}
} while (ni < nj);
if ((nj - nLo) < (nHi - ni))
{
if (nLo < nj)
QuickSort(pArray, pMatSrc, nLo, nj);
if (ni < nHi)
QuickSort(pArray, pMatSrc, ni, nHi);
}
else
{
if (ni < nHi)
QuickSort(pArray, pMatSrc, ni, nHi);
if (nLo < nj)
QuickSort(pArray, pMatSrc, nLo, nj);
}
}
}
short ScInterpreter::Compare( ScSortInfoArray* pArray, const ScMatrixRef& pMatSrc, SCCOLROW nIndex1, SCCOLROW nIndex2 ) const
{
short nRes;
sal_uInt16 nSort = 0 ;
do
{
ScSortInfo& rInfo1 = pArray->Get( nSort, nIndex1 );
ScSortInfo& rInfo2 = pArray->Get( nSort, nIndex2 );
if (!pMatSrc)
{
nRes = CompareCell(nSort, rInfo1.maCell, rInfo2.maCell);
}
else
{
if (aSortParam.bByRow)
nRes = CompareMatrixCell( pMatSrc, nSort,
static_cast <SCCOL>(aSortParam.maKeyState[nSort].nField), rInfo1.nOrg,
static_cast <SCCOL>(aSortParam.maKeyState[nSort].nField), rInfo2.nOrg );
else
nRes = CompareMatrixCell( pMatSrc, nSort,
static_cast <SCCOL>(rInfo1.nOrg), aSortParam.maKeyState[nSort].nField,
static_cast <SCCOL>(rInfo2.nOrg), aSortParam.maKeyState[nSort].nField );
}
} while ( nRes == 0 && ++nSort < pArray->GetUsedSorts() );
if ( nRes == 0 )
{
ScSortInfo& rInfo1 = pArray->Get( 0 , nIndex1 );
ScSortInfo& rInfo2 = pArray->Get( 0 , nIndex2 );
if ( rInfo1.nOrg < rInfo2.nOrg )
nRes = -1 ;
else if ( rInfo1.nOrg > rInfo2.nOrg )
nRes = 1 ;
}
return nRes;
}
short ScInterpreter::CompareCell( sal_uInt16 nSort,
ScRefCellValue& rCell1, ScRefCellValue& rCell2 ) const
{
short nRes = 0 ;
CellType eType1 = rCell1.getType(), eType2 = rCell2.getType();
if (!rCell1.isEmpty())
{
if (!rCell2.isEmpty())
{
bool bErr1 = false ;
bool bStr1 = ( eType1 != CELLTYPE_VALUE );
if (eType1 == CELLTYPE_FORMULA)
{
if (rCell1.getFormula()->GetErrCode() != FormulaError::NONE)
{
bErr1 = true ;
bStr1 = false ;
}
else if (rCell1.getFormula()->IsValue())
{
bStr1 = false ;
}
}
bool bErr2 = false ;
bool bStr2 = ( eType2 != CELLTYPE_VALUE );
if (eType2 == CELLTYPE_FORMULA)
{
if (rCell2.getFormula()->GetErrCode() != FormulaError::NONE)
{
bErr2 = true ;
bStr2 = false ;
}
else if (rCell2.getFormula()->IsValue())
{
bStr2 = false ;
}
}
if ( bStr1 && bStr2 ) // only compare strings as strings!
{
OUString aStr1;
OUString aStr2;
if (eType1 == CELLTYPE_STRING)
aStr1 = rCell1.getSharedString()->getString();
else
aStr1 = rCell1.getString(mrDoc);
if (eType2 == CELLTYPE_STRING)
aStr2 = rCell2.getSharedString()->getString();
else
aStr2 = rCell2.getString(mrDoc);
CollatorWrapper& rSortCollator = ScGlobal::GetCollator(aSortParam.bCaseSens);
nRes = static_cast <short >( rSortCollator.compareString( aStr1, aStr2 ) );
}
else if ( bStr1 ) // String <-> Number or Error
{
if (bErr2)
nRes = -1 ; // String in front of Error
else
nRes = 1 ; // Number in front of String
}
else if ( bStr2 ) // Number or Error <-> String
{
if (bErr1)
nRes = 1 ; // String in front of Error
else
nRes = -1 ; // Number in front of String
}
else if (bErr1 && bErr2)
{
// nothing, two Errors are equal
}
else if (bErr1) // Error <-> Number
{
nRes = 1 ; // Number in front of Error
}
else if (bErr2) // Number <-> Error
{
nRes = -1 ; // Number in front of Error
}
else // Mixed numbers
{
double nVal1 = rCell1.getValue();
double nVal2 = rCell2.getValue();
if (nVal1 < nVal2)
nRes = -1 ;
else if (nVal1 > nVal2)
nRes = 1 ;
}
if ( !aSortParam.maKeyState[nSort].bAscending )
nRes = -nRes;
}
else
nRes = -1 ;
}
else
{
if (!rCell2.isEmpty())
nRes = 1 ;
else
nRes = 0 ; // both empty
}
return nRes;
}
short ScInterpreter::CompareMatrixCell( const ScMatrixRef& pMatSrc, sal_uInt16 nSort, SCCOL nCell1Col, SCROW nCell1Row,
SCCOL nCell2Col, SCROW nCell2Row ) const
{
short nRes = 0 ;
// 1st value
bool bCell1Empty = false ;
bool bCell1Value = false ;
if (pMatSrc->IsEmpty(nCell1Col, nCell1Row))
bCell1Empty = true ;
else if (pMatSrc->IsStringOrEmpty(nCell1Col, nCell1Row))
bCell1Value = false ;
else
bCell1Value = true ;
// 2nd value
bool bCell2Empty = false ;
bool bCell2Value = false ;
if (pMatSrc->IsEmpty(nCell2Col, nCell2Row))
bCell2Empty = true ;
else if (pMatSrc->IsStringOrEmpty(nCell2Col, nCell2Row))
bCell2Value = false ;
else
bCell2Value = true ;
if (!bCell1Empty)
{
if (!bCell2Empty)
{
if ( !bCell1Value && !bCell2Value ) // only compare strings as strings!
{
OUString aStr1 = pMatSrc->GetString(nCell1Col, nCell1Row).getString();
OUString aStr2 = pMatSrc->GetString(nCell2Col, nCell2Row).getString();
CollatorWrapper& rSortCollator = ScGlobal::GetCollator(aSortParam.bCaseSens);
nRes = static_cast <short >( rSortCollator.compareString( aStr1, aStr2 ) );
}
else if ( !bCell1Value ) // String <-> Number or Error
{
nRes = 1 ; // Number in front of String
}
else if ( !bCell2Value ) // Number or Error <-> String
{
nRes = -1 ; // Number in front of String
}
else // Mixed numbers
{
double nVal1 = pMatSrc->GetDouble(nCell1Col, nCell1Row);
double nVal2 = pMatSrc->GetDouble(nCell2Col, nCell2Row);
if (nVal1 < nVal2)
nRes = -1 ;
else if (nVal1 > nVal2)
nRes = 1 ;
}
if ( !aSortParam.maKeyState[nSort].bAscending )
nRes = -nRes;
}
else
nRes = -1 ;
}
else
{
if (!bCell2Empty)
nRes = 1 ;
else
nRes = 0 ; // both empty
}
return nRes;
}
void ScInterpreter::GetSortArray( sal_uInt8 nParamCount, vector<double >& rSortArray, vector<tools::Long >* pIndexOrder, bool bConvertTextInArray, bool bAllowEmptyArray )
{
GetNumberSequenceArray( nParamCount, rSortArray, bConvertTextInArray );
if (rSortArray.size() > MAX_COUNT_DOUBLE_FOR_SORT(mrDoc.GetSheetLimits()))
SetError( FormulaError::MatrixSize);
else if ( rSortArray.empty() )
{
if ( bAllowEmptyArray )
return ;
SetError( FormulaError::NoValue);
}
if (nGlobalError == FormulaError::NONE)
QuickSort( rSortArray, pIndexOrder);
}
static void lcl_QuickSort( tools::Long nLo, tools::Long nHi, vector<double >& rSortArray, vector<tools::Long >* pIndexOrder )
{
// If pIndexOrder is not NULL, we assume rSortArray.size() == pIndexOrder->size().
using ::std::swap;
if (nHi - nLo == 1 )
{
if (rSortArray[nLo] > rSortArray[nHi])
{
swap(rSortArray[nLo], rSortArray[nHi]);
if (pIndexOrder)
swap(pIndexOrder->at(nLo), pIndexOrder->at(nHi));
}
return ;
}
tools::Long ni = nLo;
tools::Long nj = nHi;
do
{
double fLo = rSortArray[nLo];
while (ni <= nHi && rSortArray[ni] < fLo) ni++;
while (nj >= nLo && fLo < rSortArray[nj]) nj--;
if (ni <= nj)
{
if (ni != nj)
{
swap(rSortArray[ni], rSortArray[nj]);
if (pIndexOrder)
swap(pIndexOrder->at(ni), pIndexOrder->at(nj));
}
++ni;
--nj;
}
}
while (ni < nj);
if ((nj - nLo) < (nHi - ni))
{
if (nLo < nj) lcl_QuickSort(nLo, nj, rSortArray, pIndexOrder);
if (ni < nHi) lcl_QuickSort(ni, nHi, rSortArray, pIndexOrder);
}
else
{
if (ni < nHi) lcl_QuickSort(ni, nHi, rSortArray, pIndexOrder);
if (nLo < nj) lcl_QuickSort(nLo, nj, rSortArray, pIndexOrder);
}
}
void ScInterpreter::QuickSort( vector<double >& rSortArray, vector<tools::Long >* pIndexOrder )
{
tools::Long n = static_cast <tools::Long >(rSortArray.size());
if (pIndexOrder)
{
pIndexOrder->clear();
pIndexOrder->reserve(n);
for (tools::Long i = 0 ; i < n; ++i)
pIndexOrder->push_back(i);
}
if (n < 2 )
return ;
size_t nValCount = rSortArray.size();
for (size_t i = 0 ; (i + 4 ) <= nValCount-1 ; i += 4 )
{
size_t nInd = comphelper::rng::uniform_size_distribution(0 , nValCount-2 );
::std::swap( rSortArray[i], rSortArray[nInd]);
if (pIndexOrder)
::std::swap( pIndexOrder->at(i), pIndexOrder->at(nInd));
}
lcl_QuickSort(0 , n-1 , rSortArray, pIndexOrder);
}
void ScInterpreter::ScRank( bool bAverage )
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 2 , 3 ) )
return ;
bool bAscending;
if ( nParamCount == 3 )
bAscending = GetBool();
else
bAscending = false ;
vector<double > aSortArray;
GetSortArray( 1 , aSortArray, nullptr, false , false );
double fVal = GetDouble();
SCSIZE nSize = aSortArray.size();
if ( nSize == 0 || nGlobalError != FormulaError::NONE )
PushNoValue();
else
{
if ( fVal < aSortArray[ 0 ] || fVal > aSortArray[ nSize - 1 ] )
PushError( FormulaError::NotAvailable);
else
{
double fLastPos = 0 ;
double fFirstPos = -1 .0 ;
bool bFinished = false ;
SCSIZE i;
for (i = 0 ; i < nSize && !bFinished; i++)
{
if ( aSortArray[ i ] == fVal )
{
if ( fFirstPos < 0 )
fFirstPos = i + 1 .0 ;
}
else
{
if ( aSortArray[ i ] > fVal )
{
fLastPos = i;
bFinished = true ;
}
}
}
if ( !bFinished )
fLastPos = i;
if (fFirstPos <= 0 )
{
PushError( FormulaError::NotAvailable);
}
else if ( !bAverage )
{
if ( bAscending )
PushDouble( fFirstPos );
else
PushDouble( nSize + 1 .0 - fLastPos );
}
else
{
if ( bAscending )
PushDouble( ( fFirstPos + fLastPos ) / 2 .0 );
else
PushDouble( nSize + 1 .0 - ( fFirstPos + fLastPos ) / 2 .0 );
}
}
}
}
void ScInterpreter::ScAveDev()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCountMin( nParamCount, 1 ) )
return ;
sal_uInt16 SaveSP = sp;
double nMiddle = 0 .0 ;
KahanSum rVal = 0 .0 ;
double rValCount = 0 .0 ;
ScAddress aAdr;
ScRange aRange;
short nParam = nParamCount;
size_t nRefInList = 0 ;
while (nParam-- > 0 )
{
switch (GetStackType())
{
case svDouble :
rVal += GetDouble();
rValCount++;
break ;
case svSingleRef :
{
PopSingleRef( aAdr );
ScRefCellValue aCell(mrDoc, aAdr);
if (aCell.hasNumeric())
{
rVal += GetCellValue(aAdr, aCell);
rValCount++;
}
}
break ;
case svDoubleRef :
case svRefList :
{
FormulaError nErr = FormulaError::NONE;
double nCellVal;
PopDoubleRef( aRange, nParam, nRefInList);
ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags );
if (aValIter.GetFirst(nCellVal, nErr))
{
rVal += nCellVal;
rValCount++;
SetError(nErr);
while ((nErr == FormulaError::NONE) && aValIter.GetNext(nCellVal, nErr))
{
rVal += nCellVal;
rValCount++;
}
SetError(nErr);
}
}
break ;
case svMatrix :
case svExternalSingleRef:
case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix();
if (pMat)
{
SCSIZE nCount = pMat->GetElementCount();
if (pMat->IsNumeric())
{
for (SCSIZE nElem = 0 ; nElem < nCount; nElem++)
{
rVal += pMat->GetDouble(nElem);
rValCount++;
}
}
else
{
for (SCSIZE nElem = 0 ; nElem < nCount; nElem++)
if (!pMat->IsStringOrEmpty(nElem))
{
rVal += pMat->GetDouble(nElem);
rValCount++;
}
}
}
}
break ;
default :
SetError(FormulaError::IllegalParameter);
break ;
}
}
if (nGlobalError != FormulaError::NONE)
{
PushError( nGlobalError);
return ;
}
nMiddle = rVal.get() / rValCount;
sp = SaveSP;
rVal = 0 .0 ;
nParam = nParamCount;
nRefInList = 0 ;
while (nParam-- > 0 )
{
switch (GetStackType())
{
case svDouble :
rVal += std::abs(GetDouble() - nMiddle);
break ;
case svSingleRef :
{
PopSingleRef( aAdr );
ScRefCellValue aCell(mrDoc, aAdr);
if (aCell.hasNumeric())
rVal += std::abs(GetCellValue(aAdr, aCell) - nMiddle);
}
break ;
case svDoubleRef :
case svRefList :
{
FormulaError nErr = FormulaError::NONE;
double nCellVal;
PopDoubleRef( aRange, nParam, nRefInList);
ScValueIterator aValIter( mrContext, aRange, mnSubTotalFlags );
if (aValIter.GetFirst(nCellVal, nErr))
{
rVal += std::abs(nCellVal - nMiddle);
while (aValIter.GetNext(nCellVal, nErr))
rVal += std::abs(nCellVal - nMiddle);
}
}
break ;
case svMatrix :
case svExternalSingleRef:
case svExternalDoubleRef:
{
ScMatrixRef pMat = GetMatrix();
if (pMat)
{
SCSIZE nCount = pMat->GetElementCount();
if (pMat->IsNumeric())
{
for (SCSIZE nElem = 0 ; nElem < nCount; nElem++)
{
rVal += std::abs(pMat->GetDouble(nElem) - nMiddle);
}
}
else
{
for (SCSIZE nElem = 0 ; nElem < nCount; nElem++)
{
if (!pMat->IsStringOrEmpty(nElem))
rVal += std::abs(pMat->GetDouble(nElem) - nMiddle);
}
}
}
}
break ;
default : SetError(FormulaError::IllegalParameter); break ;
}
}
PushDouble(rVal.get() / rValCount);
}
void ScInterpreter::ScDevSq()
{
auto VarResult = []( double fVal, size_t /*nValCount*/ )
{
return fVal;
};
GetStVarParams( false /*bTextAsZero*/, VarResult);
}
void ScInterpreter::ScProbability()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 3 , 4 ) )
return ;
double fUp, fLo;
fUp = GetDouble();
if (nParamCount == 4 )
fLo = GetDouble();
else
fLo = fUp;
if (fLo > fUp)
std::swap( fLo, fUp );
ScMatrixRef pMatP = GetMatrix();
ScMatrixRef pMatW = GetMatrix();
if (!pMatP || !pMatW)
PushIllegalParameter();
else
{
SCSIZE nC1, nC2;
SCSIZE nR1, nR2;
pMatP->GetDimensions(nC1, nR1);
pMatW->GetDimensions(nC2, nR2);
if (nC1 != nC2 || nR1 != nR2 || nC1 == 0 || nR1 == 0 ||
nC2 == 0 || nR2 == 0 )
PushNA();
else
{
KahanSum fSum = 0 .0 ;
KahanSum fRes = 0 .0 ;
bool bStop = false ;
double fP, fW;
for ( SCSIZE i = 0 ; i < nC1 && !bStop; i++ )
{
for (SCSIZE j = 0 ; j < nR1 && !bStop; ++j )
{
if (pMatP->IsValue(i,j) && pMatW->IsValue(i,j))
{
fP = pMatP->GetDouble(i,j);
fW = pMatW->GetDouble(i,j);
if (fP < 0 .0 || fP > 1 .0 )
bStop = true ;
else
{
fSum += fP;
if (fW >= fLo && fW <= fUp)
fRes += fP;
}
}
else
SetError( FormulaError::IllegalArgument);
}
}
if (bStop || std::abs((fSum -1 .0 ).get()) > 1 .0 E-7 )
PushNoValue();
else
PushDouble(fRes.get());
}
}
}
void ScInterpreter::ScCorrel()
{
// This is identical to ScPearson()
ScPearson();
}
void ScInterpreter::ScCovarianceP()
{
CalculatePearsonCovar( false , false , false );
}
void ScInterpreter::ScCovarianceS()
{
CalculatePearsonCovar( false , false , true );
}
void ScInterpreter::ScPearson()
{
CalculatePearsonCovar( true , false , false );
}
void ScInterpreter::CalculatePearsonCovar( bool _bPearson, bool _bStexy, bool _bSample )
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
ScMatrixRef pMat1 = GetMatrix();
ScMatrixRef pMat2 = GetMatrix();
if (!pMat1 || !pMat2)
{
PushIllegalParameter();
return ;
}
SCSIZE nC1, nC2;
SCSIZE nR1, nR2;
pMat1->GetDimensions(nC1, nR1);
pMat2->GetDimensions(nC2, nR2);
if (nR1 != nR2 || nC1 != nC2)
{
PushIllegalArgument();
return ;
}
/* #i78250#
* ( sum ( ( X - MeanX ) ( Y - MeanY ) ) ) / N equals ( SumXY ) / N - MeanX * MeanY mathematically ,
* but the latter produces wrong results if the absolute values are high ,
* for example above 10 ^ 8
*/
double fCount = 0 .0 ;
KahanSum fSumX = 0 .0 ;
KahanSum fSumY = 0 .0 ;
for (SCSIZE i = 0 ; i < nC1; i++)
{
for (SCSIZE j = 0 ; j < nR1; j++)
{
if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j))
{
fSumX += pMat1->GetDouble(i,j);
fSumY += pMat2->GetDouble(i,j);
fCount++;
}
}
}
if (fCount < (_bStexy ? 3 .0 : (_bSample ? 2 .0 : 1 .0 )))
PushNoValue();
else
{
KahanSum fSumDeltaXDeltaY = 0 .0 ; // sum of (ValX-MeanX)*(ValY-MeanY)
KahanSum fSumSqrDeltaX = 0 .0 ; // sum of (ValX-MeanX)^2
KahanSum fSumSqrDeltaY = 0 .0 ; // sum of (ValY-MeanY)^2
const double fMeanX = fSumX.get() / fCount;
const double fMeanY = fSumY.get() / fCount;
for (SCSIZE i = 0 ; i < nC1; i++)
{
for (SCSIZE j = 0 ; j < nR1; j++)
{
if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j))
{
const double fValX = pMat1->GetDouble(i,j);
const double fValY = pMat2->GetDouble(i,j);
fSumDeltaXDeltaY += (fValX - fMeanX) * (fValY - fMeanY);
if ( _bPearson )
{
fSumSqrDeltaX += (fValX - fMeanX) * (fValX - fMeanX);
fSumSqrDeltaY += (fValY - fMeanY) * (fValY - fMeanY);
}
}
}
}
if ( _bPearson )
{
// tdf#94962 - Values below the numerical limit lead to unexpected results
if (fSumSqrDeltaX < ::std::numeric_limits<double >::min()
|| (!_bStexy && fSumSqrDeltaY < ::std::numeric_limits<double >::min()))
PushError( FormulaError::DivisionByZero);
else if ( _bStexy )
PushDouble( sqrt( ( fSumSqrDeltaY - fSumDeltaXDeltaY *
fSumDeltaXDeltaY / fSumSqrDeltaX ).get() / (fCount-2 )));
else
PushDouble( fSumDeltaXDeltaY.get() / sqrt( fSumSqrDeltaX.get() * fSumSqrDeltaY.get() ));
}
else
{
if ( _bSample )
PushDouble( fSumDeltaXDeltaY.get() / ( fCount - 1 ) );
else
PushDouble( fSumDeltaXDeltaY.get() / fCount);
}
}
}
void ScInterpreter::ScRSQ()
{
// Same as ScPearson()*ScPearson()
ScPearson();
if (nGlobalError != FormulaError::NONE)
return ;
switch (GetStackType())
{
case svDouble:
{
double fVal = PopDouble();
PushDouble( fVal * fVal);
}
break ;
default :
PopError();
PushNoValue();
}
}
void ScInterpreter::ScSTEYX()
{
CalculatePearsonCovar( true , true , false );
}
void ScInterpreter::CalculateSlopeIntercept(bool bSlope)
{
if ( !MustHaveParamCount( GetByte(), 2 ) )
return ;
ScMatrixRef pMat1 = GetMatrix();
ScMatrixRef pMat2 = GetMatrix();
if (!pMat1 || !pMat2)
{
PushIllegalParameter();
return ;
}
SCSIZE nC1, nC2;
SCSIZE nR1, nR2;
pMat1->GetDimensions(nC1, nR1);
pMat2->GetDimensions(nC2, nR2);
if (nR1 != nR2 || nC1 != nC2)
{
PushIllegalArgument();
return ;
}
// #i78250# numerical stability improved
double fCount = 0 .0 ;
KahanSum fSumX = 0 .0 ;
KahanSum fSumY = 0 .0 ;
for (SCSIZE i = 0 ; i < nC1; i++)
{
for (SCSIZE j = 0 ; j < nR1; j++)
{
if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j))
{
fSumX += pMat1->GetDouble(i,j);
fSumY += pMat2->GetDouble(i,j);
fCount++;
}
}
}
if (fCount < 1 .0 )
PushNoValue();
else
{
KahanSum fSumDeltaXDeltaY = 0 .0 ; // sum of (ValX-MeanX)*(ValY-MeanY)
KahanSum fSumSqrDeltaX = 0 .0 ; // sum of (ValX-MeanX)^2
double fMeanX = fSumX.get() / fCount;
double fMeanY = fSumY.get() / fCount;
for (SCSIZE i = 0 ; i < nC1; i++)
{
for (SCSIZE j = 0 ; j < nR1; j++)
{
if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j))
{
double fValX = pMat1->GetDouble(i,j);
double fValY = pMat2->GetDouble(i,j);
fSumDeltaXDeltaY += (fValX - fMeanX) * (fValY - fMeanY);
fSumSqrDeltaX += (fValX - fMeanX) * (fValX - fMeanX);
}
}
}
if (fSumSqrDeltaX == 0 .0 )
PushError( FormulaError::DivisionByZero);
else
{
if ( bSlope )
PushDouble( fSumDeltaXDeltaY.get() / fSumSqrDeltaX.get());
else
PushDouble( fMeanY - fSumDeltaXDeltaY.get() / fSumSqrDeltaX.get() * fMeanX);
}
}
}
void ScInterpreter::ScSlope()
{
CalculateSlopeIntercept(true );
}
void ScInterpreter::ScIntercept()
{
CalculateSlopeIntercept(false );
}
void ScInterpreter::ScForecast()
{
if ( !MustHaveParamCount( GetByte(), 3 ) )
return ;
ScMatrixRef pMat1 = GetMatrix();
ScMatrixRef pMat2 = GetMatrix();
if (!pMat1 || !pMat2)
{
PushIllegalParameter();
return ;
}
SCSIZE nC1, nC2;
SCSIZE nR1, nR2;
pMat1->GetDimensions(nC1, nR1);
pMat2->GetDimensions(nC2, nR2);
if (nR1 != nR2 || nC1 != nC2)
{
PushIllegalArgument();
return ;
}
double fVal = GetDouble();
// #i78250# numerical stability improved
double fCount = 0 .0 ;
KahanSum fSumX = 0 .0 ;
KahanSum fSumY = 0 .0 ;
for (SCSIZE i = 0 ; i < nC1; i++)
{
for (SCSIZE j = 0 ; j < nR1; j++)
{
if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j))
{
fSumX += pMat1->GetDouble(i,j);
fSumY += pMat2->GetDouble(i,j);
fCount++;
}
}
}
if (fCount < 1 .0 )
PushNoValue();
else
{
KahanSum fSumDeltaXDeltaY = 0 .0 ; // sum of (ValX-MeanX)*(ValY-MeanY)
KahanSum fSumSqrDeltaX = 0 .0 ; // sum of (ValX-MeanX)^2
double fMeanX = fSumX.get() / fCount;
double fMeanY = fSumY.get() / fCount;
for (SCSIZE i = 0 ; i < nC1; i++)
{
for (SCSIZE j = 0 ; j < nR1; j++)
{
if (!pMat1->IsStringOrEmpty(i,j) && !pMat2->IsStringOrEmpty(i,j))
{
double fValX = pMat1->GetDouble(i,j);
double fValY = pMat2->GetDouble(i,j);
fSumDeltaXDeltaY += (fValX - fMeanX) * (fValY - fMeanY);
fSumSqrDeltaX += (fValX - fMeanX) * (fValX - fMeanX);
}
}
}
if (fSumSqrDeltaX == 0 .0 )
PushError( FormulaError::DivisionByZero);
else
PushDouble( fMeanY + fSumDeltaXDeltaY.get() / fSumSqrDeltaX.get() * (fVal - fMeanX));
}
}
static void lcl_roundUpNearestPow2(SCSIZE& nNum, SCSIZE& nNumBits)
{
// Find the least power of 2 that is less than or equal to nNum.
SCSIZE nPow2(1 );
nNumBits = std::numeric_limits<SCSIZE>::digits;
nPow2 <<= (nNumBits - 1 );
while (nPow2 >= 1 )
{
if (nNum & nPow2)
break ;
--nNumBits;
nPow2 >>= 1 ;
}
if (nPow2 != nNum)
{
assert(nPow2 < 1 UL << (std::numeric_limits<unsigned long >::digits - 1 ));
nNum = nPow2 ? (nPow2 << 1 ) : 1 ;
}
else
--nNumBits;
}
static SCSIZE lcl_bitReverse(SCSIZE nIn, SCSIZE nBound)
{
SCSIZE nOut = 0 ;
for (SCSIZE nMask = 1 ; nMask < nBound; nMask <<= 1 )
{
nOut <<= 1 ;
if (nIn & nMask)
nOut |= 1 ;
}
return nOut;
}
namespace {
// Computes and stores twiddle factors for computing DFT later.
struct ScTwiddleFactors
{
ScTwiddleFactors(SCSIZE nN, bool bInverse) :
mfWReal(nN),
mfWImag(nN),
mnN(nN),
mbInverse(bInverse)
{}
void Compute();
void Conjugate()
{
mbInverse = !mbInverse;
for (SCSIZE nIdx = 0 ; nIdx < mnN; ++nIdx)
mfWImag[nIdx] = -mfWImag[nIdx];
}
std::vector<double > mfWReal;
std::vector<double > mfWImag;
SCSIZE mnN;
bool mbInverse;
};
}
void ScTwiddleFactors::Compute()
{
mfWReal.resize(mnN);
mfWImag.resize(mnN);
double nW = (mbInverse ? 2 : -2 )*M_PI/static_cast <double >(mnN);
if (mnN == 1 )
{
mfWReal[0 ] = 1 .0 ;
mfWImag[0 ] = 0 .0 ;
}
else if (mnN == 2 )
{
mfWReal[0 ] = 1 ;
mfWImag[0 ] = 0 ;
mfWReal[1 ] = -1 ;
mfWImag[1 ] = 0 ;
}
else if (mnN == 4 )
{
mfWReal[0 ] = 1 ;
mfWImag[0 ] = 0 ;
mfWReal[1 ] = 0 ;
mfWImag[1 ] = (mbInverse ? 1 .0 : -1 .0 );
mfWReal[2 ] = -1 ;
mfWImag[2 ] = 0 ;
mfWReal[3 ] = 0 ;
mfWImag[3 ] = (mbInverse ? -1 .0 : 1 .0 );
}
else if ((mnN % 4 ) == 0 )
{
const SCSIZE nQSize = mnN >> 2 ;
// Compute cos of the start quadrant.
// This is the first quadrant if mbInverse == true, else it is the fourth quadrant.
for (SCSIZE nIdx = 0 ; nIdx <= nQSize; ++nIdx)
mfWReal[nIdx] = cos(nW*static_cast <double >(nIdx));
if (mbInverse)
{
const SCSIZE nQ1End = nQSize;
// First quadrant
for (SCSIZE nIdx = 0 ; nIdx <= nQ1End; ++nIdx)
mfWImag[nIdx] = mfWReal[nQ1End-nIdx];
// Second quadrant
const SCSIZE nQ2End = nQ1End << 1 ;
for (SCSIZE nIdx = nQ1End+1 ; nIdx <= nQ2End; ++nIdx)
{
mfWReal[nIdx] = -mfWReal[nQ2End - nIdx];
mfWImag[nIdx] = mfWImag[nQ2End - nIdx];
}
// Third quadrant
const SCSIZE nQ3End = nQ2End + nQ1End;
for (SCSIZE nIdx = nQ2End+1 ; nIdx <= nQ3End; ++nIdx)
{
mfWReal[nIdx] = -mfWReal[nIdx - nQ2End];
mfWImag[nIdx] = -mfWImag[nIdx - nQ2End];
}
// Fourth Quadrant
for (SCSIZE nIdx = nQ3End+1 ; nIdx < mnN; ++nIdx)
{
mfWReal[nIdx] = mfWReal[mnN - nIdx];
mfWImag[nIdx] = -mfWImag[mnN - nIdx];
}
}
else
{
const SCSIZE nQ4End = nQSize;
const SCSIZE nQ3End = nQSize << 1 ;
const SCSIZE nQ2End = nQ3End + nQSize;
// Fourth quadrant.
for (SCSIZE nIdx = 0 ; nIdx <= nQ4End; ++nIdx)
mfWImag[nIdx] = -mfWReal[nQ4End - nIdx];
// Third quadrant.
for (SCSIZE nIdx = nQ4End+1 ; nIdx <= nQ3End; ++nIdx)
{
mfWReal[nIdx] = -mfWReal[nQ3End - nIdx];
mfWImag[nIdx] = mfWImag[nQ3End - nIdx];
}
// Second quadrant.
for (SCSIZE nIdx = nQ3End+1 ; nIdx <= nQ2End; ++nIdx)
{
mfWReal[nIdx] = -mfWReal[nIdx - nQ3End];
mfWImag[nIdx] = -mfWImag[nIdx - nQ3End];
}
// First quadrant.
for (SCSIZE nIdx = nQ2End+1 ; nIdx < mnN; ++nIdx)
{
mfWReal[nIdx] = mfWReal[mnN - nIdx];
mfWImag[nIdx] = -mfWImag[mnN - nIdx];
}
}
}
else
{
for (SCSIZE nIdx = 0 ; nIdx < mnN; ++nIdx)
{
double fAngle = nW*static_cast <double >(nIdx);
mfWReal[nIdx] = cos(fAngle);
mfWImag[nIdx] = sin(fAngle);
}
}
}
namespace {
// A radix-2 decimation in time FFT algorithm for complex valued input.
class ScComplexFFT2
{
public :
// rfArray.size() would always be even and a power of 2. (asserted in prepare())
// rfArray's first half contains the real parts and the later half contains the imaginary parts.
ScComplexFFT2(std::vector<double >& raArray, bool bInverse, bool bPolar, double fMinMag,
ScTwiddleFactors& rTF, bool bSubSampleTFs = false , bool bDisableNormalize = false ) :
mrArray(raArray),
mfWReal(rTF.mfWReal),
mfWImag(rTF.mfWImag),
mnPoints(raArray.size()/2 ),
mnStages(0 ),
mfMinMag(fMinMag),
mbInverse(bInverse),
mbPolar(bPolar),
mbDisableNormalize(bDisableNormalize),
mbSubSampleTFs(bSubSampleTFs)
{}
void Compute();
private :
void prepare();
double getReal(SCSIZE nIdx)
{
return mrArray[nIdx];
}
void setReal(double fVal, SCSIZE nIdx)
{
mrArray[nIdx] = fVal;
}
double getImag(SCSIZE nIdx)
{
return mrArray[mnPoints + nIdx];
}
void setImag(double fVal, SCSIZE nIdx)
{
mrArray[mnPoints + nIdx] = fVal;
}
SCSIZE getTFactorIndex(SCSIZE nPtIndex, SCSIZE nTfIdxScaleBits)
{
return ( ( nPtIndex << nTfIdxScaleBits ) & ( mnPoints - 1 ) ); // (x & (N-1)) is same as (x % N) but faster.
}
void computeFly(SCSIZE nTopIdx, SCSIZE nBottomIdx, SCSIZE nWIdx1, SCSIZE nWIdx2)
{
if (mbSubSampleTFs)
{
nWIdx1 <<= 1 ;
nWIdx2 <<= 1 ;
}
const double x1r = getReal(nTopIdx);
const double x2r = getReal(nBottomIdx);
const double & w1r = mfWReal[nWIdx1];
const double & w1i = mfWImag[nWIdx1];
const double & w2r = mfWReal[nWIdx2];
const double & w2i = mfWImag[nWIdx2];
const double x1i = getImag(nTopIdx);
const double x2i = getImag(nBottomIdx);
setReal(x1r + x2r*w1r - x2i*w1i, nTopIdx);
setImag(x1i + x2i*w1r + x2r*w1i, nTopIdx);
setReal(x1r + x2r*w2r - x2i*w2i, nBottomIdx);
setImag(x1i + x2i*w2r + x2r*w2i, nBottomIdx);
}
std::vector<double >& mrArray;
std::vector<double >& mfWReal;
std::vector<double >& mfWImag;
SCSIZE mnPoints;
SCSIZE mnStages;
double mfMinMag;
bool mbInverse:1 ;
bool mbPolar:1 ;
bool mbDisableNormalize:1 ;
bool mbSubSampleTFs:1 ;
};
}
void ScComplexFFT2::prepare()
{
SCSIZE nPoints = mnPoints;
lcl_roundUpNearestPow2(nPoints, mnStages);
assert(nPoints == mnPoints);
// Reorder array by bit-reversed indices.
for (SCSIZE nIdx = 0 ; nIdx < mnPoints; ++nIdx)
{
SCSIZE nRevIdx = lcl_bitReverse(nIdx, mnPoints);
if (nIdx < nRevIdx)
{
double fTmp = getReal(nIdx);
setReal(getReal(nRevIdx), nIdx);
setReal(fTmp, nRevIdx);
fTmp = getImag(nIdx);
setImag(getImag(nRevIdx), nIdx);
setImag(fTmp, nRevIdx);
}
}
}
static void lcl_normalize(std::vector<double >& rCmplxArray, bool bScaleOnlyReal)
{
const SCSIZE nPoints = rCmplxArray.size()/2 ;
const double fScale = 1 .0 /static_cast <double >(nPoints);
// Scale the real part
for (SCSIZE nIdx = 0 ; nIdx < nPoints; ++nIdx)
rCmplxArray[nIdx] *= fScale;
if (!bScaleOnlyReal)
{
const SCSIZE nLen = nPoints*2 ;
for (SCSIZE nIdx = nPoints; nIdx < nLen; ++nIdx)
rCmplxArray[nIdx] *= fScale;
}
}
static void lcl_convertToPolar(std::vector<double >& rCmplxArray, double fMinMag)
{
const SCSIZE nPoints = rCmplxArray.size()/2 ;
double fMag, fPhase, fR, fI;
for (SCSIZE nIdx = 0 ; nIdx < nPoints; ++nIdx)
{
fR = rCmplxArray[nIdx];
fI = rCmplxArray[nPoints+nIdx];
fMag = std::hypot(fR, fI);
if (fMag < fMinMag)
{
fMag = 0 .0 ;
fPhase = 0 .0 ;
}
else
{
fPhase = atan2(fI, fR);
}
rCmplxArray[nIdx] = fMag;
rCmplxArray[nPoints+nIdx] = fPhase;
}
}
void ScComplexFFT2::Compute()
{
prepare();
const SCSIZE nFliesInStage = mnPoints/2 ;
for (SCSIZE nStage = 0 ; nStage < mnStages; ++nStage)
{
const SCSIZE nTFIdxScaleBits = mnStages - nStage - 1 ; // Twiddle factor index's scale factor in bits.
const SCSIZE nFliesInGroup = SCSIZE(1 ) << nStage;
const SCSIZE nGroups = nFliesInStage/nFliesInGroup;
const SCSIZE nFlyWidth = nFliesInGroup;
for (SCSIZE nGroup = 0 , nFlyTopIdx = 0 ; nGroup < nGroups; ++nGroup)
{
for (SCSIZE nFly = 0 ; nFly < nFliesInGroup; ++nFly, ++nFlyTopIdx)
{
SCSIZE nFlyBottomIdx = nFlyTopIdx + nFlyWidth;
SCSIZE nWIdx1 = getTFactorIndex(nFlyTopIdx, nTFIdxScaleBits);
SCSIZE nWIdx2 = getTFactorIndex(nFlyBottomIdx, nTFIdxScaleBits);
computeFly(nFlyTopIdx, nFlyBottomIdx, nWIdx1, nWIdx2);
}
nFlyTopIdx += nFlyWidth;
}
}
if (mbPolar)
lcl_convertToPolar(mrArray, mfMinMag);
// Normalize after converting to polar, so we have a chance to
// save O(mnPoints) flops.
if (mbInverse && !mbDisableNormalize)
lcl_normalize(mrArray, mbPolar);
}
namespace {
// Bluestein's algorithm or chirp z-transform algorithm that can be used to
// compute DFT of a complex valued input of any length N in O(N lgN) time.
class ScComplexBluesteinFFT
{
public :
ScComplexBluesteinFFT(std::vector<double >& rArray, bool bReal, bool bInverse,
bool bPolar, double fMinMag, bool bDisableNormalize = false ) :
mrArray(rArray),
mnPoints(rArray.size()/2 ), // rArray should have space for imaginary parts even if real input.
mfMinMag(fMinMag),
mbReal(bReal),
mbInverse(bInverse),
mbPolar(bPolar),
mbDisableNormalize(bDisableNormalize)
{}
void Compute();
private :
std::vector<double >& mrArray;
const SCSIZE mnPoints;
double mfMinMag;
bool mbReal:1 ;
bool mbInverse:1 ;
bool mbPolar:1 ;
bool mbDisableNormalize:1 ;
};
}
void ScComplexBluesteinFFT::Compute()
{
std::vector<double > aRealScalars(mnPoints);
std::vector<double > aImagScalars(mnPoints);
double fW = (mbInverse ? 2 : -2 )*M_PI/static_cast <double >(mnPoints);
for (SCSIZE nIdx = 0 ; nIdx < mnPoints; ++nIdx)
{
double fAngle = 0 .5 *fW*static_cast <double >(nIdx*nIdx);
aRealScalars[nIdx] = cos(fAngle);
aImagScalars[nIdx] = sin(fAngle);
}
SCSIZE nMinSize = mnPoints*2 - 1 ;
SCSIZE nExtendedLength = nMinSize, nTmp = 0 ;
lcl_roundUpNearestPow2(nExtendedLength, nTmp);
std::vector<double > aASignal(nExtendedLength*2 ); // complex valued
std::vector<double > aBSignal(nExtendedLength*2 ); // complex valued
double fReal, fImag;
for (SCSIZE nIdx = 0 ; nIdx < mnPoints; ++nIdx)
{
// Real part of A signal.
aASignal[nIdx] = mrArray[nIdx]*aRealScalars[nIdx] + (mbReal ? 0 .0 : -mrArray[mnPoints+nIdx]*aImagScalars[nIdx]);
// Imaginary part of A signal.
aASignal[nExtendedLength + nIdx] = mrArray[nIdx]*aImagScalars[nIdx] + (mbReal ? 0 .0 : mrArray[mnPoints+nIdx]*aRealScalars[nIdx]);
// Real part of B signal.
aBSignal[nIdx] = fReal = aRealScalars[nIdx];
// Imaginary part of B signal.
aBSignal[nExtendedLength + nIdx] = fImag = -aImagScalars[nIdx]; // negative sign because B signal is the conjugation of the scalars.
if (nIdx)
{
// B signal needs a mirror of its part in 0 < n < mnPoints at the tail end.
aBSignal[nExtendedLength - nIdx] = fReal;
aBSignal[(nExtendedLength<<1 ) - nIdx] = fImag;
}
}
{
ScTwiddleFactors aTF(nExtendedLength, false /*not inverse*/);
aTF.Compute();
// Do complex-FFT2 of both A and B signal.
ScComplexFFT2 aFFT2A(aASignal, false /*not inverse*/, false /*no polar*/, 0.0 /* no clipping */,
aTF, false /*no subsample*/, true /* disable normalize */);
aFFT2A.Compute();
ScComplexFFT2 aFFT2B(aBSignal, false /*not inverse*/, false /*no polar*/, 0.0 /* no clipping */,
aTF, false /*no subsample*/, true /* disable normalize */);
aFFT2B.Compute();
double fAR, fAI, fBR, fBI;
for (SCSIZE nIdx = 0 ; nIdx < nExtendedLength; ++nIdx)
{
fAR = aASignal[nIdx];
fAI = aASignal[nExtendedLength + nIdx];
fBR = aBSignal[nIdx];
fBI = aBSignal[nExtendedLength + nIdx];
// Do point-wise product inplace in A signal.
aASignal[nIdx] = fAR*fBR - fAI*fBI;
aASignal[nExtendedLength + nIdx] = fAR*fBI + fAI*fBR;
}
// Do complex-inverse-FFT2 of aASignal.
aTF.Conjugate();
ScComplexFFT2 aFFT2AI(aASignal, true /*inverse*/, false /*no polar*/, 0.0 /* no clipping */, aTF); // Need normalization here.
aFFT2AI.Compute();
}
// Point-wise multiply with scalars.
for (SCSIZE nIdx = 0 ; nIdx < mnPoints; ++nIdx)
{
fReal = aASignal[nIdx];
fImag = aASignal[nExtendedLength + nIdx];
mrArray[nIdx] = fReal*aRealScalars[nIdx] - fImag*aImagScalars[nIdx]; // no conjugation needed here.
mrArray[mnPoints + nIdx] = fReal*aImagScalars[nIdx] + fImag*aRealScalars[nIdx];
}
// Normalize/Polar operations
if (mbPolar)
lcl_convertToPolar(mrArray, mfMinMag);
// Normalize after converting to polar, so we have a chance to
// save O(mnPoints) flops.
if (mbInverse && !mbDisableNormalize)
lcl_normalize(mrArray, mbPolar);
}
namespace {
// Computes DFT of an even length(N) real-valued input by using a
// ScComplexFFT2 if N == 2^k for some k or else by using a ScComplexBluesteinFFT
// with a complex valued input of length = N/2.
class ScRealFFT
{
public :
ScRealFFT(std::vector<double >& rInArray, std::vector<double >& rOutArray, bool bInverse,
bool bPolar, double fMinMag) :
mrInArray(rInArray),
mrOutArray(rOutArray),
mfMinMag(fMinMag),
mbInverse(bInverse),
mbPolar(bPolar)
{}
void Compute();
private :
std::vector<double >& mrInArray;
std::vector<double >& mrOutArray;
double mfMinMag;
bool mbInverse:1 ;
bool mbPolar:1 ;
};
}
void ScRealFFT::Compute()
{
// input length has to be even to do this optimization.
assert(mrInArray.size() % 2 == 0 );
assert(mrInArray.size()*2 == mrOutArray.size());
// nN is the number of points in the complex-fft input
// which will be half of the number of points in real array.
const SCSIZE nN = mrInArray.size()/2 ;
if (nN == 0 )
{
mrOutArray[0 ] = mrInArray[0 ];
mrOutArray[1 ] = 0 .0 ;
return ;
}
// work array should be the same length as mrInArray
std::vector<double > aWorkArray(nN*2 );
for (SCSIZE nIdx = 0 ; nIdx < nN; ++nIdx)
{
SCSIZE nDoubleIdx = 2 *nIdx;
// Use even elements as real part
aWorkArray[nIdx] = mrInArray[nDoubleIdx];
// and odd elements as imaginary part of the contrived complex sequence.
aWorkArray[nN+nIdx] = mrInArray[nDoubleIdx+1 ];
}
ScTwiddleFactors aTFs(nN*2 , mbInverse);
aTFs.Compute();
SCSIZE nNextPow2 = nN, nTmp = 0 ;
lcl_roundUpNearestPow2(nNextPow2, nTmp);
if (nNextPow2 == nN)
{
ScComplexFFT2 aFFT2(aWorkArray, mbInverse, false /*disable polar*/, 0.0 /* no clipping */,
aTFs, true /*subsample tf*/, true /*disable normalize*/);
aFFT2.Compute();
}
else
{
ScComplexBluesteinFFT aFFT(aWorkArray, false /*complex input*/, mbInverse, false /*disable polar*/,
0 .0 /* no clipping */, true /*disable normalize*/);
aFFT.Compute();
}
// Post process aWorkArray to populate mrOutArray
const SCSIZE nTwoN = 2 *nN, nThreeN = 3 *nN;
double fY1R, fY2R, fY1I, fY2I, fResR, fResI, fWR, fWI;
for (SCSIZE nIdx = 0 ; nIdx < nN; ++nIdx)
{
const SCSIZE nIdxRev = nIdx ? (nN - nIdx) : 0 ;
fY1R = aWorkArray[nIdx];
fY2R = aWorkArray[nIdxRev];
fY1I = aWorkArray[nN + nIdx];
fY2I = aWorkArray[nN + nIdxRev];
fWR = aTFs.mfWReal[nIdx];
fWI = aTFs.mfWImag[nIdx];
// mrOutArray has length = 4*nN
// Real part of the final output (only half of the symmetry around Nyquist frequency)
// Fills the first quarter.
mrOutArray[nIdx] = fResR = 0 .5 *(
fY1R + fY2R +
fWR * (fY1I + fY2I) +
fWI * (fY1R - fY2R) );
// Imaginary part of the final output (only half of the symmetry around Nyquist frequency)
// Fills the third quarter.
mrOutArray[nTwoN + nIdx] = fResI = 0 .5 *(
fY1I - fY2I +
fWI * (fY1I + fY2I) -
fWR * (fY1R - fY2R) );
// Fill the missing 2 quarters using symmetry argument.
if (nIdx)
{
// Fills the 2nd quarter.
mrOutArray[nN + nIdxRev] = fResR;
// Fills the 4th quarter.
mrOutArray[nThreeN + nIdxRev] = -fResI;
}
else
{
mrOutArray[nN] = fY1R - fY1I;
mrOutArray[nThreeN] = 0 .0 ;
}
}
// Normalize/Polar operations
if (mbPolar)
lcl_convertToPolar(mrOutArray, mfMinMag);
// Normalize after converting to polar, so we have a chance to
// save O(mnPoints) flops.
if (mbInverse)
lcl_normalize(mrOutArray, mbPolar);
}
using ScMatrixGenerator = ScMatrixRef(SCSIZE, SCSIZE, std::vector<double >&);
namespace {
// Generic FFT class that decides which FFT implementation to use.
class ScFFT
{
public :
ScFFT(ScMatrixRef& pMat, bool bReal, bool bInverse, bool bPolar, double fMinMag) :
mpInputMat(pMat),
mfMinMag(fMinMag),
mbReal(bReal),
mbInverse(bInverse),
mbPolar(bPolar)
{}
ScMatrixRef Compute(const std::function<ScMatrixGenerator>& rMatGenFunc);
private :
ScMatrixRef& mpInputMat;
double mfMinMag;
bool mbReal:1 ;
bool mbInverse:1 ;
bool mbPolar:1 ;
};
}
ScMatrixRef ScFFT::Compute(const std::function<ScMatrixGenerator>& rMatGenFunc)
{
std::vector<double > aArray;
mpInputMat->GetDoubleArray(aArray);
SCSIZE nPoints = mbReal ? aArray.size() : (aArray.size()/2 );
if (nPoints == 1 )
{
std::vector<double > aOutArray(2 );
aOutArray[0 ] = aArray[0 ];
aOutArray[1 ] = mbReal ? 0 .0 : aArray[1 ];
if (mbPolar)
lcl_convertToPolar(aOutArray, mfMinMag);
return rMatGenFunc(2 , 1 , aOutArray);
}
if (mbReal && (nPoints % 2 ) == 0 )
{
std::vector<double > aOutArray(nPoints*2 );
ScRealFFT aFFT(aArray, aOutArray, mbInverse, mbPolar, mfMinMag);
aFFT.Compute();
return rMatGenFunc(2 , nPoints, aOutArray);
}
SCSIZE nNextPow2 = nPoints, nTmp = 0 ;
lcl_roundUpNearestPow2(nNextPow2, nTmp);
if (nNextPow2 == nPoints && !mbReal)
{
ScTwiddleFactors aTF(nPoints, mbInverse);
aTF.Compute();
ScComplexFFT2 aFFT2(aArray, mbInverse, mbPolar, mfMinMag, aTF);
aFFT2.Compute();
return rMatGenFunc(2 , nPoints, aArray);
}
if (mbReal)
aArray.resize(nPoints*2 , 0 .0 );
ScComplexBluesteinFFT aFFT(aArray, mbReal, mbInverse, mbPolar, mfMinMag);
aFFT.Compute();
return rMatGenFunc(2 , nPoints, aArray);
}
void ScInterpreter::ScFourier()
{
sal_uInt8 nParamCount = GetByte();
if ( !MustHaveParamCount( nParamCount, 2 , 5 ) )
return ;
bool bInverse = false ;
bool bPolar = false ;
double fMinMag = 0 .0 ;
if (nParamCount == 5 )
{
if (IsMissing())
Pop();
else
fMinMag = GetDouble();
}
if (nParamCount >= 4 )
{
if (IsMissing())
Pop();
else
bPolar = GetBool();
}
if (nParamCount >= 3 )
{
if (IsMissing())
Pop();
else
bInverse = GetBool();
}
bool bGroupedByColumn = GetBool();
ScMatrixRef pInputMat = GetMatrix();
if (!pInputMat)
{
PushIllegalParameter();
return ;
}
SCSIZE nC, nR;
pInputMat->GetDimensions(nC, nR);
if ((bGroupedByColumn && nC > 2 ) || (!bGroupedByColumn && nR > 2 ))
{
// There can be no more than 2 columns (real, imaginary) if data grouped by columns.
// and no more than 2 rows if data is grouped by rows.
PushIllegalArgument();
return ;
}
if (!pInputMat->IsNumeric())
{
PushNoValue();
return ;
}
bool bRealInput = true ;
if (!bGroupedByColumn)
{
pInputMat->MatTrans(*pInputMat);
bRealInput = (nR == 1 );
}
else
{
bRealInput = (nC == 1 );
}
ScFFT aFFT(pInputMat, bRealInput, bInverse, bPolar, fMinMag);
std::function<ScMatrixGenerator> aFunc = [this ](SCSIZE nCol, SCSIZE nRow, std::vector<double >& rVec) -> ScMatrixRef
{
return this ->GetNewMat(nCol, nRow, rVec);
};
ScMatrixRef pOut = aFFT.Compute(aFunc);
PushMatrix(pOut);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Messung V0.5 in Prozent C=94 H=89 G=91
¤ Dauer der Verarbeitung: 0.253 Sekunden
(vorverarbeitet am 2026-10-01)
¤
*© Formatika GbR, Deutschland