From 5bf4bce9d41b2f8cd9d24e0d57a0d6868ef9433d Mon Sep 17 00:00:00 2001 From: xuri Date: Wed, 6 Apr 2022 00:03:22 +0800 Subject: [PATCH] ref #65, #1196: fix the compatibility issue and added new formula function - New formula functions: MODE and T.TEST --- calc.go | 61 +++++++++++++++++++++++++++++++++ calc_test.go | 91 ++++++++++++++++++++++++++++++++++++++++---------- numfmt.go | 6 ++-- xmlWorkbook.go | 2 +- 4 files changed, 138 insertions(+), 22 deletions(-) diff --git a/calc.go b/calc.go index b0876a8..d921c35 100644 --- a/calc.go +++ b/calc.go @@ -549,6 +549,7 @@ type formulaFuncs struct { // MINUTE // MIRR // MOD +// MODE // MONTH // MROUND // MULTINOMIAL @@ -673,6 +674,7 @@ type formulaFuncs struct { // TRIMMEAN // TRUE // TRUNC +// T.TEST // TTEST // TYPE // UNICHAR @@ -7974,6 +7976,51 @@ func (fn *formulaFuncs) LOGNORMDIST(argsList *list.List) formulaArg { 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 // cumulative distribution function for the Negative Binomial Distribution. // 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) } +// 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 // supplied set of values. The syntax of the function is: // diff --git a/calc_test.go b/calc_test.go index 9b8b226..f6499de 100644 --- a/calc_test.go +++ b/calc_test.go @@ -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) { f := NewFile() f.NewSheet("Sheet2") @@ -4914,12 +4951,18 @@ func TestCalcTTEST(t *testing.T) { } f := prepareCalcData(cellData) formulaList := map[string]string{ - "=TTEST(A1:A12,B1:B12,1,1)": "0.44907068944428", - "=TTEST(A1:A12,B1:B12,1,2)": "0.436717306029283", - "=TTEST(A1:A12,B1:B12,1,3)": "0.436722015384755", - "=TTEST(A1:A12,B1:B12,2,1)": "0.898141378888559", - "=TTEST(A1:A12,B1:B12,2,2)": "0.873434612058567", - "=TTEST(A1:A12,B1:B12,2,3)": "0.873444030769511", + "=TTEST(A1:A12,B1:B12,1,1)": "0.44907068944428", + "=TTEST(A1:A12,B1:B12,1,2)": "0.436717306029283", + "=TTEST(A1:A12,B1:B12,1,3)": "0.436722015384755", + "=TTEST(A1:A12,B1:B12,2,1)": "0.898141378888559", + "=TTEST(A1:A12,B1:B12,2,2)": "0.873434612058567", + "=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 { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) @@ -4928,18 +4971,30 @@ func TestCalcTTEST(t *testing.T) { assert.Equal(t, expected, result, formula) } calcError := map[string]string{ - "=TTEST()": "TTEST requires 4 arguments", - "=TTEST(\"\",B1:B12,1,1)": "#NUM!", - "=TTEST(A1:A12,\"\",1,1)": "#NUM!", - "=TTEST(A1:A12,B1:B12,\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax", - "=TTEST(A1:A12,B1:B12,1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", - "=TTEST(A1:A12,B1:B12,0,1)": "#NUM!", - "=TTEST(A1:A12,B1:B12,1,0)": "#NUM!", - "=TTEST(A1:A2,B1:B1,1,1)": "#N/A", - "=TTEST(A13:A14,B13:B14,1,1)": "#NUM!", - "=TTEST(A12:A13,B12:B13,1,1)": "#DIV/0!", - "=TTEST(A13:A14,B13:B14,1,2)": "#NUM!", - "=TTEST(D1:D4,E1:E4,1,3)": "#NUM!", + "=TTEST()": "TTEST requires 4 arguments", + "=TTEST(\"\",B1:B12,1,1)": "#NUM!", + "=TTEST(A1:A12,\"\",1,1)": "#NUM!", + "=TTEST(A1:A12,B1:B12,\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=TTEST(A1:A12,B1:B12,1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=TTEST(A1:A12,B1:B12,0,1)": "#NUM!", + "=TTEST(A1:A12,B1:B12,1,0)": "#NUM!", + "=TTEST(A1:A2,B1:B1,1,1)": "#N/A", + "=TTEST(A13:A14,B13:B14,1,1)": "#NUM!", + "=TTEST(A12:A13,B12:B13,1,1)": "#DIV/0!", + "=TTEST(A13:A14,B13:B14,1,2)": "#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 { assert.NoError(t, f.SetCellFormula("Sheet1", "C1", formula)) diff --git a/numfmt.go b/numfmt.go index b48c36a..6cb7fc7 100644 --- a/numfmt.go +++ b/numfmt.go @@ -33,9 +33,9 @@ type languageInfo struct { type numberFormat struct { section []nfp.Section t time.Time - sectionIdx int - isNumeric, hours, seconds bool - number float64 + sectionIdx int + isNumeric, hours, seconds bool + number float64 ap, afterPoint, beforePoint, localCode, result, value, valueSectionType string } diff --git a/xmlWorkbook.go b/xmlWorkbook.go index a500a34..a0fce15 100644 --- a/xmlWorkbook.go +++ b/xmlWorkbook.go @@ -40,9 +40,9 @@ type xlsxWorkbook struct { FileVersion *xlsxFileVersion `xml:"fileVersion"` FileSharing *xlsxExtLst `xml:"fileSharing"` WorkbookPr *xlsxWorkbookPr `xml:"workbookPr"` - WorkbookProtection *xlsxWorkbookProtection `xml:"workbookProtection"` AlternateContent *xlsxAlternateContent `xml:"mc:AlternateContent"` DecodeAlternateContent *xlsxInnerXML `xml:"http://schemas.openxmlformats.org/markup-compatibility/2006 AlternateContent"` + WorkbookProtection *xlsxWorkbookProtection `xml:"workbookProtection"` BookViews *xlsxBookViews `xml:"bookViews"` Sheets xlsxSheets `xml:"sheets"` FunctionGroups *xlsxExtLst `xml:"functionGroups"`