#64 fn: NORM.DIST, NORMDIST, NORM.INV, NORMINV, NORM.S.DIST, NORMSDIST, NORM.S.INV, and NORMSINV

This commit is contained in:
xuri 2021-03-24 08:24:59 +08:00
parent bd83c27ffc
commit ab2c1c8fe1
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
4 changed files with 279 additions and 22 deletions

241
calc.go
View File

@ -323,6 +323,14 @@ var tokenPriority = map[string]int{
// MULTINOMIAL
// MUNIT
// NA
// NORM.DIST
// NORMDIST
// NORM.INV
// NORMINV
// NORM.S.DIST
// NORMSDIST
// NORM.S.INV
// NORMSINV
// NOT
// NOW
// OCT2BIN
@ -599,7 +607,7 @@ func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token,
}
// call formula function to evaluate
arg := callFuncByName(&formulaFuncs{f: f, sheet: sheet, cell: cell}, strings.NewReplacer(
"_xlfn", "", ".", "").Replace(opfStack.Peek().(efp.Token).TValue),
"_xlfn.", "", ".", "dot").Replace(opfStack.Peek().(efp.Token).TValue),
[]reflect.Value{reflect.ValueOf(argsStack.Peek().(*list.List))})
if arg.Type == ArgError && opfStack.Len() == 1 {
return errors.New(arg.Value())
@ -1922,12 +1930,12 @@ func (fn *formulaFuncs) CEILING(argsList *list.List) formulaArg {
return newNumberFormulaArg(number * significance)
}
// CEILINGMATH function rounds a supplied number up to a supplied multiple of
// significance. The syntax of the function is:
// CEILINGdotMATH function rounds a supplied number up to a supplied multiple
// of significance. The syntax of the function is:
//
// CEILING.MATH(number,[significance],[mode])
//
func (fn *formulaFuncs) CEILINGMATH(argsList *list.List) formulaArg {
func (fn *formulaFuncs) CEILINGdotMATH(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "CEILING.MATH requires at least 1 argument")
}
@ -1971,13 +1979,13 @@ func (fn *formulaFuncs) CEILINGMATH(argsList *list.List) formulaArg {
return newNumberFormulaArg(val * significance)
}
// CEILINGPRECISE function rounds a supplied number up (regardless of the
// CEILINGdotPRECISE function rounds a supplied number up (regardless of the
// number's sign), to the nearest multiple of a given number. The syntax of
// the function is:
//
// CEILING.PRECISE(number,[significance])
//
func (fn *formulaFuncs) CEILINGPRECISE(argsList *list.List) formulaArg {
func (fn *formulaFuncs) CEILINGdotPRECISE(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "CEILING.PRECISE requires at least 1 argument")
}
@ -2365,12 +2373,12 @@ func (fn *formulaFuncs) FLOOR(argsList *list.List) formulaArg {
return newStringFormulaArg(strings.ToUpper(fmt.Sprintf("%g", val*significance.Number)))
}
// FLOORMATH function rounds a supplied number down to a supplied multiple of
// significance. The syntax of the function is:
// FLOORdotMATH function rounds a supplied number down to a supplied multiple
// of significance. The syntax of the function is:
//
// FLOOR.MATH(number,[significance],[mode])
//
func (fn *formulaFuncs) FLOORMATH(argsList *list.List) formulaArg {
func (fn *formulaFuncs) FLOORdotMATH(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.MATH requires at least 1 argument")
}
@ -2409,12 +2417,12 @@ func (fn *formulaFuncs) FLOORMATH(argsList *list.List) formulaArg {
return newNumberFormulaArg(val * significance)
}
// FLOORPRECISE function rounds a supplied number down to a supplied multiple
// of significance. The syntax of the function is:
// FLOORdotPRECISE function rounds a supplied number down to a supplied
// multiple of significance. The syntax of the function is:
//
// FLOOR.PRECISE(number,[significance])
//
func (fn *formulaFuncs) FLOORPRECISE(argsList *list.List) formulaArg {
func (fn *formulaFuncs) FLOORdotPRECISE(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "FLOOR.PRECISE requires at least 1 argument")
}
@ -2534,13 +2542,13 @@ func (fn *formulaFuncs) INT(argsList *list.List) formulaArg {
return newNumberFormulaArg(val)
}
// ISOCEILING function rounds a supplied number up (regardless of the number's
// sign), to the nearest multiple of a supplied significance. The syntax of
// the function is:
// ISOdotCEILING function rounds a supplied number up (regardless of the
// number's sign), to the nearest multiple of a supplied significance. The
// syntax of the function is:
//
// ISO.CEILING(number,[significance])
//
func (fn *formulaFuncs) ISOCEILING(argsList *list.List) formulaArg {
func (fn *formulaFuncs) ISOdotCEILING(argsList *list.List) formulaArg {
if argsList.Len() == 0 {
return newErrorFormulaArg(formulaErrorVALUE, "ISO.CEILING requires at least 1 argument")
}
@ -3961,6 +3969,207 @@ func (fn *formulaFuncs) KURT(argsList *list.List) formulaArg {
return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV)
}
// NORMdotDIST function calculates the Normal Probability Density Function or
// the Cumulative Normal Distribution. Function for a supplied set of
// parameters. The syntax of the function is:
//
// NORM.DIST(x,mean,standard_dev,cumulative)
//
func (fn *formulaFuncs) NORMdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "NORM.DIST requires 4 arguments")
}
return fn.NORMDIST(argsList)
}
// NORMDIST function calculates the Normal Probability Density Function or the
// Cumulative Normal Distribution. Function for a supplied set of parameters.
// The syntax of the function is:
//
// NORMDIST(x,mean,standard_dev,cumulative)
//
func (fn *formulaFuncs) NORMDIST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "NORMDIST requires 4 arguments")
}
var x, mean, stdDev, cumulative formulaArg
if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber {
return x
}
if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber {
return mean
}
if stdDev = argsList.Back().Prev().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber {
return stdDev
}
if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type == ArgError {
return cumulative
}
if stdDev.Number < 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if cumulative.Number == 1 {
return newNumberFormulaArg(0.5 * (1 + math.Erf((x.Number-mean.Number)/(stdDev.Number*math.Sqrt(2)))))
}
return newNumberFormulaArg((1 / (math.Sqrt(2*math.Pi) * stdDev.Number)) * math.Exp(0-(math.Pow(x.Number-mean.Number, 2)/(2*(stdDev.Number*stdDev.Number)))))
}
// NORMdotINV function calculates the inverse of the Cumulative Normal
// Distribution Function for a supplied value of x, and a supplied
// distribution mean & standard deviation. The syntax of the function is:
//
// NORM.INV(probability,mean,standard_dev)
//
func (fn *formulaFuncs) NORMdotINV(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "NORM.INV requires 3 arguments")
}
return fn.NORMINV(argsList)
}
// NORMINV function calculates the inverse of the Cumulative Normal
// Distribution Function for a supplied value of x, and a supplied
// distribution mean & standard deviation. The syntax of the function is:
//
// NORMINV(probability,mean,standard_dev)
//
func (fn *formulaFuncs) NORMINV(argsList *list.List) formulaArg {
if argsList.Len() != 3 {
return newErrorFormulaArg(formulaErrorVALUE, "NORMINV requires 3 arguments")
}
var prob, mean, stdDev formulaArg
if prob = argsList.Front().Value.(formulaArg).ToNumber(); prob.Type != ArgNumber {
return prob
}
if mean = argsList.Front().Next().Value.(formulaArg).ToNumber(); mean.Type != ArgNumber {
return mean
}
if stdDev = argsList.Back().Value.(formulaArg).ToNumber(); stdDev.Type != ArgNumber {
return stdDev
}
if prob.Number < 0 || prob.Number > 1 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
if stdDev.Number < 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
inv, err := norminv(prob.Number)
if err != nil {
return newErrorFormulaArg(err.Error(), err.Error())
}
return newNumberFormulaArg(inv*stdDev.Number + mean.Number)
}
// NORMdotSdotDIST function calculates the Standard Normal Cumulative
// Distribution Function for a supplied value. The syntax of the function
// is:
//
// NORM.S.DIST(z)
//
func (fn *formulaFuncs) NORMdotSdotDIST(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "NORM.S.DIST requires 2 numeric arguments")
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
args.PushBack(argsList.Back().Value.(formulaArg))
return fn.NORMDIST(args)
}
// NORMSDIST function calculates the Standard Normal Cumulative Distribution
// Function for a supplied value. The syntax of the function is:
//
// NORMSDIST(z)
//
func (fn *formulaFuncs) NORMSDIST(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "NORMSDIST requires 1 numeric argument")
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
args.PushBack(formulaArg{Type: ArgNumber, Number: 1, Boolean: true})
return fn.NORMDIST(args)
}
// NORMSINV function calculates the inverse of the Standard Normal Cumulative
// Distribution Function for a supplied probability value. The syntax of the
// function is:
//
// NORMSINV(probability)
//
func (fn *formulaFuncs) NORMSINV(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "NORMSINV requires 1 numeric argument")
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
return fn.NORMINV(args)
}
// NORMdotSdotINV function calculates the inverse of the Standard Normal
// Cumulative Distribution Function for a supplied probability value. The
// syntax of the function is:
//
// NORM.S.INV(probability)
//
func (fn *formulaFuncs) NORMdotSdotINV(argsList *list.List) formulaArg {
if argsList.Len() != 1 {
return newErrorFormulaArg(formulaErrorVALUE, "NORM.S.INV requires 1 numeric argument")
}
args := list.New().Init()
args.PushBack(argsList.Front().Value.(formulaArg))
args.PushBack(formulaArg{Type: ArgNumber, Number: 0})
args.PushBack(formulaArg{Type: ArgNumber, Number: 1})
return fn.NORMINV(args)
}
// norminv returns the inverse of the normal cumulative distribution for the
// specified value.
func norminv(p float64) (float64, error) {
a := map[int]float64{
1: -3.969683028665376e+01, 2: 2.209460984245205e+02, 3: -2.759285104469687e+02,
4: 1.383577518672690e+02, 5: -3.066479806614716e+01, 6: 2.506628277459239e+00,
}
b := map[int]float64{
1: -5.447609879822406e+01, 2: 1.615858368580409e+02, 3: -1.556989798598866e+02,
4: 6.680131188771972e+01, 5: -1.328068155288572e+01,
}
c := map[int]float64{
1: -7.784894002430293e-03, 2: -3.223964580411365e-01, 3: -2.400758277161838e+00,
4: -2.549732539343734e+00, 5: 4.374664141464968e+00, 6: 2.938163982698783e+00,
}
d := map[int]float64{
1: 7.784695709041462e-03, 2: 3.224671290700398e-01, 3: 2.445134137142996e+00,
4: 3.754408661907416e+00,
}
pLow := 0.02425 // Use lower region approx. below this
pHigh := 1 - pLow // Use upper region approx. above this
if 0 < p && p < pLow {
// Rational approximation for lower region.
q := math.Sqrt(-2 * math.Log(p))
return (((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q + c[6]) /
((((d[1]*q+d[2])*q+d[3])*q+d[4])*q + 1), nil
} else if pLow <= p && p <= pHigh {
// Rational approximation for central region.
q := p - 0.5
r := q * q
return (((((a[1]*r+a[2])*r+a[3])*r+a[4])*r+a[5])*r + a[6]) * q /
(((((b[1]*r+b[2])*r+b[3])*r+b[4])*r+b[5])*r + 1), nil
} else if pHigh < p && p < 1 {
// Rational approximation for upper region.
q := math.Sqrt(-2 * math.Log(1-p))
return -(((((c[1]*q+c[2])*q+c[3])*q+c[4])*q+c[5])*q + c[6]) /
((((d[1]*q+d[2])*q+d[3])*q+d[4])*q + 1), nil
}
return 0, errors.New(formulaErrorNUM)
}
// kth is an implementation of the formula function LARGE and SMALL.
func (fn *formulaFuncs) kth(name string, argsList *list.List) formulaArg {
if argsList.Len() != 2 {

View File

@ -609,6 +609,27 @@ func TestCalcCellValue(t *testing.T) {
"=KURT(F1:F9)": "-1.033503502551368",
"=KURT(F1,F2:F9)": "-1.033503502551368",
"=KURT(INT(1),MUNIT(2))": "-3.333333333333336",
// NORM.DIST
"=NORM.DIST(0.8,1,0.3,TRUE)": "0.252492537546923",
"=NORM.DIST(50,40,20,FALSE)": "0.017603266338215",
// NORMDIST
"=NORMDIST(0.8,1,0.3,TRUE)": "0.252492537546923",
"=NORMDIST(50,40,20,FALSE)": "0.017603266338215",
// NORM.INV
"=NORM.INV(0.6,5,2)": "5.506694205719997",
// NORMINV
"=NORMINV(0.6,5,2)": "5.506694205719997",
"=NORMINV(0.99,40,1.5)": "43.489521811582044",
"=NORMINV(0.02,40,1.5)": "36.91937663649545",
// NORM.S.DIST
"=NORM.S.DIST(0.8,TRUE)": "0.788144601416603",
// NORMSDIST
"=NORMSDIST(1.333333)": "0.908788725604095",
"=NORMSDIST(0)": "0.5",
// NORM.S.INV
"=NORM.S.INV(0.25)": "-0.674489750223423",
// NORMSINV
"=NORMSINV(0.25)": "-0.674489750223423",
// LARGE
"=LARGE(A1:A5,1)": "3",
"=LARGE(A1:B5,2)": "4",
@ -1375,6 +1396,33 @@ func TestCalcCellValue(t *testing.T) {
// KURT
"=KURT()": "KURT requires at least 1 argument",
"=KURT(F1,INT(1))": "#DIV/0!",
// NORM.DIST
"=NORM.DIST()": "NORM.DIST requires 4 arguments",
// NORMDIST
"=NORMDIST()": "NORMDIST requires 4 arguments",
"=NORMDIST(\"\",0,0,FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=NORMDIST(0,\"\",0,FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=NORMDIST(0,0,\"\",FALSE)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=NORMDIST(0,0,0,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax",
"=NORMDIST(0,0,-1,TRUE)": "#N/A",
// NORM.INV
"=NORM.INV()": "NORM.INV requires 3 arguments",
// NORMINV
"=NORMINV()": "NORMINV requires 3 arguments",
"=NORMINV(\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=NORMINV(0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=NORMINV(0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=NORMINV(0,0,-1)": "#N/A",
"=NORMINV(-1,0,0)": "#N/A",
"=NORMINV(0,0,0)": "#NUM!",
// NORM.S.DIST
"=NORM.S.DIST()": "NORM.S.DIST requires 2 numeric arguments",
// NORMSDIST
"=NORMSDIST()": "NORMSDIST requires 1 numeric argument",
// NORM.S.INV
"=NORM.S.INV()": "NORM.S.INV requires 1 numeric argument",
// NORMSINV
"=NORMSINV()": "NORMSINV requires 1 numeric argument",
// LARGE
"=LARGE()": "LARGE requires 2 arguments",
"=LARGE(A1:A5,0)": "k should be > 0",

4
go.mod
View File

@ -6,8 +6,8 @@ require (
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/richardlehane/mscfb v1.0.3
github.com/stretchr/testify v1.6.1
github.com/xuri/efp v0.0.0-20210311002341-9c6784cb2d17
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670
github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
golang.org/x/text v0.3.5

8
go.sum
View File

@ -11,10 +11,10 @@ github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTK
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/xuri/efp v0.0.0-20210311002341-9c6784cb2d17 h1:Ou4I7pYPQBk/qE9K2y31rawl/ftLHbTJJAFYJPVSyQo=
github.com/xuri/efp v0.0.0-20210311002341-9c6784cb2d17/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670 h1:gzMM0EjIYiRmJI3+jBdFuoynZlpxa2JQZsolKu09BXo=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 h1:EpI0bqf/eX9SdZDwlMmahKM+CDBgNbsXMhsN28XrM8o=
github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb h1:fqpd0EBDzlHRCjiphRR5Zo/RSWWQlWv34418dnEixWk=
golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=