summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxuri <xuri.me@gmail.com>2022-04-04 00:21:33 +0800
committerxuri <xuri.me@gmail.com>2022-04-04 00:21:33 +0800
commitecbc6e2fde1941cb5ac9e5f3bfce329e7bfa8825 (patch)
tree24597853107787b521fc98263f19c242ac2be6f5
parentbe8fc0a4c5795bb793b171c25fd90e0369812a05 (diff)
ref #65, new formula functions: T.INV and T.INV.2T
- Typo fixed
-rw-r--r--calc.go111
-rw-r--r--calc_test.go19
-rw-r--r--file.go4
-rw-r--r--lib.go47
-rw-r--r--numfmt.go10
5 files changed, 140 insertions, 51 deletions
diff --git a/calc.go b/calc.go
index ad62596..c375b71 100644
--- a/calc.go
+++ b/calc.go
@@ -664,6 +664,8 @@ type formulaFuncs struct {
// TEXTJOIN
// TIME
// TIMEVALUE
+// T.INV
+// T.INV.2T
// TODAY
// TRANSPOSE
// TRIM
@@ -1265,27 +1267,6 @@ func isOperand(token efp.Token) bool {
return token.TType == efp.TokenTypeOperand && (token.TSubType == efp.TokenSubTypeNumber || token.TSubType == efp.TokenSubTypeText)
}
-// getDefinedNameRefTo convert defined name to reference range.
-func (f *File) getDefinedNameRefTo(definedNameName string, currentSheet string) (refTo string) {
- var workbookRefTo, worksheetRefTo string
- for _, definedName := range f.GetDefinedName() {
- if definedName.Name == definedNameName {
- // worksheet scope takes precedence over scope workbook when both definedNames exist
- if definedName.Scope == "Workbook" {
- workbookRefTo = definedName.RefersTo
- }
- if definedName.Scope == currentSheet {
- worksheetRefTo = definedName.RefersTo
- }
- }
- }
- refTo = workbookRefTo
- if worksheetRefTo != "" {
- refTo = worksheetRefTo
- }
- return
-}
-
// parseToken parse basic arithmetic operator priority and evaluate based on
// operators and operands.
func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Stack) error {
@@ -6647,14 +6628,16 @@ func hasChangeOfSign(u, w float64) bool {
// calcInverseIterator directly maps the required parameters for inverse
// distribution functions.
type calcInverseIterator struct {
- name string
- fp, fDF float64
+ name string
+ fp, fDF, nT float64
}
-// chiSqDist implements inverse distribution with left tail for the Chi-Square
-// distribution.
-func (iterator *calcInverseIterator) chiSqDist(x float64) float64 {
- return iterator.fp - getChiSqDistCDF(x, iterator.fDF)
+// callBack implements the callback function for the inverse iterator.
+func (iterator *calcInverseIterator) callBack(x float64) float64 {
+ if iterator.name == "CHISQ.INV" {
+ return iterator.fp - getChiSqDistCDF(x, iterator.fDF)
+ }
+ return iterator.fp - getTDist(x, iterator.fDF, iterator.nT)
}
// inverseQuadraticInterpolation inverse quadratic interpolation with
@@ -6682,7 +6665,7 @@ func inverseQuadraticInterpolation(iterator calcInverseIterator, fAx, fAy, fBx,
bHasToInterpolate = true
}
fPx, fQx, fRx, fPy, fQy = fQx, fRx, fSx, fQy, fRy
- fRy = iterator.chiSqDist(fSx)
+ fRy = iterator.callBack(fSx)
if hasChangeOfSign(fAy, fRy) {
fBx, fBy = fRx, fRy
} else {
@@ -6697,7 +6680,7 @@ func inverseQuadraticInterpolation(iterator calcInverseIterator, fAx, fAy, fBx,
// calcIterateInverse function calculates the iteration for inverse
// distributions.
func calcIterateInverse(iterator calcInverseIterator, fAx, fBx float64) float64 {
- fAy, fBy := iterator.chiSqDist(fAx), iterator.chiSqDist(fBx)
+ fAy, fBy := iterator.callBack(fAx), iterator.callBack(fBx)
var fTemp float64
var nCount int
for nCount = 0; nCount < 1000 && !hasChangeOfSign(fAy, fBy); nCount++ {
@@ -6709,13 +6692,13 @@ func calcIterateInverse(iterator calcInverseIterator, fAx, fBx float64) float64
}
fBx = fTemp
fBy = fAy
- fAy = iterator.chiSqDist(fAx)
+ fAy = iterator.callBack(fAx)
} else {
fTemp = fBx
fBx += 2 * (fBx - fAx)
fAx = fTemp
fAy = fBy
- fBy = iterator.chiSqDist(fBx)
+ fBy = iterator.callBack(fBx)
}
}
if fAy == 0 || fBy == 0 {
@@ -9152,6 +9135,72 @@ func (fn *formulaFuncs) TDIST(argsList *list.List) formulaArg {
return newNumberFormulaArg(getTDist(x.Number, degrees.Number, tails.Number))
}
+// TdotINV function calculates the left-tailed inverse of 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:
+//
+// T.INV(probability,degrees_freedom)
+//
+func (fn *formulaFuncs) TdotINV(argsList *list.List) formulaArg {
+ if argsList.Len() != 2 {
+ return newErrorFormulaArg(formulaErrorVALUE, "T.INV requires 2 arguments")
+ }
+ var probability, degrees formulaArg
+ if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
+ return probability
+ }
+ if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber {
+ return degrees
+ }
+ if probability.Number <= 0 || probability.Number >= 1 || degrees.Number < 1 {
+ return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
+ }
+ if probability.Number < 0.5 {
+ return newNumberFormulaArg(-calcIterateInverse(calcInverseIterator{
+ name: "T.INV",
+ fp: 1 - probability.Number,
+ fDF: degrees.Number,
+ nT: 4,
+ }, degrees.Number/2, degrees.Number))
+ }
+ return newNumberFormulaArg(calcIterateInverse(calcInverseIterator{
+ name: "T.INV",
+ fp: probability.Number,
+ fDF: degrees.Number,
+ nT: 4,
+ }, degrees.Number/2, degrees.Number))
+}
+
+// TdotINVdot2T function calculates the inverse of the two-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.INV.2T(probability,degrees_freedom)
+//
+func (fn *formulaFuncs) TdotINVdot2T(argsList *list.List) formulaArg {
+ if argsList.Len() != 2 {
+ return newErrorFormulaArg(formulaErrorVALUE, "T.INV.2T requires 2 arguments")
+ }
+ var probability, degrees formulaArg
+ if probability = argsList.Front().Value.(formulaArg).ToNumber(); probability.Type != ArgNumber {
+ return probability
+ }
+ if degrees = argsList.Back().Value.(formulaArg).ToNumber(); degrees.Type != ArgNumber {
+ return degrees
+ }
+ if probability.Number <= 0 || probability.Number > 1 || degrees.Number < 1 {
+ return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM)
+ }
+ return newNumberFormulaArg(calcIterateInverse(calcInverseIterator{
+ name: "T.INV.2T",
+ fp: probability.Number,
+ fDF: degrees.Number,
+ nT: 2,
+ }, degrees.Number/2, degrees.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 6324344..8565038 100644
--- a/calc_test.go
+++ b/calc_test.go
@@ -1167,6 +1167,12 @@ func TestCalcCellValue(t *testing.T) {
// TDIST
"=TDIST(1,10,1)": "0.17044656615103",
"=TDIST(1,10,2)": "0.34089313230206",
+ // T.INV
+ "=T.INV(0.25,10)": "-0.699812061312432",
+ "=T.INV(0.75,10)": "0.699812061312432",
+ // T.INV.2T
+ "=T.INV.2T(1,10)": "0",
+ "=T.INV.2T(0.5,10)": "0.699812061312432",
// TRIMMEAN
"=TRIMMEAN(A1:B4,10%)": "2.5",
"=TRIMMEAN(A1:B4,70%)": "2.5",
@@ -3048,6 +3054,19 @@ func TestCalcCellValue(t *testing.T) {
"=TDIST(-1,10,1)": "#NUM!",
"=TDIST(1,0,1)": "#NUM!",
"=TDIST(1,10,0)": "#NUM!",
+ // T.INV
+ "=T.INV()": "T.INV requires 2 arguments",
+ "=T.INV(\"\",10)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+ "=T.INV(0.25,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+ "=T.INV(0,10)": "#NUM!",
+ "=T.INV(1,10)": "#NUM!",
+ "=T.INV(0.25,0.5)": "#NUM!",
+ // T.INV.2T
+ "=T.INV.2T()": "T.INV.2T requires 2 arguments",
+ "=T.INV.2T(\"\",10)": "strconv.ParseFloat: parsing \"\": invalid syntax",
+ "=T.INV.2T(0.25,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
+ "=T.INV.2T(0,10)": "#NUM!",
+ "=T.INV.2T(0.25,0.5)": "#NUM!",
// TRIMMEAN
"=TRIMMEAN()": "TRIMMEAN requires 2 arguments",
"=TRIMMEAN(A1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
diff --git a/file.go b/file.go
index 0135e20..9707a79 100644
--- a/file.go
+++ b/file.go
@@ -63,7 +63,7 @@ func (f *File) Save() error {
return f.SaveAs(f.Path)
}
-// SaveAs provides a function to create or update to an spreadsheet at the
+// SaveAs provides a function to create or update to a spreadsheet at the
// provided path.
func (f *File) SaveAs(name string, opt ...Options) error {
if len(name) > MaxFileNameLength {
@@ -81,7 +81,7 @@ func (f *File) SaveAs(name string, opt ...Options) error {
return ErrWorkbookExt
}
f.setContentTypePartProjectExtensions(contentType)
- file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0o600)
+ file, err := os.OpenFile(filepath.Clean(name), os.O_WRONLY|os.O_TRUNC|os.O_CREATE, os.ModePerm)
if err != nil {
return err
}
diff --git a/lib.go b/lib.go
index 4205e08..723b976 100644
--- a/lib.go
+++ b/lib.go
@@ -132,7 +132,7 @@ func (f *File) saveFileList(name string, content []byte) {
f.Pkg.Store(name, append([]byte(xml.Header), content...))
}
-// Read file content as string in a archive file.
+// Read file content as string in an archive file.
func readFile(file *zip.File) ([]byte, error) {
rc, err := file.Open()
if err != nil {
@@ -157,8 +157,8 @@ func SplitCellName(cell string) (string, int, error) {
if strings.IndexFunc(cell, alpha) == 0 {
i := strings.LastIndexFunc(cell, alpha)
if i >= 0 && i < len(cell)-1 {
- col, rowstr := strings.ReplaceAll(cell[:i+1], "$", ""), cell[i+1:]
- if row, err := strconv.Atoi(rowstr); err == nil && row > 0 {
+ col, rowStr := strings.ReplaceAll(cell[:i+1], "$", ""), cell[i+1:]
+ if row, err := strconv.Atoi(rowStr); err == nil && row > 0 {
return col, row, nil
}
}
@@ -187,7 +187,7 @@ func JoinCellName(col string, row int) (string, error) {
}
// ColumnNameToNumber provides a function to convert Excel sheet column name
-// to int. Column name case insensitive. The function returns an error if
+// to int. Column name case-insensitive. The function returns an error if
// column name incorrect.
//
// Example:
@@ -248,14 +248,14 @@ func ColumnNumberToName(num int) (string, error) {
// excelize.CellNameToCoordinates("Z3") // returns 26, 3, nil
//
func CellNameToCoordinates(cell string) (int, int, error) {
- colname, row, err := SplitCellName(cell)
+ colName, row, err := SplitCellName(cell)
if err != nil {
return -1, -1, newCellNameToCoordinatesError(cell, err)
}
if row > TotalRows {
return -1, -1, ErrMaxRows
}
- col, err := ColumnNameToNumber(colname)
+ col, err := ColumnNameToNumber(colName)
return col, row, err
}
@@ -277,8 +277,8 @@ func CoordinatesToCellName(col, row int, abs ...bool) (string, error) {
sign = "$"
}
}
- colname, err := ColumnNumberToName(col)
- return sign + colname + sign + strconv.Itoa(row), err
+ colName, err := ColumnNumberToName(col)
+ return sign + colName + sign + strconv.Itoa(row), err
}
// areaRefToCoordinates provides a function to convert area reference to a
@@ -336,6 +336,27 @@ func (f *File) coordinatesToAreaRef(coordinates []int) (string, error) {
return firstCell + ":" + lastCell, err
}
+// getDefinedNameRefTo convert defined name to reference range.
+func (f *File) getDefinedNameRefTo(definedNameName string, currentSheet string) (refTo string) {
+ var workbookRefTo, worksheetRefTo string
+ for _, definedName := range f.GetDefinedName() {
+ if definedName.Name == definedNameName {
+ // worksheet scope takes precedence over scope workbook when both definedNames exist
+ if definedName.Scope == "Workbook" {
+ workbookRefTo = definedName.RefersTo
+ }
+ if definedName.Scope == currentSheet {
+ worksheetRefTo = definedName.RefersTo
+ }
+ }
+ }
+ refTo = workbookRefTo
+ if worksheetRefTo != "" {
+ refTo = worksheetRefTo
+ }
+ return
+}
+
// flatSqref convert reference sequence to cell coordinates list.
func (f *File) flatSqref(sqref string) (cells map[int][][]int, err error) {
var coordinates []int
@@ -365,7 +386,7 @@ func (f *File) flatSqref(sqref string) (cells map[int][][]int, err error) {
return
}
-// inCoordinates provides a method to check if an coordinate is present in
+// inCoordinates provides a method to check if a coordinate is present in
// coordinates array, and return the index of its location, otherwise
// return -1.
func inCoordinates(a [][]int, x []int) int {
@@ -391,7 +412,7 @@ func inStrSlice(a []string, x string, caseSensitive bool) int {
return -1
}
-// inFloat64Slice provides a method to check if an element is present in an
+// inFloat64Slice provides a method to check if an element is present in a
// float64 array, and return the index of its location, otherwise return -1.
func inFloat64Slice(a []float64, x float64) int {
for idx, n := range a {
@@ -405,7 +426,7 @@ func inFloat64Slice(a []float64, x float64) int {
// boolPtr returns a pointer to a bool with the given value.
func boolPtr(b bool) *bool { return &b }
-// intPtr returns a pointer to a int with the given value.
+// intPtr returns a pointer to an int with the given value.
func intPtr(i int) *int { return &i }
// float64Ptr returns a pointer to a float64 with the given value.
@@ -626,7 +647,7 @@ func (f *File) replaceNameSpaceBytes(path string, contentMarshal []byte) []byte
return bytesReplace(contentMarshal, oldXmlns, newXmlns, -1)
}
-// addNameSpaces provides a function to add a XML attribute by the given
+// addNameSpaces provides a function to add an XML attribute by the given
// component part path.
func (f *File) addNameSpaces(path string, ns xml.Attr) {
exist := false
@@ -715,7 +736,7 @@ var (
// bstrUnmarshal parses the binary basic string, this will trim escaped string
// literal which not permitted in an XML 1.0 document. The basic string
-// variant type can store any valid Unicode character. Unicode characters
+// variant type can store any valid Unicode character. Unicode's characters
// that cannot be directly represented in XML as defined by the XML 1.0
// specification, shall be escaped using the Unicode numerical character
// representation escape character format _xHHHH_, where H represents a
diff --git a/numfmt.go b/numfmt.go
index 685005f..b48c36a 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
- isNumberic, hours, seconds bool
- number float64
+ sectionIdx int
+ isNumeric, hours, seconds bool
+ number float64
ap, afterPoint, beforePoint, localCode, result, value, valueSectionType string
}
@@ -279,7 +279,7 @@ var (
// prepareNumberic split the number into two before and after parts by a
// decimal point.
func (nf *numberFormat) prepareNumberic(value string) {
- if nf.isNumberic, _ = isNumeric(value); !nf.isNumberic {
+ if nf.isNumeric, _ = isNumeric(value); !nf.isNumeric {
return
}
}
@@ -297,7 +297,7 @@ func format(value, numFmt string) string {
if section.Type != nf.valueSectionType {
continue
}
- if nf.isNumberic {
+ if nf.isNumeric {
switch section.Type {
case nfp.TokenSectionPositive:
return nf.positiveHandler()