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
// 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:
//

@ -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",

@ -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
}

@ -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

@ -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()

Loading…
Cancel
Save