ref #65, #1196: fix the compatibility issue and added new formula function

- New formula functions: MODE and T.TEST
pull/2/head
xuri 3 years ago
parent 26174a2c43
commit 5bf4bce9d4
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7

@ -549,6 +549,7 @@ type formulaFuncs struct {
// MINUTE // MINUTE
// MIRR // MIRR
// MOD // MOD
// MODE
// MONTH // MONTH
// MROUND // MROUND
// MULTINOMIAL // MULTINOMIAL
@ -673,6 +674,7 @@ type formulaFuncs struct {
// TRIMMEAN // TRIMMEAN
// TRUE // TRUE
// TRUNC // TRUNC
// T.TEST
// TTEST // TTEST
// TYPE // TYPE
// UNICHAR // UNICHAR
@ -7974,6 +7976,51 @@ func (fn *formulaFuncs) LOGNORMDIST(argsList *list.List) formulaArg {
return fn.NORMSDIST(args) return fn.NORMSDIST(args)
} }
// MODE function returns the statistical mode (the most frequently occurring
// value) of a list of supplied numbers. If there are 2 or more most
// frequently occurring values in the supplied data, the function returns the
// lowest of these values The syntax of the function is:
//
// MODE(number1,[number2],...)
//
func (fn *formulaFuncs) MODE(argsList *list.List) formulaArg {
if argsList.Len() < 1 {
return newErrorFormulaArg(formulaErrorVALUE, "MODE requires at least 1 argument")
}
var values []float64
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
cells := arg.Value.(formulaArg)
if cells.Type != ArgMatrix && cells.ToNumber().Type != ArgNumber {
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
}
for _, cell := range cells.ToList() {
if num := cell.ToNumber(); num.Type == ArgNumber {
values = append(values, num.Number)
}
}
}
sort.Float64s(values)
cnt := len(values)
var count, modeCnt int
var mode float64
for i := 0; i < cnt; i++ {
count = 0
for j := 0; j < cnt; j++ {
if j != i && values[j] == values[i] {
count++
}
}
if count > modeCnt {
modeCnt = count
mode = values[i]
}
}
if modeCnt == 0 {
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
return newNumberFormulaArg(mode)
}
// NEGBINOMdotDIST function calculates the probability mass function or the // NEGBINOMdotDIST function calculates the probability mass function or the
// cumulative distribution function for the Negative Binomial Distribution. // cumulative distribution function for the Negative Binomial Distribution.
// This gives the probability that there will be a given number of failures // This gives the probability that there will be a given number of failures
@ -9342,6 +9389,20 @@ func (fn *formulaFuncs) TTEST(argsList *list.List) formulaArg {
return fn.tTest(array1.Matrix, array2.Matrix, tails.Number, typeArg.Number) return fn.tTest(array1.Matrix, array2.Matrix, tails.Number, typeArg.Number)
} }
// TdotTEST function calculates the probability associated with the Student's T
// Test, which is commonly used for identifying whether two data sets are
// likely to have come from the same two underlying populations with the same
// mean. The syntax of the function is:
//
// T.TEST(array1,array2,tails,type)
//
func (fn *formulaFuncs) TdotTEST(argsList *list.List) formulaArg {
if argsList.Len() != 4 {
return newErrorFormulaArg(formulaErrorVALUE, "T.TEST requires 4 arguments")
}
return fn.TTEST(argsList)
}
// TRIMMEAN function calculates the trimmed mean (or truncated mean) of a // TRIMMEAN function calculates the trimmed mean (or truncated mean) of a
// supplied set of values. The syntax of the function is: // supplied set of values. The syntax of the function is:
// //

@ -4865,6 +4865,43 @@ func TestCalcISFORMULA(t *testing.T) {
} }
} }
func TestCalcMODE(t *testing.T) {
cellData := [][]interface{}{
{1, 1},
{1, 1},
{2, 2},
{2, 2},
{3, 2},
{3},
{3},
{4},
{4},
{4},
}
f := prepareCalcData(cellData)
formulaList := map[string]string{
"=MODE(A1:A10)": "3",
"=MODE(B1:B6)": "2",
}
for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.NoError(t, err, formula)
assert.Equal(t, expected, result, formula)
}
calcError := map[string]string{
"=MODE()": "MODE requires at least 1 argument",
"=MODE(0,\"\")": "#VALUE!",
"=MODE(D1:D3)": "#N/A",
}
for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
result, err := f.CalcCellValue("Sheet1", "C1")
assert.EqualError(t, err, expected, formula)
assert.Equal(t, "", result, formula)
}
}
func TestCalcSHEET(t *testing.T) { func TestCalcSHEET(t *testing.T) {
f := NewFile() f := NewFile()
f.NewSheet("Sheet2") f.NewSheet("Sheet2")
@ -4920,6 +4957,12 @@ func TestCalcTTEST(t *testing.T) {
"=TTEST(A1:A12,B1:B12,2,1)": "0.898141378888559", "=TTEST(A1:A12,B1:B12,2,1)": "0.898141378888559",
"=TTEST(A1:A12,B1:B12,2,2)": "0.873434612058567", "=TTEST(A1:A12,B1:B12,2,2)": "0.873434612058567",
"=TTEST(A1:A12,B1:B12,2,3)": "0.873444030769511", "=TTEST(A1:A12,B1:B12,2,3)": "0.873444030769511",
"=T.TEST(A1:A12,B1:B12,1,1)": "0.44907068944428",
"=T.TEST(A1:A12,B1:B12,1,2)": "0.436717306029283",
"=T.TEST(A1:A12,B1:B12,1,3)": "0.436722015384755",
"=T.TEST(A1:A12,B1:B12,2,1)": "0.898141378888559",
"=T.TEST(A1:A12,B1:B12,2,2)": "0.873434612058567",
"=T.TEST(A1:A12,B1:B12,2,3)": "0.873444030769511",
} }
for formula, expected := range formulaList { for formula, expected := range formulaList {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))
@ -4940,6 +4983,18 @@ func TestCalcTTEST(t *testing.T) {
"=TTEST(A12:A13,B12:B13,1,1)": "#DIV/0!", "=TTEST(A12:A13,B12:B13,1,1)": "#DIV/0!",
"=TTEST(A13:A14,B13:B14,1,2)": "#NUM!", "=TTEST(A13:A14,B13:B14,1,2)": "#NUM!",
"=TTEST(D1:D4,E1:E4,1,3)": "#NUM!", "=TTEST(D1:D4,E1:E4,1,3)": "#NUM!",
"=T.TEST()": "T.TEST requires 4 arguments",
"=T.TEST(\"\",B1:B12,1,1)": "#NUM!",
"=T.TEST(A1:A12,\"\",1,1)": "#NUM!",
"=T.TEST(A1:A12,B1:B12,\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=T.TEST(A1:A12,B1:B12,1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
"=T.TEST(A1:A12,B1:B12,0,1)": "#NUM!",
"=T.TEST(A1:A12,B1:B12,1,0)": "#NUM!",
"=T.TEST(A1:A2,B1:B1,1,1)": "#N/A",
"=T.TEST(A13:A14,B13:B14,1,1)": "#NUM!",
"=T.TEST(A12:A13,B12:B13,1,1)": "#DIV/0!",
"=T.TEST(A13:A14,B13:B14,1,2)": "#NUM!",
"=T.TEST(D1:D4,E1:E4,1,3)": "#NUM!",
} }
for formula, expected := range calcError { for formula, expected := range calcError {
assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula))

@ -40,9 +40,9 @@ type xlsxWorkbook struct {
FileVersion *xlsxFileVersion `xml:"fileVersion"` FileVersion *xlsxFileVersion `xml:"fileVersion"`
FileSharing *xlsxExtLst `xml:"fileSharing"` FileSharing *xlsxExtLst `xml:"fileSharing"`
WorkbookPr *xlsxWorkbookPr `xml:"workbookPr"` WorkbookPr *xlsxWorkbookPr `xml:"workbookPr"`
WorkbookProtection *xlsxWorkbookProtection `xml:"workbookProtection"`
AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"` AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"`
DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"` DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"`
WorkbookProtection *xlsxWorkbookProtection `xml:"workbookProtection"`
BookViews *xlsxBookViews `xml:"bookViews"` BookViews *xlsxBookViews `xml:"bookViews"`
Sheets xlsxSheets `xml:"sheets"` Sheets xlsxSheets `xml:"sheets"`
FunctionGroups *xlsxExtLst `xml:"functionGroups"` FunctionGroups *xlsxExtLst `xml:"functionGroups"`

Loading…
Cancel
Save