/* -*- 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 .
*/
/// 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;
}
class ScDistFunc
{ public: virtualdouble 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 */ staticbool lcl_HasChangeOfSign( double u, double w )
{ return (u < 0.0 && w > 0.0) || (u > 0.0 && w < 0.0);
}
double ScInterpreter::BinomCoeff(double n, double k)
{ // this method has been duplicated as BinomialCoefficient() // in scaddins/source/analysis/analysishelper.cxx
// 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. */ staticdouble lcl_getLanczosSum(double fZ)
{ staticconstdouble 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
}; staticconstdouble 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. */ staticdouble lcl_GetGammaHelper(double fZ)
{ double fGamma = lcl_getLanczosSum(fZ); constdouble 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 */ staticdouble lcl_GetLogGammaHelper(double fZ)
{ constdouble 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)
{ constdouble fLogPi = log(M_PI); constdouble 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;
constdouble 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
{ constdouble apl2m = fA + 2.0*rm; constdouble d2m = rm*(fB-rm)*fX/((apl2m-1.0)*apl2m); constdouble 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;
}
}
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);
}
}
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);
}
}
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(); elseif (!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.
*/ staticvoid 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());
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 ); elseif ( 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 );
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;
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++;
} elseif ( 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++;
} elseif ( 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++;
} elseif ( 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++;
} elseif ( 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++;
} elseif ( 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++;
} elseif ( 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);
}
if (nGlobalError != FormulaError::NONE)
{
PushError( nGlobalError); returnfalse;
} // if (nGlobalError != FormulaError::NONE) returntrue;
}
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);
vector<double> aSortArray;
GetNumberSequenceArray(1, aSortArray, false ); const SCSIZE nSize = aSortArray.size(); if (nSize == 0 || nGlobalError != FormulaError::NONE)
PushNoValue(); elseif (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]); elseif (!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);
}
}
// 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;
}
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));
}
}
staticvoid 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;
mfWReal[3] = 0;
mfWImag[3] = (mbInverse ? -1.0 : 1.0);
} elseif ((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];
// 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)
{}
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)
{}
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;
}
}
// 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();
// 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:
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];
}
// 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:
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;
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.257Bemerkung:
(vorverarbeitet am 2026-04-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.