ref #65, new formula functions: T.INV and T.INV.2T

- Typo fixed
pull/2/head
xuri 3 years ago
parent be8fc0a4c5
commit ecbc6e2fde
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7

@ -664,6 +664,8 @@ type formulaFuncs struct {
// TEXTJOIN // TEXTJOIN
// TIME // TIME
// TIMEVALUE // TIMEVALUE
// T.INV
// T.INV.2T
// TODAY // TODAY
// TRANSPOSE // TRANSPOSE
// TRIM // TRIM
@ -1265,27 +1267,6 @@ func isOperand(token efp.Token) bool {
return token.TType == efp.TokenTypeOperand && (token.TSubType == efp.TokenSubTypeNumber || token.TSubType == efp.TokenSubTypeText) 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 // parseToken parse basic arithmetic operator priority and evaluate based on
// operators and operands. // operators and operands.
func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Stack) error { 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 // calcInverseIterator directly maps the required parameters for inverse
// distribution functions. // distribution functions.
type calcInverseIterator struct { type calcInverseIterator struct {
name string name string
fp, fDF float64 fp, fDF, nT float64
} }
// chiSqDist implements inverse distribution with left tail for the Chi-Square // callBack implements the callback function for the inverse iterator.
// distribution. func (iterator *calcInverseIterator) callBack(x float64) float64 {
func (iterator *calcInverseIterator) chiSqDist(x float64) float64 { if iterator.name == "CHISQ.INV" {
return iterator.fp - getChiSqDistCDF(x, iterator.fDF) return iterator.fp - getChiSqDistCDF(x, iterator.fDF)
}
return iterator.fp - getTDist(x, iterator.fDF, iterator.nT)
} }
// inverseQuadraticInterpolation inverse quadratic interpolation with // inverseQuadraticInterpolation inverse quadratic interpolation with
@ -6682,7 +6665,7 @@ func inverseQuadraticInterpolation(iterator calcInverseIterator, fAx, fAy, fBx,
bHasToInterpolate = true bHasToInterpolate = true
} }
fPx, fQx, fRx, fPy, fQy = fQx, fRx, fSx, fQy, fRy fPx, fQx, fRx, fPy, fQy = fQx, fRx, fSx, fQy, fRy
fRy = iterator.chiSqDist(fSx) fRy = iterator.callBack(fSx)
if hasChangeOfSign(fAy, fRy) { if hasChangeOfSign(fAy, fRy) {
fBx, fBy = fRx, fRy fBx, fBy = fRx, fRy
} else { } else {
@ -6697,7 +6680,7 @@ func inverseQuadraticInterpolation(iterator calcInverseIterator, fAx, fAy, fBx,
// calcIterateInverse function calculates the iteration for inverse // calcIterateInverse function calculates the iteration for inverse
// distributions. // distributions.
func calcIterateInverse(iterator calcInverseIterator, fAx, fBx float64) float64 { 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 fTemp float64
var nCount int var nCount int
for nCount = 0; nCount < 1000 && !hasChangeOfSign(fAy, fBy); nCount++ { for nCount = 0; nCount < 1000 && !hasChangeOfSign(fAy, fBy); nCount++ {
@ -6709,13 +6692,13 @@ func calcIterateInverse(iterator calcInverseIterator, fAx, fBx float64) float64
} }
fBx = fTemp fBx = fTemp
fBy = fAy fBy = fAy
fAy = iterator.chiSqDist(fAx) fAy = iterator.callBack(fAx)
} else { } else {
fTemp = fBx fTemp = fBx
fBx += 2 * (fBx - fAx) fBx += 2 * (fBx - fAx)
fAx = fTemp fAx = fTemp
fAy = fBy fAy = fBy
fBy = iterator.chiSqDist(fBx) fBy = iterator.callBack(fBx)
} }
} }
if fAy == 0 || fBy == 0 { 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)) 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 // 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:
// //

@ -1167,6 +1167,12 @@ func TestCalcCellValue(t *testing.T) {
// TDIST // TDIST
"=TDIST(1,10,1)": "0.17044656615103", "=TDIST(1,10,1)": "0.17044656615103",
"=TDIST(1,10,2)": "0.34089313230206", "=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
"=TRIMMEAN(A1:B4,10%)": "2.5", "=TRIMMEAN(A1:B4,10%)": "2.5",
"=TRIMMEAN(A1:B4,70%)": "2.5", "=TRIMMEAN(A1:B4,70%)": "2.5",
@ -3048,6 +3054,19 @@ func TestCalcCellValue(t *testing.T) {
"=TDIST(-1,10,1)": "#NUM!", "=TDIST(-1,10,1)": "#NUM!",
"=TDIST(1,0,1)": "#NUM!", "=TDIST(1,0,1)": "#NUM!",
"=TDIST(1,10,0)": "#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()": "TRIMMEAN requires 2 arguments", "=TRIMMEAN()": "TRIMMEAN requires 2 arguments",
"=TRIMMEAN(A1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", "=TRIMMEAN(A1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",

@ -63,7 +63,7 @@ func (f *File) Save() error {
return f.SaveAs(f.Path) 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. // provided path.
func (f *File) SaveAs(name string, opt ...Options) error { func (f *File) SaveAs(name string, opt ...Options) error {
if len(name) > MaxFileNameLength { if len(name) > MaxFileNameLength {
@ -81,7 +81,7 @@ func (f *File) SaveAs(name string, opt ...Options) error {
return ErrWorkbookExt return ErrWorkbookExt
} }
f.setContentTypePartProjectExtensions(contentType) 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 { if err != nil {
return err return err
} }

@ -132,7 +132,7 @@ func (f *File) saveFileList(name string, content []byte) {
f.Pkg.Store(name, append([]byte(xml.Header), content...)) 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) { func readFile(file *zip.File) ([]byte, error) {
rc, err := file.Open() rc, err := file.Open()
if err != nil { if err != nil {
@ -157,8 +157,8 @@ func SplitCellName(cell string) (string, int, error) {
if strings.IndexFunc(cell, alpha) == 0 { if strings.IndexFunc(cell, alpha) == 0 {
i := strings.LastIndexFunc(cell, alpha) i := strings.LastIndexFunc(cell, alpha)
if i >= 0 && i < len(cell)-1 { if i >= 0 && i < len(cell)-1 {
col, rowstr := strings.ReplaceAll(cell[:i+1], "$", ""), cell[i+1:] col, rowStr := strings.ReplaceAll(cell[:i+1], "$", ""), cell[i+1:]
if row, err := strconv.Atoi(rowstr); err == nil && row > 0 { if row, err := strconv.Atoi(rowStr); err == nil && row > 0 {
return col, row, nil 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 // 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. // column name incorrect.
// //
// Example: // Example:
@ -248,14 +248,14 @@ func ColumnNumberToName(num int) (string, error) {
// excelize.CellNameToCoordinates("Z3") // returns 26, 3, nil // excelize.CellNameToCoordinates("Z3") // returns 26, 3, nil
// //
func CellNameToCoordinates(cell string) (int, int, error) { func CellNameToCoordinates(cell string) (int, int, error) {
colname, row, err := SplitCellName(cell) colName, row, err := SplitCellName(cell)
if err != nil { if err != nil {
return -1, -1, newCellNameToCoordinatesError(cell, err) return -1, -1, newCellNameToCoordinatesError(cell, err)
} }
if row > TotalRows { if row > TotalRows {
return -1, -1, ErrMaxRows return -1, -1, ErrMaxRows
} }
col, err := ColumnNameToNumber(colname) col, err := ColumnNameToNumber(colName)
return col, row, err return col, row, err
} }
@ -277,8 +277,8 @@ func CoordinatesToCellName(col, row int, abs ...bool) (string, error) {
sign = "$" sign = "$"
} }
} }
colname, err := ColumnNumberToName(col) colName, err := ColumnNumberToName(col)
return sign + colname + sign + strconv.Itoa(row), err return sign + colName + sign + strconv.Itoa(row), err
} }
// areaRefToCoordinates provides a function to convert area reference to a // 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 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. // flatSqref convert reference sequence to cell coordinates list.
func (f *File) flatSqref(sqref string) (cells map[int][][]int, err error) { func (f *File) flatSqref(sqref string) (cells map[int][][]int, err error) {
var coordinates []int var coordinates []int
@ -365,7 +386,7 @@ func (f *File) flatSqref(sqref string) (cells map[int][][]int, err error) {
return 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 // coordinates array, and return the index of its location, otherwise
// return -1. // return -1.
func inCoordinates(a [][]int, x []int) int { func inCoordinates(a [][]int, x []int) int {
@ -391,7 +412,7 @@ func inStrSlice(a []string, x string, caseSensitive bool) int {
return -1 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. // float64 array, and return the index of its location, otherwise return -1.
func inFloat64Slice(a []float64, x float64) int { func inFloat64Slice(a []float64, x float64) int {
for idx, n := range a { 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. // boolPtr returns a pointer to a bool with the given value.
func boolPtr(b bool) *bool { return &b } 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 } func intPtr(i int) *int { return &i }
// float64Ptr returns a pointer to a float64 with the given value. // 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) 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. // component part path.
func (f *File) addNameSpaces(path string, ns xml.Attr) { func (f *File) addNameSpaces(path string, ns xml.Attr) {
exist := false exist := false
@ -715,7 +736,7 @@ var (
// bstrUnmarshal parses the binary basic string, this will trim escaped string // bstrUnmarshal parses the binary basic string, this will trim escaped string
// literal which not permitted in an XML 1.0 document. The basic 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 // that cannot be directly represented in XML as defined by the XML 1.0
// specification, shall be escaped using the Unicode numerical character // specification, shall be escaped using the Unicode numerical character
// representation escape character format _xHHHH_, where H represents a // representation escape character format _xHHHH_, where H represents a

@ -33,9 +33,9 @@ type languageInfo struct {
type numberFormat struct { type numberFormat struct {
section []nfp.Section section []nfp.Section
t time.Time t time.Time
sectionIdx int sectionIdx int
isNumberic, hours, seconds bool isNumeric, hours, seconds bool
number float64 number float64
ap, afterPoint, beforePoint, localCode, result, value, valueSectionType string 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 // prepareNumberic split the number into two before and after parts by a
// decimal point. // decimal point.
func (nf *numberFormat) prepareNumberic(value string) { func (nf *numberFormat) prepareNumberic(value string) {
if nf.isNumberic, _ = isNumeric(value); !nf.isNumberic { if nf.isNumeric, _ = isNumeric(value); !nf.isNumeric {
return return
} }
} }
@ -297,7 +297,7 @@ func format(value, numFmt string) string {
if section.Type != nf.valueSectionType { if section.Type != nf.valueSectionType {
continue continue
} }
if nf.isNumberic { if nf.isNumeric {
switch section.Type { switch section.Type {
case nfp.TokenSectionPositive: case nfp.TokenSectionPositive:
return nf.positiveHandler() return nf.positiveHandler()

Loading…
Cancel
Save