/* -*- 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 );
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.