ref #65: new formula functions IFNA and IFS and fix string compare result issue in arithmetic operations

pull/2/head
xuri 3 years ago
parent 620f873186
commit 5f907b7824
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7

@ -357,6 +357,8 @@ type formulaFuncs struct {
// HLOOKUP
// IF
// IFERROR
// IFNA
// IFS
// IMABS
// IMAGINARY
// IMARGUMENT
@ -754,12 +756,12 @@ func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token,
}
// calcPow evaluate exponentiation arithmetic operations.
func calcPow(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
func calcPow(rOpd, lOpd efp.Token, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
if err != nil {
return err
}
@ -769,54 +771,106 @@ func calcPow(rOpd, lOpd string, opdStack *Stack) error {
}
// calcEq evaluate equal arithmetic operations.
func calcEq(rOpd, lOpd string, opdStack *Stack) error {
func calcEq(rOpd, lOpd efp.Token, opdStack *Stack) error {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd == lOpd)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
return nil
}
// calcNEq evaluate not equal arithmetic operations.
func calcNEq(rOpd, lOpd string, opdStack *Stack) error {
func calcNEq(rOpd, lOpd efp.Token, opdStack *Stack) error {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd != lOpd)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
return nil
}
// calcL evaluate less than arithmetic operations.
func calcL(rOpd, lOpd string, opdStack *Stack) error {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) == -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
func calcL(rOpd, lOpd efp.Token, opdStack *Stack) error {
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber {
lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64)
rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64)
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal < rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) == -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
return nil
}
// calcLe evaluate less than or equal arithmetic operations.
func calcLe(rOpd, lOpd string, opdStack *Stack) error {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) != 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
func calcLe(rOpd, lOpd efp.Token, opdStack *Stack) error {
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber {
lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64)
rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64)
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal <= rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) != 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
return nil
}
// calcG evaluate greater than or equal arithmetic operations.
func calcG(rOpd, lOpd string, opdStack *Stack) error {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) == 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
func calcG(rOpd, lOpd efp.Token, opdStack *Stack) error {
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber {
lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64)
rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64)
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal > rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) == 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
return nil
}
// calcGe evaluate greater than or equal arithmetic operations.
func calcGe(rOpd, lOpd string, opdStack *Stack) error {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd, rOpd) != -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
func calcGe(rOpd, lOpd efp.Token, opdStack *Stack) error {
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber {
lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64)
rOpdVal, _ := strconv.ParseFloat(rOpd.TValue, 64)
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(lOpdVal >= rOpdVal)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeText {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) != -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber {
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
return nil
}
// calcSplice evaluate splice '&' operations.
func calcSplice(rOpd, lOpd string, opdStack *Stack) error {
opdStack.Push(efp.Token{TValue: lOpd + rOpd, TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
func calcSplice(rOpd, lOpd efp.Token, opdStack *Stack) error {
opdStack.Push(efp.Token{TValue: lOpd.TValue + rOpd.TValue, TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
return nil
}
// calcAdd evaluate addition arithmetic operations.
func calcAdd(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
func calcAdd(rOpd, lOpd efp.Token, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
if err != nil {
return err
}
@ -826,12 +880,12 @@ func calcAdd(rOpd, lOpd string, opdStack *Stack) error {
}
// calcSubtract evaluate subtraction arithmetic operations.
func calcSubtract(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
func calcSubtract(rOpd, lOpd efp.Token, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
if err != nil {
return err
}
@ -841,12 +895,12 @@ func calcSubtract(rOpd, lOpd string, opdStack *Stack) error {
}
// calcMultiply evaluate multiplication arithmetic operations.
func calcMultiply(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
func calcMultiply(rOpd, lOpd efp.Token, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
if err != nil {
return err
}
@ -856,12 +910,12 @@ func calcMultiply(rOpd, lOpd string, opdStack *Stack) error {
}
// calcDiv evaluate division arithmetic operations.
func calcDiv(rOpd, lOpd string, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd, 64)
func calcDiv(rOpd, lOpd efp.Token, opdStack *Stack) error {
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
if err != nil {
return err
}
rOpdVal, err := strconv.ParseFloat(rOpd, 64)
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
if err != nil {
return err
}
@ -887,7 +941,7 @@ func calculate(opdStack *Stack, opt efp.Token) error {
result := 0 - opdVal
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
}
tokenCalcFunc := map[string]func(rOpd, lOpd string, opdStack *Stack) error{
tokenCalcFunc := map[string]func(rOpd, lOpd efp.Token, opdStack *Stack) error{
"^": calcPow,
"*": calcMultiply,
"/": calcDiv,
@ -906,7 +960,7 @@ func calculate(opdStack *Stack, opt efp.Token) error {
}
rOpd := opdStack.Pop().(efp.Token)
lOpd := opdStack.Pop().(efp.Token)
if err := calcSubtract(rOpd.TValue, lOpd.TValue, opdStack); err != nil {
if err := calcSubtract(rOpd, lOpd, opdStack); err != nil {
return err
}
}
@ -917,7 +971,8 @@ func calculate(opdStack *Stack, opt efp.Token) error {
}
rOpd := opdStack.Pop().(efp.Token)
lOpd := opdStack.Pop().(efp.Token)
if err := fn(rOpd.TValue, lOpd.TValue, opdStack); err != nil {
if err := fn(rOpd, lOpd, opdStack); err != nil {
return err
}
}
@ -6056,6 +6111,44 @@ func (fn *formulaFuncs) IFERROR(argsList *list.List) formulaArg {
return argsList.Back().Value.(formulaArg)
}
// IFNA function tests if an initial supplied value (or expression) evaluates
// to the Excel #N/A error. If so, the function returns a second supplied
// value; Otherwise the function returns the first supplied value. The syntax
// of the function is:
//
// IFNA(value,value_if_na)
//
func (fn *formulaFuncs) IFNA(argsList *list.List) formulaArg {
if argsList.Len() != 2 {
return newErrorFormulaArg(formulaErrorVALUE, "IFNA requires 2 arguments")
}
arg := argsList.Front().Value.(formulaArg)
if arg.Type == ArgError && arg.Value() == formulaErrorNA {
return argsList.Back().Value.(formulaArg)
}
return arg
}
// IFS function tests a number of supplied conditions and returns the result
// corresponding to the first condition that evaluates to TRUE. If none of
// the supplied conditions evaluate to TRUE, the function returns the #N/A
// error.
//
// IFS(logical_test1,value_if_true1,[logical_test2,value_if_true2],...)
//
func (fn *formulaFuncs) IFS(argsList *list.List) formulaArg {
if argsList.Len() < 2 {
return newErrorFormulaArg(formulaErrorVALUE, "IFS requires at least 2 arguments")
}
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
if arg.Value.(formulaArg).ToBool().Number == 1 {
return arg.Next().Value.(formulaArg)
}
arg = arg.Next()
}
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
}
// NOT function returns the opposite to a supplied logical value. The syntax
// of the function is:
//

@ -39,12 +39,24 @@ func TestCalcCellValue(t *testing.T) {
"=1=2": "FALSE",
"=1<2": "TRUE",
"=3<2": "FALSE",
"=1<\"-1\"": "TRUE",
"=\"-1\"<1": "FALSE",
"=\"-1\"<\"-2\"": "TRUE",
"=2<=3": "TRUE",
"=2<=1": "FALSE",
"=1<=\"-1\"": "TRUE",
"=\"-1\"<=1": "FALSE",
"=\"-1\"<=\"-2\"": "TRUE",
"=2>1": "TRUE",
"=2>3": "FALSE",
"=1>\"-1\"": "FALSE",
"=\"-1\">-1": "TRUE",
"=\"-1\">\"-2\"": "FALSE",
"=2>=1": "TRUE",
"=2>=3": "FALSE",
"=1>=\"-1\"": "FALSE",
"=\"-1\">=-1": "TRUE",
"=\"-1\">=\"-2\"": "FALSE",
"=1&2": "12",
"=15%": "0.15",
"=1+20%": "1.2",
@ -922,6 +934,13 @@ func TestCalcCellValue(t *testing.T) {
"=IFERROR(1/2,0)": "0.5",
"=IFERROR(ISERROR(),0)": "0",
"=IFERROR(1/0,0)": "0",
// IFNA
"=IFNA(1,\"not found\")": "1",
"=IFNA(NA(),\"not found\")": "not found",
// IFS
"=IFS(4>1,5/4,4<-1,-5/4,TRUE,0)": "1.25",
"=IFS(-2>1,5/-2,-2<-1,-5/-2,TRUE,0)": "2.5",
"=IFS(0>1,5/0,0<-1,-5/0,TRUE,0)": "0",
// NOT
"=NOT(FALSE())": "TRUE",
"=NOT(\"false\")": "TRUE",
@ -1314,6 +1333,16 @@ func TestCalcCellValue(t *testing.T) {
}
mathCalcError := map[string]string{
"=1/0": "#DIV/0!",
"1^\"text\"": "strconv.ParseFloat: parsing \"text\": invalid syntax",
"\"text\"^1": "strconv.ParseFloat: parsing \"text\": invalid syntax",
"1+\"text\"": "strconv.ParseFloat: parsing \"text\": invalid syntax",
"\"text\"+1": "strconv.ParseFloat: parsing \"text\": invalid syntax",
"1-\"text\"": "strconv.ParseFloat: parsing \"text\": invalid syntax",
"\"text\"-1": "strconv.ParseFloat: parsing \"text\": invalid syntax",
"1*\"text\"": "strconv.ParseFloat: parsing \"text\": invalid syntax",
"\"text\"*1": "strconv.ParseFloat: parsing \"text\": invalid syntax",
"1/\"text\"": "strconv.ParseFloat: parsing \"text\": invalid syntax",
"\"text\"/1": "strconv.ParseFloat: parsing \"text\": invalid syntax",
// Engineering Functions
// BESSELI
"=BESSELI()": "BESSELI requires 2 numeric arguments",
@ -2023,6 +2052,10 @@ func TestCalcCellValue(t *testing.T) {
"=FALSE(A1)": "FALSE takes no arguments",
// IFERROR
"=IFERROR()": "IFERROR requires 2 arguments",
// IFNA
"=IFNA()": "IFNA requires 2 arguments",
// IFS
"=IFS()": "IFS requires at least 2 arguments",
// NOT
"=NOT()": "NOT requires 1 argument",
"=NOT(NOT())": "NOT requires 1 argument",
@ -2598,40 +2631,6 @@ func TestCalcWithDefinedName(t *testing.T) {
}
func TestCalcArithmeticOperations(t *testing.T) {
opdStack := NewStack()
for _, test := range [][]string{{"1", "text", "FALSE"}, {"text", "1", "TRUE"}} {
assert.NoError(t, calcL(test[0], test[1], opdStack))
assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue)
opdStack.Empty()
assert.NoError(t, calcLe(test[0], test[1], opdStack))
assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue)
opdStack.Empty()
}
for _, test := range [][]string{{"1", "text", "TRUE"}, {"text", "1", "FALSE"}} {
assert.NoError(t, calcG(test[0], test[1], opdStack))
assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue)
opdStack.Empty()
assert.NoError(t, calcGe(test[0], test[1], opdStack))
assert.Equal(t, test[2], opdStack.Peek().(efp.Token).TValue)
opdStack.Empty()
}
err := `strconv.ParseFloat: parsing "text": invalid syntax`
assert.EqualError(t, calcPow("1", "text", nil), err)
assert.EqualError(t, calcPow("text", "1", nil), err)
assert.EqualError(t, calcAdd("1", "text", nil), err)
assert.EqualError(t, calcAdd("text", "1", nil), err)
assert.EqualError(t, calcAdd("1", "text", nil), err)
assert.EqualError(t, calcAdd("text", "1", nil), err)
assert.EqualError(t, calcSubtract("1", "text", nil), err)
assert.EqualError(t, calcSubtract("text", "1", nil), err)
assert.EqualError(t, calcMultiply("1", "text", nil), err)
assert.EqualError(t, calcMultiply("text", "1", nil), err)
assert.EqualError(t, calcDiv("1", "text", nil), err)
assert.EqualError(t, calcDiv("text", "1", nil), err)
}
func TestCalcISBLANK(t *testing.T) {
argsList := list.New()
argsList.PushBack(formulaArg{

Loading…
Cancel
Save