6036 lines
182 KiB
C++
6036 lines
182 KiB
C++
/* -*- 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.0E-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.7454501427834140764e-4+0.0227238449892691845833
|
|
)
|
|
*t+0.24178072517745061177
|
|
)
|
|
*t+1.27045825245236838258
|
|
)
|
|
*t+3.64784832476320460504
|
|
)
|
|
*t+5.7694972214606914055
|
|
)
|
|
*t+4.6303378461565452959
|
|
)
|
|
*t+1.42343711074968357734
|
|
)
|
|
/
|
|
(
|
|
(
|
|
(
|
|
(
|
|
(
|
|
(
|
|
(
|
|
t*1.05075007164441684324e-9+5.475938084995344946e-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.01033439929228813265e-7+2.71155556874348757815e-5
|
|
)
|
|
*t+0.0012426609473880784386
|
|
)
|
|
*t+0.026532189526576123093
|
|
)
|
|
*t+0.29656057182850489123
|
|
)
|
|
*t+1.7848265399172913358
|
|
)
|
|
*t+5.4637849111641143699
|
|
)
|
|
*t+6.6579046435011037772
|
|
)
|
|
/
|
|
(
|
|
(
|
|
(
|
|
(
|
|
(
|
|
(
|
|
(
|
|
t*2.04426310338993978564e-15+1.4215117583164458887e-7
|
|
)
|
|
*t+1.8463183175100546818e-5
|
|
)
|
|
*t+7.868691311456132591e-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::BinomKoeff(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 > 1E10 )
|
|
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(BinomKoeff(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(BinomKoeff(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.0E10 || fF2 >= 1.0E10)
|
|
{
|
|
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.0E10 || fF2 >= 1.0E10 )
|
|
{
|
|
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.0E10 || fF2 >= 1.0E10 || 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.0E10 || fF2 >= 1.0E10 || 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.0E-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 < 1UL << (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: */
|