From b8345731a477633bc82216dbc398faecafaf894f Mon Sep 17 00:00:00 2001 From: xuri Date: Sat, 2 Apr 2022 00:04:21 +0800 Subject: [PATCH] ref #65, new formula functions: T.DIST and TDIST --- calc.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++++ calc_test.go | 23 ++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/calc.go b/calc.go index 5bad72a..6252363 100644 --- a/calc.go +++ b/calc.go @@ -657,6 +657,8 @@ type formulaFuncs struct { // TBILLEQ // TBILLPRICE // TBILLYIELD +// T.DIST +// TDIST // TEXTJOIN // TIME // TIMEVALUE @@ -9008,6 +9010,94 @@ func (fn *formulaFuncs) STDEVdotP(argsList *list.List) formulaArg { return fn.stdevp("STDEV.P", argsList) } +// getTDist is an implementation for the beta distribution probability density +// function. +func getTDist(T, fDF, nType float64) float64 { + var res float64 + switch nType { + case 1: + res = 0.5 * getBetaDist(fDF/(fDF+T*T), fDF/2, 0.5) + break + case 2: + res = getBetaDist(fDF/(fDF+T*T), fDF/2, 0.5) + break + case 3: + res = math.Pow(1+(T*T/fDF), -(fDF+1)/2) / (math.Sqrt(fDF) * getBeta(0.5, fDF/2.0)) + break + case 4: + X := fDF / (T*T + fDF) + R := 0.5 * getBetaDist(X, 0.5*fDF, 0.5) + res = 1 - R + if T < 0 { + res = R + } + break + } + return res +} + +// TdotDIST function calculates the one-tailed Student's T Distribution, which +// is a continuous probability distribution that is frequently used for +// testing hypotheses on small sample data sets. The syntax of the function +// is: +// +// T.DIST(x,degrees_freedom,cumulative) +// +func (fn *formulaFuncs) TdotDIST(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "T.DIST requires 3 arguments") + } + var x, degrees, cumulative formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if degrees = argsList.Front().Next().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { + return degrees + } + if cumulative = argsList.Back().Value.(formulaArg).ToBool(); cumulative.Type != ArgNumber { + return cumulative + } + if cumulative.Number == 1 && degrees.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if cumulative.Number == 0 { + if degrees.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + if degrees.Number == 0 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(getTDist(x.Number, degrees.Number, 3)) + } + return newNumberFormulaArg(getTDist(x.Number, degrees.Number, 4)) +} + +// TDIST function calculates the Student's T Distribution, which is a +// continuous probability distribution that is frequently used for testing +// hypotheses on small sample data sets. The syntax of the function is: +// +// TDIST(x,degrees_freedom,tails) +// +func (fn *formulaFuncs) TDIST(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "TDIST requires 3 arguments") + } + var x, degrees, tails formulaArg + if x = argsList.Front().Value.(formulaArg).ToNumber(); x.Type != ArgNumber { + return x + } + if degrees = argsList.Front().Next().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber { + return degrees + } + if tails = argsList.Back().Value.(formulaArg).ToNumber(); tails.Type != ArgNumber { + return tails + } + if x.Number < 0 || degrees.Number < 1 || (tails.Number != 1 && tails.Number != 2) { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + return newNumberFormulaArg(getTDist(x.Number, degrees.Number, tails.Number)) +} + // TRIMMEAN function calculates the trimmed mean (or truncated mean) of a // supplied set of values. The syntax of the function is: // diff --git a/calc_test.go b/calc_test.go index 2b76ed3..321934f 100644 --- a/calc_test.go +++ b/calc_test.go @@ -1155,6 +1155,13 @@ func TestCalcCellValue(t *testing.T) { "=STDEVP(A1:B2,6,-1)": "2.40947204913349", // STDEV.P "=STDEV.P(A1:B2,6,-1)": "2.40947204913349", + // T.DIST + "=T.DIST(1,10,TRUE)": "0.82955343384897", + "=T.DIST(-1,10,TRUE)": "0.17044656615103", + "=T.DIST(-1,10,FALSE)": "0.230361989229139", + // TDIST + "=TDIST(1,10,1)": "0.17044656615103", + "=TDIST(1,10,2)": "0.34089313230206", // TRIMMEAN "=TRIMMEAN(A1:B4,10%)": "2.5", "=TRIMMEAN(A1:B4,70%)": "2.5", @@ -3009,6 +3016,22 @@ func TestCalcCellValue(t *testing.T) { // STDEV.P "=STDEV.P()": "STDEV.P requires at least 1 argument", "=STDEV.P(\"\")": "#DIV/0!", + // T.DIST + "=T.DIST()": "T.DIST requires 3 arguments", + "=T.DIST(\"\",10,TRUE)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=T.DIST(1,\"\",TRUE)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=T.DIST(1,10,\"\")": "strconv.ParseBool: parsing \"\": invalid syntax", + "=T.DIST(1,0,TRUE)": "#NUM!", + "=T.DIST(1,-1,FALSE)": "#NUM!", + "=T.DIST(1,0,FALSE)": "#DIV/0!", + // TDIST + "=TDIST()": "TDIST requires 3 arguments", + "=TDIST(\"\",10,1)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=TDIST(1,\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=TDIST(1,10,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=TDIST(-1,10,1)": "#NUM!", + "=TDIST(1,0,1)": "#NUM!", + "=TDIST(1,10,0)": "#NUM!", // TRIMMEAN "=TRIMMEAN()": "TRIMMEAN requires 2 arguments", "=TRIMMEAN(A1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",