ref #65, #1185, new formula functions and precision improvement

* New formula functions: BETA.DIST, BINOMDIST and BINOM
* Fix a part of formula function calculation result precision issue on arm64
This commit is contained in:
xuri 2022-03-20 00:16:32 +08:00
parent 94f197c4fe
commit 49424b0eb3
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
2 changed files with 197 additions and 10 deletions

142
calc.go
View File

@ -334,11 +334,14 @@ type formulaFuncs struct {
// BESSELK
// BESSELY
// BETADIST
// BETA.DIST
// BETAINV
// BETA.INV
// BIN2DEC
// BIN2HEX
// BIN2OCT
// BINOMDIST
// BINOM.DIST
// BITAND
// BITLSHIFT
// BITOR
@ -686,7 +689,7 @@ func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
}
result = token.TValue
isNum, precision := isNumeric(result)
if isNum && precision > 15 {
if isNum && (precision > 15 || precision == 0) {
num := roundPrecision(result, -1)
result = strings.ToUpper(num)
}
@ -5406,6 +5409,74 @@ func getBetaDist(fXin, fAlpha, fBeta float64) float64 {
return fResult
}
// prepareBETAdotDISTArgs checking and prepare arguments for the formula
// function BETA.DIST.
func (fn *formulaFuncs) prepareBETAdotDISTArgs(argsList *list.List) formulaArg {
if argsList.Len() < 4 {
return newErrorFormulaArg(formulaErrorVALUE, "BETA.DIST requires at least 4 arguments")
}
if argsList.Len() > 6 {
return newErrorFormulaArg(formulaErrorVALUE, "BETA.DIST requires at most 6 arguments")
}
x := argsList.Front().Value.(formulaArg).ToNumber()
if x.Type != ArgNumber {
return x
}
alpha := argsList.Front().Next().Value.(formulaArg).ToNumber()
if alpha.Type != ArgNumber {
return alpha
}
beta := argsList.Front().Next().Next().Value.(formulaArg).ToNumber()
if beta.Type != ArgNumber {
return beta
}
if alpha.Number <= 0 || beta.Number <= 0 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
cumulative := argsList.Front().Next().Next().Next().Value.(formulaArg).ToBool()
if cumulative.Type != ArgNumber {
return cumulative
}
a, b := newNumberFormulaArg(0), newNumberFormulaArg(1)
if argsList.Len() > 4 {
if a = argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber(); a.Type != ArgNumber {
return a
}
}
if argsList.Len() == 6 {
if b = argsList.Back().Value.(formulaArg).ToNumber(); b.Type != ArgNumber {
return b
}
}
return newListFormulaArg([]formulaArg{x, alpha, beta, cumulative, a, b})
}
// BETAdotDIST function calculates the cumulative beta distribution function
// or the probability density function of the Beta distribution, for a
// supplied set of parameters. The syntax of the function is:
//
// BETA.DIST(x,alpha,beta,cumulative,[A],[B])
//
func (fn *formulaFuncs) BETAdotDIST(argsList *list.List) formulaArg {
args := fn.prepareBETAdotDISTArgs(argsList)
if args.Type != ArgList {
return args
}
x, alpha, beta, cumulative, a, b := args.List[0], args.List[1], args.List[2], args.List[3], args.List[4], args.List[5]
if x.Number < a.Number || x.Number > b.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if a.Number == b.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
fScale := b.Number - a.Number
x.Number = (x.Number - a.Number) / fScale
if cumulative.Number == 1 {
return newNumberFormulaArg(getBetaDist(x.Number, alpha.Number, beta.Number))
}
return newNumberFormulaArg(getBetaDistPDF(x.Number, alpha.Number, beta.Number) / fScale)
}
// BETADIST function calculates the cumulative beta probability density
// function for a supplied set of parameters. The syntax of the function is:
//
@ -5836,6 +5907,69 @@ func incompleteGamma(a, x float64) float64 {
return math.Pow(x, a) * math.Exp(0-x) * summer
}
// binomCoeff implement binomial coefficient calcuation.
func binomCoeff(n, k float64) float64 {
return fact(n) / (fact(k) * fact(n-k))
}
// binomdist implement binomial distribution calcuation.
func binomdist(x, n, p float64) float64 {
return binomCoeff(n, x) * math.Pow(p, x) * math.Pow(1-p, n-x)
}
// BINOMfotDIST function returns the Binomial Distribution probability for a
// given number of successes from a specified number of trials. The syntax of
// the function is:
//
// BINOM.DIST(number_s,trials,probability_s,cumulative)
//
func (fn *formulaFuncs) BINOMdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "BINOM.DIST requires 4 arguments")
}
return fn.BINOMDIST(argsList)
}
// BINOMDIST function returns the Binomial Distribution probability of a
// specified number of successes out of a specified number of trials. The
// syntax of the function is:
//
// BINOMDIST(number_s,trials,probability_s,cumulative)
//
func (fn *formulaFuncs) BINOMDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "BINOMDIST requires 4 arguments")
}
var s, trials, probability, cumulative formulaArg
if s = argsList.Front().Value.(formulaArg).ToNumber(); s.Type != ArgNumber {
return s
}
if trials = argsList.Front().Next().Value.(formulaArg).ToNumber(); trials.Type != ArgNumber {
return trials
}
if s.Number < 0 || s.Number > trials.Number {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if probability = argsList.Back().Prev().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
return probability
}
if probability.Number < 0 || probability.Number > 1 {
return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
return cumulative
}
if cumulative.Number == 1 {
bm := 0.0
for i := 0; i <= int(s.Number); i++ {
bm += binomdist(float64(i), trials.Number, probability.Number)
}
return newNumberFormulaArg(bm)
}
return newNumberFormulaArg(binomdist(s.Number, trials.Number, probability.Number))
}
// CHIDIST function calculates the right-tailed probability of the chi-square
// distribution. The syntax of the function is:
//
@ -11641,7 +11775,7 @@ func (fn *formulaFuncs) AMORLINC(argsList *list.List) formulaArg {
if int(period.Number) <= periods {
return newNumberFormulaArg(rate2)
} else if int(period.Number)-1 == periods {
return newNumberFormulaArg(delta - rate2*float64(periods) - rate1)
return newNumberFormulaArg(delta - rate2*float64(periods) - math.Nextafter(rate1, rate1))
}
return newNumberFormulaArg(0)
}
@ -13334,7 +13468,9 @@ func (fn *formulaFuncs) rate(nper, pmt, pv, fv, t, guess formulaArg, argsList *l
rt := rate*t.Number + 1
p0 := pmt.Number * (t1 - 1)
f1 := fv.Number + t1*pv.Number + p0*rt/rate
f2 := nper.Number*t2*pv.Number - p0*rt/math.Pow(rate, 2)
n1 := nper.Number * t2 * pv.Number
n2 := p0 * rt / math.Pow(rate, 2)
f2 := math.Nextafter(n1, n1) - math.Nextafter(n2, n2)
f3 := (nper.Number*pmt.Number*t2*rt + p0*t.Number) / rate
delta := f1 / (f2 + f3)
if math.Abs(delta) < epsMax {

View File

@ -784,6 +784,9 @@ func TestCalcCellValue(t *testing.T) {
"=AVERAGEA(A1)": "1",
"=AVERAGEA(A1:A2)": "1.5",
"=AVERAGEA(D2:F9)": "12671.375",
// BETA.DIST
"=BETA.DIST(0.4,4,5,TRUE,0,1)": "0.4059136",
"=BETA.DIST(0.6,4,5,FALSE,0,1)": "1.548288",
// BETADIST
"=BETADIST(0.4,4,5)": "0.4059136",
"=BETADIST(0.4,4,5,0,1)": "0.4059136",
@ -796,12 +799,26 @@ func TestCalcCellValue(t *testing.T) {
"=BETADIST(0.4,2,100)": "1",
"=BETADIST(0.75,3,4)": "0.96240234375",
"=BETADIST(0.2,0.7,4)": "0.71794309318323",
"=BETADIST(0.01,3,4)": "1.9553589999999998e-05",
"=BETADIST(0.01,3,4)": "1.955359E-05",
"=BETADIST(0.75,130,140)": "1",
// BETAINV
"=BETAINV(0.2,4,5,0,1)": "0.303225844664082",
// BETA.INV
"=BETA.INV(0.2,4,5,0,1)": "0.303225844664082",
// BINOMDIST
"=BINOMDIST(10,100,0.5,FALSE)": "1.36554263874631E-17",
"=BINOMDIST(50,100,0.5,FALSE)": "0.0795892373871787",
"=BINOMDIST(65,100,0.5,FALSE)": "0.000863855665741652",
"=BINOMDIST(10,100,0.5,TRUE)": "1.53164508771899E-17",
"=BINOMDIST(50,100,0.5,TRUE)": "0.539794618693589",
"=BINOMDIST(65,100,0.5,TRUE)": "0.999105034804256",
// BINOM.DIST
"=BINOM.DIST(10,100,0.5,FALSE)": "1.36554263874631E-17",
"=BINOM.DIST(50,100,0.5,FALSE)": "0.0795892373871787",
"=BINOM.DIST(65,100,0.5,FALSE)": "0.000863855665741652",
"=BINOM.DIST(10,100,0.5,TRUE)": "1.53164508771899E-17",
"=BINOM.DIST(50,100,0.5,TRUE)": "0.539794618693589",
"=BINOM.DIST(65,100,0.5,TRUE)": "0.999105034804256",
// CHIDIST
"=CHIDIST(0.5,3)": "0.918891411654676",
"=CHIDIST(8,3)": "0.0460117056892315",
@ -1468,7 +1485,7 @@ func TestCalcCellValue(t *testing.T) {
"=UPPER(\"TEST 123\")": "TEST 123",
// VALUE
"=VALUE(\"50\")": "50",
"=VALUE(\"1.0E-07\")": "1e-07",
"=VALUE(\"1.0E-07\")": "1E-07",
"=VALUE(\"5,000\")": "5000",
"=VALUE(\"20%\")": "0.2",
"=VALUE(\"12:00:00\")": "0.5",
@ -2341,6 +2358,25 @@ func TestCalcCellValue(t *testing.T) {
"=AVERAGE(H1)": "AVERAGE divide by zero",
// AVERAGEA
"=AVERAGEA(H1)": "AVERAGEA divide by zero",
// AVERAGEIF
"=AVERAGEIF()": "AVERAGEIF requires at least 2 arguments",
"=AVERAGEIF(H1,\"\")": "AVERAGEIF divide by zero",
"=AVERAGEIF(D1:D3,\"Month\",D1:D3)": "AVERAGEIF divide by zero",
"=AVERAGEIF(C1:C3,\"Month\",D1:D3)": "AVERAGEIF divide by zero",
// BETA.DIST
"=BETA.DIST()": "BETA.DIST requires at least 4 arguments",
"=BETA.DIST(0.4,4,5,TRUE,0,1,0)": "BETA.DIST requires at most 6 arguments",
"=BETA.DIST(\"\",4,5,TRUE,0,1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=BETA.DIST(0.4,\"\",5,TRUE,0,1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=BETA.DIST(0.4,4,\"\",TRUE,0,1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=BETA.DIST(0.4,4,5,\"\",0,1)": "strconv.ParseBool: parsing \"\": invalid syntax",
"=BETA.DIST(0.4,4,5,TRUE,\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=BETA.DIST(0.4,4,5,TRUE,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=BETA.DIST(0.4,0,5,TRUE,0,1)": "#NUM!",
"=BETA.DIST(0.4,4,0,TRUE,0,0)": "#NUM!",
"=BETA.DIST(0.4,4,5,TRUE,0.5,1)": "#NUM!",
"=BETA.DIST(0.4,4,5,TRUE,0,0.3)": "#NUM!",
"=BETA.DIST(0.4,4,5,TRUE,0.4,0.4)": "#NUM!",
// BETADIST
"=BETADIST()": "BETADIST requires at least 3 arguments",
"=BETADIST(0.4,4,5,0,1,0)": "BETADIST requires at most 5 arguments",
@ -2380,11 +2416,26 @@ func TestCalcCellValue(t *testing.T) {
"=BETA.INV(0.2,0,5,0,1)": "#NUM!",
"=BETA.INV(0.2,4,0,0,1)": "#NUM!",
"=BETA.INV(0.2,4,5,2,2)": "#NUM!",
// AVERAGEIF
"=AVERAGEIF()": "AVERAGEIF requires at least 2 arguments",
"=AVERAGEIF(H1,\"\")": "AVERAGEIF divide by zero",
"=AVERAGEIF(D1:D3,\"Month\",D1:D3)": "AVERAGEIF divide by zero",
"=AVERAGEIF(C1:C3,\"Month\",D1:D3)": "AVERAGEIF divide by zero",
// BINOMDIST
"=BINOMDIST()": "BINOMDIST requires 4 arguments",
"=BINOMDIST(\"\",100,0.5,FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=BINOMDIST(10,\"\",0.5,FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=BINOMDIST(10,100,\"\",FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=BINOMDIST(10,100,0.5,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax",
"=BINOMDIST(-1,100,0.5,FALSE)": "#NUM!",
"=BINOMDIST(110,100,0.5,FALSE)": "#NUM!",
"=BINOMDIST(10,100,-1,FALSE)": "#NUM!",
"=BINOMDIST(10,100,2,FALSE)": "#NUM!",
// BINOM.DIST
"=BINOM.DIST()": "BINOM.DIST requires 4 arguments",
"=BINOM.DIST(\"\",100,0.5,FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=BINOM.DIST(10,\"\",0.5,FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=BINOM.DIST(10,100,\"\",FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=BINOM.DIST(10,100,0.5,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax",
"=BINOM.DIST(-1,100,0.5,FALSE)": "#NUM!",
"=BINOM.DIST(110,100,0.5,FALSE)": "#NUM!",
"=BINOM.DIST(10,100,-1,FALSE)": "#NUM!",
"=BINOM.DIST(10,100,2,FALSE)": "#NUM!",
// CHIDIST
"=CHIDIST()": "CHIDIST requires 2 numeric arguments",
"=CHIDIST(\"\",3)": "strconv.ParseFloat: parsing \"\": invalid syntax",