Skip to content

Commit 61eb265

Browse files
committed
This closes qax-os#1171, improve the compatibility and added new formula function
ref qax-os#65, added new formula function: FINV
1 parent 354d169 commit 61eb265

File tree

3 files changed

+385
-2
lines changed

3 files changed

+385
-2
lines changed

calc.go

+335
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ type formulaFuncs struct {
415415
// FALSE
416416
// FIND
417417
// FINDB
418+
// FINV
418419
// FISHER
419420
// FISHERINV
420421
// FIXED
@@ -5835,6 +5836,340 @@ func (fn *formulaFuncs) EXPONDIST(argsList *list.List) formulaArg {
58355836
return newNumberFormulaArg(lambda.Number * math.Exp(-lambda.Number*x.Number))
58365837
}
58375838

5839+
// d1mach returns double precision real machine constants.
5840+
func d1mach(i int) float64 {
5841+
arr := []float64{
5842+
2.2250738585072014e-308,
5843+
1.7976931348623158e+308,
5844+
1.1102230246251565e-16,
5845+
2.2204460492503131e-16,
5846+
0.301029995663981195,
5847+
}
5848+
if i > len(arr) {
5849+
return 0
5850+
}
5851+
return arr[i-1]
5852+
}
5853+
5854+
// chebyshevInit determines the number of terms for the double precision
5855+
// orthogonal series "dos" needed to insure the error is no larger
5856+
// than "eta". Ordinarily eta will be chosen to be one-tenth machine
5857+
// precision.
5858+
func chebyshevInit(nos int, eta float64, dos []float64) int {
5859+
i, e := 0, 0.0
5860+
if nos < 1 {
5861+
return 0
5862+
}
5863+
for ii := 1; ii <= nos; ii++ {
5864+
i = nos - ii
5865+
e += math.Abs(dos[i])
5866+
if e > eta {
5867+
return i
5868+
}
5869+
}
5870+
return i
5871+
}
5872+
5873+
// chebyshevEval evaluates the n-term Chebyshev series "a" at "x".
5874+
func chebyshevEval(n int, x float64, a []float64) float64 {
5875+
if n < 1 || n > 1000 || x < -1.1 || x > 1.1 {
5876+
return math.NaN()
5877+
}
5878+
twox, b0, b1, b2 := x*2, 0.0, 0.0, 0.0
5879+
for i := 1; i <= n; i++ {
5880+
b2 = b1
5881+
b1 = b0
5882+
b0 = twox*b1 - b2 + a[n-i]
5883+
}
5884+
return (b0 - b2) * 0.5
5885+
}
5886+
5887+
// lgammacor is an implementation for the log(gamma) correction.
5888+
func lgammacor(x float64) float64 {
5889+
algmcs := []float64{
5890+
0.1666389480451863247205729650822, -0.1384948176067563840732986059135e-4,
5891+
0.9810825646924729426157171547487e-8, -0.1809129475572494194263306266719e-10,
5892+
0.6221098041892605227126015543416e-13, -0.3399615005417721944303330599666e-15,
5893+
0.2683181998482698748957538846666e-17, -0.2868042435334643284144622399999e-19,
5894+
0.3962837061046434803679306666666e-21, -0.6831888753985766870111999999999e-23,
5895+
0.1429227355942498147573333333333e-24, -0.3547598158101070547199999999999e-26,
5896+
0.1025680058010470912000000000000e-27, -0.3401102254316748799999999999999e-29,
5897+
0.1276642195630062933333333333333e-30,
5898+
}
5899+
nalgm := chebyshevInit(15, d1mach(3), algmcs)
5900+
xbig := 1.0 / math.Sqrt(d1mach(3))
5901+
xmax := math.Exp(math.Min(math.Log(d1mach(2)/12.0), -math.Log(12.0*d1mach(1))))
5902+
if x < 10.0 {
5903+
return math.NaN()
5904+
} else if x >= xmax {
5905+
return 4.930380657631324e-32
5906+
} else if x < xbig {
5907+
tmp := 10.0 / x
5908+
return chebyshevEval(nalgm, tmp*tmp*2.0-1.0, algmcs) / x
5909+
}
5910+
return 1.0 / (x * 12.0)
5911+
}
5912+
5913+
// logrelerr compute the relative error logarithm.
5914+
func logrelerr(x float64) float64 {
5915+
alnrcs := []float64{
5916+
0.10378693562743769800686267719098e+1, -0.13364301504908918098766041553133,
5917+
0.19408249135520563357926199374750e-1, -0.30107551127535777690376537776592e-2,
5918+
0.48694614797154850090456366509137e-3, -0.81054881893175356066809943008622e-4,
5919+
0.13778847799559524782938251496059e-4, -0.23802210894358970251369992914935e-5,
5920+
0.41640416213865183476391859901989e-6, -0.73595828378075994984266837031998e-7,
5921+
0.13117611876241674949152294345011e-7, -0.23546709317742425136696092330175e-8,
5922+
0.42522773276034997775638052962567e-9, -0.77190894134840796826108107493300e-10,
5923+
0.14075746481359069909215356472191e-10, -0.25769072058024680627537078627584e-11,
5924+
0.47342406666294421849154395005938e-12, -0.87249012674742641745301263292675e-13,
5925+
0.16124614902740551465739833119115e-13, -0.29875652015665773006710792416815e-14,
5926+
0.55480701209082887983041321697279e-15, -0.10324619158271569595141333961932e-15,
5927+
0.19250239203049851177878503244868e-16, -0.35955073465265150011189707844266e-17,
5928+
0.67264542537876857892194574226773e-18, -0.12602624168735219252082425637546e-18,
5929+
0.23644884408606210044916158955519e-19, -0.44419377050807936898878389179733e-20,
5930+
0.83546594464034259016241293994666e-21, -0.15731559416479562574899253521066e-21,
5931+
0.29653128740247422686154369706666e-22, -0.55949583481815947292156013226666e-23,
5932+
0.10566354268835681048187284138666e-23, -0.19972483680670204548314999466666e-24,
5933+
0.37782977818839361421049855999999e-25, -0.71531586889081740345038165333333e-26,
5934+
0.13552488463674213646502024533333e-26, -0.25694673048487567430079829333333e-27,
5935+
0.48747756066216949076459519999999e-28, -0.92542112530849715321132373333333e-29,
5936+
0.17578597841760239233269760000000e-29, -0.33410026677731010351377066666666e-30,
5937+
0.63533936180236187354180266666666e-31,
5938+
}
5939+
nlnrel := chebyshevInit(43, 0.1*d1mach(3), alnrcs)
5940+
if x <= -1 {
5941+
return math.NaN()
5942+
}
5943+
if math.Abs(x) <= 0.375 {
5944+
return x * (1.0 - x*chebyshevEval(nlnrel, x/0.375, alnrcs))
5945+
}
5946+
return math.Log(x + 1.0)
5947+
}
5948+
5949+
// logBeta is an implementation for the log of the beta distribution
5950+
// function.
5951+
func logBeta(a, b float64) float64 {
5952+
corr, p, q := 0.0, a, a
5953+
if b < p {
5954+
p = b
5955+
}
5956+
if b > q {
5957+
q = b
5958+
}
5959+
if p < 0 {
5960+
return math.NaN()
5961+
}
5962+
if p == 0 {
5963+
return math.MaxFloat64
5964+
}
5965+
if p >= 10.0 {
5966+
corr = lgammacor(p) + lgammacor(q) - lgammacor(p+q)
5967+
return math.Log(q)*-0.5 + 0.918938533204672741780329736406 + corr + (p-0.5)*math.Log(p/(p+q)) + q*logrelerr(-p/(p+q))
5968+
}
5969+
if q >= 10 {
5970+
corr = lgammacor(q) - lgammacor(p+q)
5971+
val, _ := math.Lgamma(p)
5972+
return val + corr + p - p*math.Log(p+q) + (q-0.5)*logrelerr(-p/(p+q))
5973+
}
5974+
return math.Log(math.Gamma(p) * (math.Gamma(q) / math.Gamma(p+q)))
5975+
}
5976+
5977+
// pbetaRaw is a part of pbeta for the beta distribution.
5978+
func pbetaRaw(alnsml, ans, eps, p, pin, q, sml, x, y float64) float64 {
5979+
if q > 1.0 {
5980+
xb := p*math.Log(y) + q*math.Log(1.0-y) - logBeta(p, q) - math.Log(q)
5981+
ib := int(math.Max(xb/alnsml, 0.0))
5982+
term := math.Exp(xb - float64(ib)*alnsml)
5983+
c := 1.0 / (1.0 - y)
5984+
p1 := q * c / (p + q - 1.0)
5985+
finsum := 0.0
5986+
n := int(q)
5987+
if q == float64(n) {
5988+
n = n - 1
5989+
}
5990+
for i := 1; i <= n; i++ {
5991+
if p1 <= 1 && term/eps <= finsum {
5992+
break
5993+
}
5994+
xi := float64(i)
5995+
term = (q - xi + 1.0) * c * term / (p + q - xi)
5996+
if term > 1.0 {
5997+
ib = ib - 1
5998+
term = term * sml
5999+
}
6000+
if ib == 0 {
6001+
finsum = finsum + term
6002+
}
6003+
}
6004+
ans = ans + finsum
6005+
}
6006+
if y != x || p != pin {
6007+
ans = 1.0 - ans
6008+
}
6009+
ans = math.Max(math.Min(ans, 1.0), 0.0)
6010+
return ans
6011+
}
6012+
6013+
// pbeta returns distribution function of the beta distribution.
6014+
func pbeta(x, pin, qin float64) (ans float64) {
6015+
eps := d1mach(3)
6016+
alneps := math.Log(eps)
6017+
sml := d1mach(1)
6018+
alnsml := math.Log(sml)
6019+
y := x
6020+
p := pin
6021+
q := qin
6022+
if p/(p+q) < x {
6023+
y = 1.0 - y
6024+
p = qin
6025+
q = pin
6026+
}
6027+
if (p+q)*y/(p+1.0) < eps {
6028+
xb := p*math.Log(math.Max(y, sml)) - math.Log(p) - logBeta(p, q)
6029+
if xb > alnsml && y != 0.0 {
6030+
ans = math.Exp(xb)
6031+
}
6032+
if y != x || p != pin {
6033+
ans = 1.0 - ans
6034+
}
6035+
} else {
6036+
ps := q - math.Floor(q)
6037+
if ps == 0.0 {
6038+
ps = 1.0
6039+
}
6040+
xb := p*math.Log(y) - logBeta(ps, p) - math.Log(p)
6041+
if xb >= alnsml {
6042+
ans = math.Exp(xb)
6043+
term := ans * p
6044+
if ps != 1.0 {
6045+
n := int(math.Max(alneps/math.Log(y), 4.0))
6046+
for i := 1; i <= n; i++ {
6047+
xi := float64(i)
6048+
term = term * (xi - ps) * y / xi
6049+
ans = ans + term/(p+xi)
6050+
}
6051+
}
6052+
}
6053+
ans = pbetaRaw(alnsml, ans, eps, p, pin, q, sml, x, y)
6054+
}
6055+
return ans
6056+
}
6057+
6058+
// betainvProbIterator is a part of betainv for the inverse of the beta function.
6059+
func betainvProbIterator(alpha1, alpha3, beta1, beta2, beta3, logbeta, lower, maxCumulative, prob1, prob2, upper float64, needSwap bool) float64 {
6060+
var i, j, prev, prop4 float64
6061+
j = 1
6062+
for prob := 0; prob < 1000; prob++ {
6063+
prop3 := pbeta(beta3, alpha1, beta1)
6064+
prop3 = (prop3 - prob1) * math.Exp(logbeta+prob2*math.Log(beta3)+beta2*math.Log(1.0-beta3))
6065+
if prop3*prop4 <= 0 {
6066+
prev = math.Max(math.Abs(j), maxCumulative)
6067+
}
6068+
h := 1.0
6069+
for iteratorCount := 0; iteratorCount < 1000; iteratorCount++ {
6070+
j = h * prop3
6071+
if math.Abs(j) < prev {
6072+
i = beta3 - j
6073+
if i >= 0 && i <= 1.0 {
6074+
if prev <= alpha3 {
6075+
return beta3
6076+
}
6077+
if math.Abs(prop3) <= alpha3 {
6078+
return beta3
6079+
}
6080+
if i != 0 && i != 1.0 {
6081+
break
6082+
}
6083+
}
6084+
}
6085+
h /= 3.0
6086+
}
6087+
if i == beta3 {
6088+
return beta3
6089+
}
6090+
beta3, prop4 = i, prop3
6091+
}
6092+
return beta3
6093+
}
6094+
6095+
// betainv is an implementation for the quantile of the beta distribution.
6096+
func betainv(probability, alpha, beta, lower, upper float64) float64 {
6097+
minCumulative, maxCumulative := 1.0e-300, 3.0e-308
6098+
lowerBound, upperBound := maxCumulative, 1.0-2.22e-16
6099+
needSwap := false
6100+
var alpha1, alpha2, beta1, beta2, beta3, prob1, x, y float64
6101+
if probability <= 0.5 {
6102+
prob1, alpha1, beta1 = probability, alpha, beta
6103+
} else {
6104+
prob1, alpha1, beta1, needSwap = 1.0-probability, beta, alpha, true
6105+
}
6106+
logbeta := logBeta(alpha, beta)
6107+
prob2 := math.Sqrt(-math.Log(prob1 * prob1))
6108+
prob3 := prob2 - (prob2*0.27061+2.3075)/(prob2*(prob2*0.04481+0.99229)+1)
6109+
if alpha1 > 1 && beta1 > 1 {
6110+
alpha2, beta2, prob2 = 1/(alpha1+alpha1-1), 1/(beta1+beta1-1), (prob3*prob3-3)/6
6111+
x = 2 / (alpha2 + beta2)
6112+
y = prob3*math.Sqrt(x+prob2)/x - (beta2-alpha2)*(prob2+5/6.0-2/(x*3))
6113+
beta3 = alpha1 / (alpha1 + beta1*math.Exp(y+y))
6114+
} else {
6115+
beta2, prob2 = 1/(beta1*9), beta1+beta1
6116+
beta2 = prob2 * math.Pow(1-beta2+prob3*math.Sqrt(beta2), 3)
6117+
if beta2 <= 0 {
6118+
beta3 = 1 - math.Exp((math.Log((1-prob1)*beta1)+logbeta)/beta1)
6119+
} else {
6120+
beta2 = (prob2 + alpha1*4 - 2) / beta2
6121+
if beta2 <= 1 {
6122+
beta3 = math.Exp((logbeta + math.Log(alpha1*prob1)) / alpha1)
6123+
} else {
6124+
beta3 = 1 - 2/(beta2+1)
6125+
}
6126+
}
6127+
}
6128+
beta2, prob2 = 1-beta1, 1-alpha1
6129+
if beta3 < lowerBound {
6130+
beta3 = lowerBound
6131+
} else if beta3 > upperBound {
6132+
beta3 = upperBound
6133+
}
6134+
alpha3 := math.Max(minCumulative, math.Pow(10.0, -13.0-2.5/(alpha1*alpha1)-0.5/(prob1*prob1)))
6135+
beta3 = betainvProbIterator(alpha1, alpha3, beta1, beta2, beta3, logbeta, lower, maxCumulative, prob1, prob2, upper, needSwap)
6136+
if needSwap {
6137+
beta3 = 1.0 - beta3
6138+
}
6139+
return (upper-lower)*beta3 + lower
6140+
}
6141+
6142+
// FINV function calculates the inverse of the (right-tailed) F Probability
6143+
// Distribution for a supplied probability. The syntax of the function is:
6144+
//
6145+
// FINV(probability,deg_freedom1,deg_freedom2)
6146+
//
6147+
func (fn *formulaFuncs) FINV(argsList *list.List) formulaArg {
6148+
if argsList.Len() != 3 {
6149+
return newErrorFormulaArg(formulaErrorVALUE, "FINV requires 3 arguments")
6150+
}
6151+
var probability, d1, d2 formulaArg
6152+
if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
6153+
return probability
6154+
}
6155+
if d1 = argsList.Front().Next().Value.(formulaArg).ToNumber(); d1.Type != ArgNumber {
6156+
return d1
6157+
}
6158+
if d2 = argsList.Back().Value.(formulaArg).ToNumber(); d2.Type != ArgNumber {
6159+
return d2
6160+
}
6161+
if probability.Number <= 0 || probability.Number > 1 {
6162+
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
6163+
}
6164+
if d1.Number < 1 || d1.Number >= math.Pow10(10) {
6165+
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
6166+
}
6167+
if d2.Number < 1 || d2.Number >= math.Pow10(10) {
6168+
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
6169+
}
6170+
return newNumberFormulaArg((1/betainv(1.0-(1.0-probability.Number), d2.Number/2, d1.Number/2, 0, 1) - 1.0) * (d2.Number / d1.Number))
6171+
}
6172+
58386173
// NORMdotDIST function calculates the Normal Probability Density Function or
58396174
// the Cumulative Normal Distribution. Function for a supplied set of
58406175
// parameters. The syntax of the function is:

0 commit comments

Comments
 (0)