forked from p30928647/excelize
Optimization formula calculation performance and update README card badge
This commit is contained in:
parent
81d9362b4f
commit
0f7a0c8f3b
|
@ -3,7 +3,7 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/xuri/excelize/actions/workflows/go.yml"><img src="https://github.com/xuri/excelize/actions/workflows/go.yml/badge.svg" alt="Build Status"></a>
|
<a href="https://github.com/xuri/excelize/actions/workflows/go.yml"><img src="https://github.com/xuri/excelize/actions/workflows/go.yml/badge.svg" alt="Build Status"></a>
|
||||||
<a href="https://codecov.io/gh/qax-os/excelize"><img src="https://codecov.io/gh/qax-os/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
|
<a href="https://codecov.io/gh/qax-os/excelize"><img src="https://codecov.io/gh/qax-os/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
|
||||||
<a href="https://goreportcard.com/report/github.com/xuri/excelize"><img src="https://goreportcard.com/badge/github.com/xuri/excelize" alt="Go Report Card"></a>
|
<a href="https://goreportcard.com/report/github.com/xuri/excelize/v2"><img src="https://goreportcard.com/badge/github.com/xuri/excelize/v2" alt="Go Report Card"></a>
|
||||||
<a href="https://pkg.go.dev/github.com/xuri/excelize/v2"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a>
|
<a href="https://pkg.go.dev/github.com/xuri/excelize/v2"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a>
|
||||||
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
|
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
|
||||||
<a href="https://www.paypal.com/paypalme/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
|
<a href="https://www.paypal.com/paypalme/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/xuri/excelize/actions/workflows/go.yml"><img src="https://github.com/xuri/excelize/actions/workflows/go.yml/badge.svg" alt="Build Status"></a>
|
<a href="https://github.com/xuri/excelize/actions/workflows/go.yml"><img src="https://github.com/xuri/excelize/actions/workflows/go.yml/badge.svg" alt="Build Status"></a>
|
||||||
<a href="https://codecov.io/gh/qax-os/excelize"><img src="https://codecov.io/gh/qax-os/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
|
<a href="https://codecov.io/gh/qax-os/excelize"><img src="https://codecov.io/gh/qax-os/excelize/branch/master/graph/badge.svg" alt="Code Coverage"></a>
|
||||||
<a href="https://goreportcard.com/report/github.com/xuri/excelize"><img src="https://goreportcard.com/badge/github.com/xuri/excelize" alt="Go Report Card"></a>
|
<a href="https://goreportcard.com/report/github.com/xuri/excelize/v2"><img src="https://goreportcard.com/badge/github.com/xuri/excelize/v2" alt="Go Report Card"></a>
|
||||||
<a href="https://pkg.go.dev/github.com/xuri/excelize/v2"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a>
|
<a href="https://pkg.go.dev/github.com/xuri/excelize/v2"><img src="https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white" alt="go.dev"></a>
|
||||||
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
|
<a href="https://opensource.org/licenses/BSD-3-Clause"><img src="https://img.shields.io/badge/license-bsd-orange.svg" alt="Licenses"></a>
|
||||||
<a href="https://www.paypal.com/paypalme/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
|
<a href="https://www.paypal.com/paypalme/xuri"><img src="https://img.shields.io/badge/Donate-PayPal-green.svg" alt="Donate"></a>
|
||||||
|
|
459
calc.go
459
calc.go
|
@ -736,7 +736,7 @@ type formulaFuncs struct {
|
||||||
func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
|
func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
|
||||||
var (
|
var (
|
||||||
formula string
|
formula string
|
||||||
token efp.Token
|
token formulaArg
|
||||||
)
|
)
|
||||||
if formula, err = f.GetCellFormula(sheet, cell); err != nil {
|
if formula, err = f.GetCellFormula(sheet, cell); err != nil {
|
||||||
return
|
return
|
||||||
|
@ -749,7 +749,7 @@ func (f *File) CalcCellValue(sheet, cell string) (result string, err error) {
|
||||||
if token, err = f.evalInfixExp(sheet, cell, tokens); err != nil {
|
if token, err = f.evalInfixExp(sheet, cell, tokens); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
result = token.TValue
|
result = token.Value()
|
||||||
isNum, precision := isNumeric(result)
|
isNum, precision := isNumeric(result)
|
||||||
if isNum && (precision > 15 || precision == 0) {
|
if isNum && (precision > 15 || precision == 0) {
|
||||||
num := roundPrecision(result, -1)
|
num := roundPrecision(result, -1)
|
||||||
|
@ -826,7 +826,7 @@ func newEmptyFormulaArg() formulaArg {
|
||||||
//
|
//
|
||||||
// TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union
|
// TODO: handle subtypes: Nothing, Text, Logical, Error, Concatenation, Intersection, Union
|
||||||
//
|
//
|
||||||
func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token, error) {
|
func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (formulaArg, error) {
|
||||||
var err error
|
var err error
|
||||||
opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack()
|
opdStack, optStack, opfStack, opfdStack, opftStack, argsStack := NewStack(), NewStack(), NewStack(), NewStack(), NewStack(), NewStack()
|
||||||
for i := 0; i < len(tokens); i++ {
|
for i := 0; i < len(tokens); i++ {
|
||||||
|
@ -835,7 +835,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token,
|
||||||
// out of function stack
|
// out of function stack
|
||||||
if opfStack.Len() == 0 {
|
if opfStack.Len() == 0 {
|
||||||
if err = f.parseToken(sheet, token, opdStack, optStack); err != nil {
|
if err = f.parseToken(sheet, token, opdStack, optStack); err != nil {
|
||||||
return efp.Token{}, err
|
return newEmptyFormulaArg(), err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -864,16 +864,12 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token,
|
||||||
// parse reference: must reference at here
|
// parse reference: must reference at here
|
||||||
result, err := f.parseReference(sheet, token.TValue)
|
result, err := f.parseReference(sheet, token.TValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return efp.Token{TValue: formulaErrorNAME}, err
|
return result, err
|
||||||
}
|
}
|
||||||
if result.Type != ArgString {
|
if result.Type != ArgString {
|
||||||
return efp.Token{}, errors.New(formulaErrorVALUE)
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE), errors.New(formulaErrorVALUE)
|
||||||
}
|
}
|
||||||
opfdStack.Push(efp.Token{
|
opfdStack.Push(result)
|
||||||
TType: efp.TokenTypeOperand,
|
|
||||||
TSubType: efp.TokenSubTypeNumber,
|
|
||||||
TValue: result.String,
|
|
||||||
})
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if nextToken.TType == efp.TokenTypeArgument || nextToken.TType == efp.TokenTypeFunction {
|
if nextToken.TType == efp.TokenTypeArgument || nextToken.TType == efp.TokenTypeFunction {
|
||||||
|
@ -884,10 +880,10 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token,
|
||||||
}
|
}
|
||||||
result, err := f.parseReference(sheet, token.TValue)
|
result, err := f.parseReference(sheet, token.TValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return efp.Token{TValue: formulaErrorNAME}, err
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE), err
|
||||||
}
|
}
|
||||||
if result.Type == ArgUnknown {
|
if result.Type == ArgUnknown {
|
||||||
return efp.Token{}, errors.New(formulaErrorVALUE)
|
return newEmptyFormulaArg(), errors.New(formulaErrorVALUE)
|
||||||
}
|
}
|
||||||
argsStack.Peek().(*list.List).PushBack(result)
|
argsStack.Peek().(*list.List).PushBack(result)
|
||||||
continue
|
continue
|
||||||
|
@ -896,18 +892,14 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token,
|
||||||
|
|
||||||
if isEndParenthesesToken(token) && isBeginParenthesesToken(opftStack.Peek().(efp.Token)) {
|
if isEndParenthesesToken(token) && isBeginParenthesesToken(opftStack.Peek().(efp.Token)) {
|
||||||
if arg := argsStack.Peek().(*list.List).Back(); arg != nil {
|
if arg := argsStack.Peek().(*list.List).Back(); arg != nil {
|
||||||
opfdStack.Push(efp.Token{
|
opfdStack.Push(arg.Value.(formulaArg))
|
||||||
TType: efp.TokenTypeOperand,
|
|
||||||
TSubType: efp.TokenSubTypeNumber,
|
|
||||||
TValue: arg.Value.(formulaArg).Value(),
|
|
||||||
})
|
|
||||||
argsStack.Peek().(*list.List).Remove(arg)
|
argsStack.Peek().(*list.List).Remove(arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// check current token is opft
|
// check current token is opft
|
||||||
if err = f.parseToken(sheet, token, opfdStack, opftStack); err != nil {
|
if err = f.parseToken(sheet, token, opfdStack, opftStack); err != nil {
|
||||||
return efp.Token{}, err
|
return newEmptyFormulaArg(), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// current token is arg
|
// current token is arg
|
||||||
|
@ -921,7 +913,7 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token,
|
||||||
opftStack.Pop()
|
opftStack.Pop()
|
||||||
}
|
}
|
||||||
if !opfdStack.Empty() {
|
if !opfdStack.Empty() {
|
||||||
argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue))
|
argsStack.Peek().(*list.List).PushBack(opfdStack.Pop().(formulaArg))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -932,21 +924,21 @@ func (f *File) evalInfixExp(sheet, cell string, tokens []efp.Token) (efp.Token,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = f.evalInfixExpFunc(sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil {
|
if err = f.evalInfixExpFunc(sheet, cell, token, nextToken, opfStack, opdStack, opftStack, opfdStack, argsStack); err != nil {
|
||||||
return efp.Token{}, err
|
return newEmptyFormulaArg(), err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for optStack.Len() != 0 {
|
for optStack.Len() != 0 {
|
||||||
topOpt := optStack.Peek().(efp.Token)
|
topOpt := optStack.Peek().(efp.Token)
|
||||||
if err = calculate(opdStack, topOpt); err != nil {
|
if err = calculate(opdStack, topOpt); err != nil {
|
||||||
return efp.Token{}, err
|
return newEmptyFormulaArg(), err
|
||||||
}
|
}
|
||||||
optStack.Pop()
|
optStack.Pop()
|
||||||
}
|
}
|
||||||
if opdStack.Len() == 0 {
|
if opdStack.Len() == 0 {
|
||||||
return efp.Token{}, ErrInvalidFormula
|
return newEmptyFormulaArg(), ErrInvalidFormula
|
||||||
}
|
}
|
||||||
return opdStack.Peek().(efp.Token), err
|
return opdStack.Peek().(formulaArg), err
|
||||||
}
|
}
|
||||||
|
|
||||||
// evalInfixExpFunc evaluate formula function in the infix expression.
|
// evalInfixExpFunc evaluate formula function in the infix expression.
|
||||||
|
@ -968,11 +960,7 @@ func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token,
|
||||||
if opfStack.Len() > 0 { // still in function stack
|
if opfStack.Len() > 0 { // still in function stack
|
||||||
if nextToken.TType == efp.TokenTypeOperatorInfix || (opftStack.Len() > 1 && opfdStack.Len() > 0) {
|
if nextToken.TType == efp.TokenTypeOperatorInfix || (opftStack.Len() > 1 && opfdStack.Len() > 0) {
|
||||||
// mathematics calculate in formula function
|
// mathematics calculate in formula function
|
||||||
if arg.Type == ArgError {
|
opfdStack.Push(arg)
|
||||||
opfdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeError})
|
|
||||||
} else {
|
|
||||||
opfdStack.Push(efp.Token{TValue: arg.Value(), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
argsStack.Peek().(*list.List).PushBack(arg)
|
argsStack.Peek().(*list.List).PushBack(arg)
|
||||||
}
|
}
|
||||||
|
@ -981,7 +969,7 @@ func (f *File) evalInfixExpFunc(sheet, cell string, token, nextToken efp.Token,
|
||||||
if arg.Type == ArgMatrix && len(arg.Matrix) > 0 && len(arg.Matrix[0]) > 0 {
|
if arg.Type == ArgMatrix && len(arg.Matrix) > 0 && len(arg.Matrix[0]) > 0 {
|
||||||
val = arg.Matrix[0][0].Value()
|
val = arg.Matrix[0][0].Value()
|
||||||
}
|
}
|
||||||
opdStack.Push(efp.Token{TValue: val, TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newStringFormulaArg(val))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1010,179 +998,166 @@ func prepareEvalInfixExp(opfStack, opftStack, opfdStack, argsStack *Stack) {
|
||||||
}
|
}
|
||||||
// push opfd to args
|
// push opfd to args
|
||||||
if argument && opfdStack.Len() > 0 {
|
if argument && opfdStack.Len() > 0 {
|
||||||
argsStack.Peek().(*list.List).PushBack(newStringFormulaArg(opfdStack.Pop().(efp.Token).TValue))
|
argsStack.Peek().(*list.List).PushBack(opfdStack.Pop().(formulaArg))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcPow evaluate exponentiation arithmetic operations.
|
// calcPow evaluate exponentiation arithmetic operations.
|
||||||
func calcPow(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcPow(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
|
lOpdVal := lOpd.ToNumber()
|
||||||
if err != nil {
|
if lOpdVal.Type != ArgNumber {
|
||||||
return err
|
return errors.New(lOpdVal.Value())
|
||||||
}
|
}
|
||||||
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
|
rOpdVal := rOpd.ToNumber()
|
||||||
if err != nil {
|
if rOpdVal.Type != ArgNumber {
|
||||||
return err
|
return errors.New(rOpdVal.Value())
|
||||||
}
|
}
|
||||||
result := math.Pow(lOpdVal, rOpdVal)
|
opdStack.Push(newNumberFormulaArg(math.Pow(lOpdVal.Number, rOpdVal.Number)))
|
||||||
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcEq evaluate equal arithmetic operations.
|
// calcEq evaluate equal arithmetic operations.
|
||||||
func calcEq(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcEq(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd.TValue == lOpd.TValue)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(rOpd.Value() == lOpd.Value()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcNEq evaluate not equal arithmetic operations.
|
// calcNEq evaluate not equal arithmetic operations.
|
||||||
func calcNEq(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcNEq(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(rOpd.TValue != lOpd.TValue)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(rOpd.Value() != lOpd.Value()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcL evaluate less than arithmetic operations.
|
// calcL evaluate less than arithmetic operations.
|
||||||
func calcL(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcL(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber {
|
if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber {
|
||||||
lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64)
|
opdStack.Push(newBoolFormulaArg(lOpd.Number < rOpd.Number))
|
||||||
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 {
|
if rOpd.Type == ArgString && lOpd.Type == ArgString {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) == -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) == -1))
|
||||||
}
|
}
|
||||||
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText {
|
if rOpd.Type == ArgNumber && lOpd.Type == ArgString {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(false))
|
||||||
}
|
}
|
||||||
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber {
|
if rOpd.Type == ArgString && lOpd.Type == ArgNumber {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(true))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcLe evaluate less than or equal arithmetic operations.
|
// calcLe evaluate less than or equal arithmetic operations.
|
||||||
func calcLe(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcLe(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber {
|
if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber {
|
||||||
lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64)
|
opdStack.Push(newBoolFormulaArg(lOpd.Number <= rOpd.Number))
|
||||||
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 {
|
if rOpd.Type == ArgString && lOpd.Type == ArgString {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) != 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) != 1))
|
||||||
}
|
}
|
||||||
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText {
|
if rOpd.Type == ArgNumber && lOpd.Type == ArgString {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(false))
|
||||||
}
|
}
|
||||||
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber {
|
if rOpd.Type == ArgString && lOpd.Type == ArgNumber {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(true))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcG evaluate greater than or equal arithmetic operations.
|
// calcG evaluate greater than or equal arithmetic operations.
|
||||||
func calcG(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcG(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber {
|
if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber {
|
||||||
lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64)
|
opdStack.Push(newBoolFormulaArg(lOpd.Number > rOpd.Number))
|
||||||
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 {
|
if rOpd.Type == ArgString && lOpd.Type == ArgString {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) == 1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) == 1))
|
||||||
}
|
}
|
||||||
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText {
|
if rOpd.Type == ArgNumber && lOpd.Type == ArgString {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(true))
|
||||||
}
|
}
|
||||||
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber {
|
if rOpd.Type == ArgString && lOpd.Type == ArgNumber {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(false))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcGe evaluate greater than or equal arithmetic operations.
|
// calcGe evaluate greater than or equal arithmetic operations.
|
||||||
func calcGe(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcGe(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeNumber {
|
if rOpd.Type == ArgNumber && lOpd.Type == ArgNumber {
|
||||||
lOpdVal, _ := strconv.ParseFloat(lOpd.TValue, 64)
|
opdStack.Push(newBoolFormulaArg(lOpd.Number >= rOpd.Number))
|
||||||
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 {
|
if rOpd.Type == ArgString && lOpd.Type == ArgString {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(strings.Compare(lOpd.TValue, rOpd.TValue) != -1)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(strings.Compare(lOpd.Value(), rOpd.Value()) != -1))
|
||||||
}
|
}
|
||||||
if rOpd.TSubType == efp.TokenSubTypeNumber && lOpd.TSubType == efp.TokenSubTypeText {
|
if rOpd.Type == ArgNumber && lOpd.Type == ArgString {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(true)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(true))
|
||||||
}
|
}
|
||||||
if rOpd.TSubType == efp.TokenSubTypeText && lOpd.TSubType == efp.TokenSubTypeNumber {
|
if rOpd.Type == ArgString && lOpd.Type == ArgNumber {
|
||||||
opdStack.Push(efp.Token{TValue: strings.ToUpper(strconv.FormatBool(false)), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newBoolFormulaArg(false))
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcSplice evaluate splice '&' operations.
|
// calcSplice evaluate splice '&' operations.
|
||||||
func calcSplice(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcSplice(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
opdStack.Push(efp.Token{TValue: lOpd.TValue + rOpd.TValue, TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newStringFormulaArg(lOpd.Value() + rOpd.Value()))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcAdd evaluate addition arithmetic operations.
|
// calcAdd evaluate addition arithmetic operations.
|
||||||
func calcAdd(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcAdd(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
|
lOpdVal := lOpd.ToNumber()
|
||||||
if err != nil {
|
if lOpdVal.Type != ArgNumber {
|
||||||
return err
|
return errors.New(lOpdVal.Value())
|
||||||
}
|
}
|
||||||
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
|
rOpdVal := rOpd.ToNumber()
|
||||||
if err != nil {
|
if rOpdVal.Type != ArgNumber {
|
||||||
return err
|
return errors.New(rOpdVal.Value())
|
||||||
}
|
}
|
||||||
result := lOpdVal + rOpdVal
|
opdStack.Push(newNumberFormulaArg(lOpdVal.Number + rOpdVal.Number))
|
||||||
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcSubtract evaluate subtraction arithmetic operations.
|
// calcSubtract evaluate subtraction arithmetic operations.
|
||||||
func calcSubtract(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcSubtract(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
|
lOpdVal := lOpd.ToNumber()
|
||||||
if err != nil {
|
if lOpdVal.Type != ArgNumber {
|
||||||
return err
|
return errors.New(lOpdVal.Value())
|
||||||
}
|
}
|
||||||
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
|
rOpdVal := rOpd.ToNumber()
|
||||||
if err != nil {
|
if rOpdVal.Type != ArgNumber {
|
||||||
return err
|
return errors.New(rOpdVal.Value())
|
||||||
}
|
}
|
||||||
result := lOpdVal - rOpdVal
|
opdStack.Push(newNumberFormulaArg(lOpdVal.Number - rOpdVal.Number))
|
||||||
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcMultiply evaluate multiplication arithmetic operations.
|
// calcMultiply evaluate multiplication arithmetic operations.
|
||||||
func calcMultiply(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcMultiply(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
|
lOpdVal := lOpd.ToNumber()
|
||||||
if err != nil {
|
if lOpdVal.Type != ArgNumber {
|
||||||
return err
|
return errors.New(lOpdVal.Value())
|
||||||
}
|
}
|
||||||
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
|
rOpdVal := rOpd.ToNumber()
|
||||||
if err != nil {
|
if rOpdVal.Type != ArgNumber {
|
||||||
return err
|
return errors.New(rOpdVal.Value())
|
||||||
}
|
}
|
||||||
result := lOpdVal * rOpdVal
|
opdStack.Push(newNumberFormulaArg(lOpdVal.Number * rOpdVal.Number))
|
||||||
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcDiv evaluate division arithmetic operations.
|
// calcDiv evaluate division arithmetic operations.
|
||||||
func calcDiv(rOpd, lOpd efp.Token, opdStack *Stack) error {
|
func calcDiv(rOpd, lOpd formulaArg, opdStack *Stack) error {
|
||||||
lOpdVal, err := strconv.ParseFloat(lOpd.TValue, 64)
|
lOpdVal := lOpd.ToNumber()
|
||||||
if err != nil {
|
if lOpdVal.Type != ArgNumber {
|
||||||
return err
|
return errors.New(lOpdVal.Value())
|
||||||
}
|
}
|
||||||
rOpdVal, err := strconv.ParseFloat(rOpd.TValue, 64)
|
rOpdVal := rOpd.ToNumber()
|
||||||
if err != nil {
|
if rOpdVal.Type != ArgNumber {
|
||||||
return err
|
return errors.New(rOpdVal.Value())
|
||||||
}
|
}
|
||||||
result := lOpdVal / rOpdVal
|
if rOpdVal.Number == 0 {
|
||||||
if rOpdVal == 0 {
|
|
||||||
return errors.New(formulaErrorDIV)
|
return errors.New(formulaErrorDIV)
|
||||||
}
|
}
|
||||||
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
opdStack.Push(newNumberFormulaArg(lOpdVal.Number / rOpdVal.Number))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1192,25 +1167,20 @@ func calculate(opdStack *Stack, opt efp.Token) error {
|
||||||
if opdStack.Len() < 1 {
|
if opdStack.Len() < 1 {
|
||||||
return ErrInvalidFormula
|
return ErrInvalidFormula
|
||||||
}
|
}
|
||||||
opd := opdStack.Pop().(efp.Token)
|
opd := opdStack.Pop().(formulaArg)
|
||||||
opdVal, err := strconv.ParseFloat(opd.TValue, 64)
|
opdStack.Push(newNumberFormulaArg(0 - opd.Number))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
result := 0 - opdVal
|
|
||||||
opdStack.Push(efp.Token{TValue: fmt.Sprintf("%g", result), TType: efp.TokenTypeOperand, TSubType: efp.TokenSubTypeNumber})
|
|
||||||
}
|
}
|
||||||
if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorInfix {
|
if opt.TValue == "-" && opt.TType == efp.TokenTypeOperatorInfix {
|
||||||
if opdStack.Len() < 2 {
|
if opdStack.Len() < 2 {
|
||||||
return ErrInvalidFormula
|
return ErrInvalidFormula
|
||||||
}
|
}
|
||||||
rOpd := opdStack.Pop().(efp.Token)
|
rOpd := opdStack.Pop().(formulaArg)
|
||||||
lOpd := opdStack.Pop().(efp.Token)
|
lOpd := opdStack.Pop().(formulaArg)
|
||||||
if err := calcSubtract(rOpd, lOpd, opdStack); err != nil {
|
if err := calcSubtract(rOpd, lOpd, opdStack); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tokenCalcFunc := map[string]func(rOpd, lOpd efp.Token, opdStack *Stack) error{
|
tokenCalcFunc := map[string]func(rOpd, lOpd formulaArg, opdStack *Stack) error{
|
||||||
"^": calcPow,
|
"^": calcPow,
|
||||||
"*": calcMultiply,
|
"*": calcMultiply,
|
||||||
"/": calcDiv,
|
"/": calcDiv,
|
||||||
|
@ -1228,13 +1198,13 @@ func calculate(opdStack *Stack, opt efp.Token) error {
|
||||||
if opdStack.Len() < 2 {
|
if opdStack.Len() < 2 {
|
||||||
return ErrInvalidFormula
|
return ErrInvalidFormula
|
||||||
}
|
}
|
||||||
rOpd := opdStack.Pop().(efp.Token)
|
rOpd := opdStack.Pop().(formulaArg)
|
||||||
lOpd := opdStack.Pop().(efp.Token)
|
lOpd := opdStack.Pop().(formulaArg)
|
||||||
if rOpd.TSubType == efp.TokenSubTypeError {
|
if rOpd.Type == ArgError {
|
||||||
return errors.New(rOpd.TValue)
|
return errors.New(rOpd.Value())
|
||||||
}
|
}
|
||||||
if lOpd.TSubType == efp.TokenSubTypeError {
|
if lOpd.Type == ArgError {
|
||||||
return errors.New(lOpd.TValue)
|
return errors.New(lOpd.Value())
|
||||||
}
|
}
|
||||||
if err := fn(rOpd, lOpd, opdStack); err != nil {
|
if err := fn(rOpd, lOpd, opdStack); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -1322,7 +1292,7 @@ func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Sta
|
||||||
}
|
}
|
||||||
token.TValue = result.String
|
token.TValue = result.String
|
||||||
token.TType = efp.TokenTypeOperand
|
token.TType = efp.TokenTypeOperand
|
||||||
token.TSubType = efp.TokenSubTypeNumber
|
token.TSubType = efp.TokenSubTypeText
|
||||||
}
|
}
|
||||||
if isOperatorPrefixToken(token) {
|
if isOperatorPrefixToken(token) {
|
||||||
if err := f.parseOperatorPrefixToken(optStack, opdStack, token); err != nil {
|
if err := f.parseOperatorPrefixToken(optStack, opdStack, token); err != nil {
|
||||||
|
@ -1343,15 +1313,17 @@ func (f *File) parseToken(sheet string, token efp.Token, opdStack, optStack *Sta
|
||||||
optStack.Pop()
|
optStack.Pop()
|
||||||
}
|
}
|
||||||
if token.TType == efp.TokenTypeOperatorPostfix && !opdStack.Empty() {
|
if token.TType == efp.TokenTypeOperatorPostfix && !opdStack.Empty() {
|
||||||
topOpd := opdStack.Pop().(efp.Token)
|
topOpd := opdStack.Pop().(formulaArg)
|
||||||
opd, err := strconv.ParseFloat(topOpd.TValue, 64)
|
opdStack.Push(newNumberFormulaArg(topOpd.Number / 100))
|
||||||
topOpd.TValue = strconv.FormatFloat(opd/100, 'f', -1, 64)
|
|
||||||
opdStack.Push(topOpd)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
// opd
|
// opd
|
||||||
if isOperand(token) {
|
if isOperand(token) {
|
||||||
opdStack.Push(token)
|
if token.TSubType == efp.TokenSubTypeNumber {
|
||||||
|
num, _ := strconv.ParseFloat(token.TValue, 64)
|
||||||
|
opdStack.Push(newNumberFormulaArg(num))
|
||||||
|
} else {
|
||||||
|
opdStack.Push(newStringFormulaArg(token.TValue))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -3723,7 +3695,7 @@ func (fn *formulaFuncs) BASE(argsList *list.List) formulaArg {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "radix must be an integer >= 2 and <= 36")
|
return newErrorFormulaArg(formulaErrorVALUE, "radix must be an integer >= 2 and <= 36")
|
||||||
}
|
}
|
||||||
if argsList.Len() > 2 {
|
if argsList.Len() > 2 {
|
||||||
if minLength, err = strconv.Atoi(argsList.Back().Value.(formulaArg).String); err != nil {
|
if minLength, err = strconv.Atoi(argsList.Back().Value.(formulaArg).Value()); err != nil {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4058,17 +4030,16 @@ func (fn *formulaFuncs) DECIMAL(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() != 2 {
|
if argsList.Len() != 2 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "DECIMAL requires 2 numeric arguments")
|
return newErrorFormulaArg(formulaErrorVALUE, "DECIMAL requires 2 numeric arguments")
|
||||||
}
|
}
|
||||||
text := argsList.Front().Value.(formulaArg).String
|
text := argsList.Front().Value.(formulaArg).Value()
|
||||||
var radix int
|
|
||||||
var err error
|
var err error
|
||||||
radix, err = strconv.Atoi(argsList.Back().Value.(formulaArg).String)
|
radix := argsList.Back().Value.(formulaArg).ToNumber()
|
||||||
if err != nil {
|
if radix.Type != ArgNumber {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
return radix
|
||||||
}
|
}
|
||||||
if len(text) > 2 && (strings.HasPrefix(text, "0x") || strings.HasPrefix(text, "0X")) {
|
if len(text) > 2 && (strings.HasPrefix(text, "0x") || strings.HasPrefix(text, "0X")) {
|
||||||
text = text[2:]
|
text = text[2:]
|
||||||
}
|
}
|
||||||
val, err := strconv.ParseInt(text, radix, 64)
|
val, err := strconv.ParseInt(text, int(radix.Number), 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
||||||
}
|
}
|
||||||
|
@ -4948,8 +4919,6 @@ func (fn *formulaFuncs) PRODUCT(argsList *list.List) formulaArg {
|
||||||
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
|
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
|
||||||
token := arg.Value.(formulaArg)
|
token := arg.Value.(formulaArg)
|
||||||
switch token.Type {
|
switch token.Type {
|
||||||
case ArgUnknown:
|
|
||||||
continue
|
|
||||||
case ArgString:
|
case ArgString:
|
||||||
if token.String == "" {
|
if token.String == "" {
|
||||||
continue
|
continue
|
||||||
|
@ -4963,13 +4932,13 @@ func (fn *formulaFuncs) PRODUCT(argsList *list.List) formulaArg {
|
||||||
case ArgMatrix:
|
case ArgMatrix:
|
||||||
for _, row := range token.Matrix {
|
for _, row := range token.Matrix {
|
||||||
for _, value := range row {
|
for _, value := range row {
|
||||||
if value.String == "" {
|
if value.Value() == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if val, err = strconv.ParseFloat(value.String, 64); err != nil {
|
if val, err = strconv.ParseFloat(value.String, 64); err != nil {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
||||||
}
|
}
|
||||||
product = product * val
|
product *= val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5684,10 +5653,9 @@ func (fn *formulaFuncs) SUMIF(argsList *list.List) formulaArg {
|
||||||
ok, _ = formulaCriteriaEval(fromVal, criteria)
|
ok, _ = formulaCriteriaEval(fromVal, criteria)
|
||||||
if ok {
|
if ok {
|
||||||
if argsList.Len() == 3 {
|
if argsList.Len() == 3 {
|
||||||
if len(sumRange) <= rowIdx || len(sumRange[rowIdx]) <= colIdx {
|
if len(sumRange) > rowIdx && len(sumRange[rowIdx]) > colIdx {
|
||||||
continue
|
fromVal = sumRange[rowIdx][colIdx].String
|
||||||
}
|
}
|
||||||
fromVal = sumRange[rowIdx][colIdx].String
|
|
||||||
}
|
}
|
||||||
if val, err = strconv.ParseFloat(fromVal, 64); err != nil {
|
if val, err = strconv.ParseFloat(fromVal, 64); err != nil {
|
||||||
continue
|
continue
|
||||||
|
@ -5718,6 +5686,9 @@ func (fn *formulaFuncs) SUMIFS(argsList *list.List) formulaArg {
|
||||||
args = append(args, arg.Value.(formulaArg))
|
args = append(args, arg.Value.(formulaArg))
|
||||||
}
|
}
|
||||||
for _, ref := range formulaIfsMatch(args) {
|
for _, ref := range formulaIfsMatch(args) {
|
||||||
|
if ref.Row >= len(sumRange) || ref.Col >= len(sumRange[ref.Row]) {
|
||||||
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||||
|
}
|
||||||
if num := sumRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber {
|
if num := sumRange[ref.Row][ref.Col].ToNumber(); num.Type == ArgNumber {
|
||||||
sum += num.Number
|
sum += num.Number
|
||||||
}
|
}
|
||||||
|
@ -5812,14 +5783,14 @@ func (fn *formulaFuncs) SUMSQ(argsList *list.List) formulaArg {
|
||||||
}
|
}
|
||||||
sq += val * val
|
sq += val * val
|
||||||
case ArgNumber:
|
case ArgNumber:
|
||||||
sq += token.Number
|
sq += token.Number * token.Number
|
||||||
case ArgMatrix:
|
case ArgMatrix:
|
||||||
for _, row := range token.Matrix {
|
for _, row := range token.Matrix {
|
||||||
for _, value := range row {
|
for _, value := range row {
|
||||||
if value.String == "" {
|
if value.Value() == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if val, err = strconv.ParseFloat(value.String, 64); err != nil {
|
if val, err = strconv.ParseFloat(value.Value(), 64); err != nil {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
||||||
}
|
}
|
||||||
sq += val * val
|
sq += val * val
|
||||||
|
@ -6028,7 +5999,7 @@ func (fn *formulaFuncs) AVERAGEIF(argsList *list.List) formulaArg {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "AVERAGEIF requires at least 2 arguments")
|
return newErrorFormulaArg(formulaErrorVALUE, "AVERAGEIF requires at least 2 arguments")
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).String)
|
criteria = formulaCriteriaParser(argsList.Front().Next().Value.(formulaArg).Value())
|
||||||
rangeMtx = argsList.Front().Value.(formulaArg).Matrix
|
rangeMtx = argsList.Front().Value.(formulaArg).Matrix
|
||||||
cellRange [][]formulaArg
|
cellRange [][]formulaArg
|
||||||
args []formulaArg
|
args []formulaArg
|
||||||
|
@ -6041,17 +6012,16 @@ func (fn *formulaFuncs) AVERAGEIF(argsList *list.List) formulaArg {
|
||||||
}
|
}
|
||||||
for rowIdx, row := range rangeMtx {
|
for rowIdx, row := range rangeMtx {
|
||||||
for colIdx, col := range row {
|
for colIdx, col := range row {
|
||||||
fromVal := col.String
|
fromVal := col.Value()
|
||||||
if col.String == "" {
|
if col.Value() == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
ok, _ = formulaCriteriaEval(fromVal, criteria)
|
ok, _ = formulaCriteriaEval(fromVal, criteria)
|
||||||
if ok {
|
if ok {
|
||||||
if argsList.Len() == 3 {
|
if argsList.Len() == 3 {
|
||||||
if len(cellRange) <= rowIdx || len(cellRange[rowIdx]) <= colIdx {
|
if len(cellRange) > rowIdx && len(cellRange[rowIdx]) > colIdx {
|
||||||
continue
|
fromVal = cellRange[rowIdx][colIdx].Value()
|
||||||
}
|
}
|
||||||
fromVal = cellRange[rowIdx][colIdx].String
|
|
||||||
}
|
}
|
||||||
if val, err = strconv.ParseFloat(fromVal, 64); err != nil {
|
if val, err = strconv.ParseFloat(fromVal, 64); err != nil {
|
||||||
continue
|
continue
|
||||||
|
@ -7686,12 +7656,10 @@ func (fn *formulaFuncs) COUNT(argsList *list.List) formulaArg {
|
||||||
for token := argsList.Front(); token != nil; token = token.Next() {
|
for token := argsList.Front(); token != nil; token = token.Next() {
|
||||||
arg := token.Value.(formulaArg)
|
arg := token.Value.(formulaArg)
|
||||||
switch arg.Type {
|
switch arg.Type {
|
||||||
case ArgString:
|
case ArgString, ArgNumber:
|
||||||
if arg.ToNumber().Type != ArgError {
|
if arg.ToNumber().Type != ArgError {
|
||||||
count++
|
count++
|
||||||
}
|
}
|
||||||
case ArgNumber:
|
|
||||||
count++
|
|
||||||
case ArgMatrix:
|
case ArgMatrix:
|
||||||
for _, row := range arg.Matrix {
|
for _, row := range arg.Matrix {
|
||||||
for _, value := range row {
|
for _, value := range row {
|
||||||
|
@ -7928,23 +7896,14 @@ func (fn *formulaFuncs) GAMMA(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() != 1 {
|
if argsList.Len() != 1 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument")
|
return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument")
|
||||||
}
|
}
|
||||||
token := argsList.Front().Value.(formulaArg)
|
number := argsList.Front().Value.(formulaArg).ToNumber()
|
||||||
switch token.Type {
|
if number.Type != ArgNumber {
|
||||||
case ArgString:
|
return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument")
|
||||||
arg := token.ToNumber()
|
|
||||||
if arg.Type == ArgNumber {
|
|
||||||
if arg.Number <= 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
|
|
||||||
}
|
|
||||||
return newNumberFormulaArg(math.Gamma(arg.Number))
|
|
||||||
}
|
|
||||||
case ArgNumber:
|
|
||||||
if token.Number <= 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
|
|
||||||
}
|
|
||||||
return newNumberFormulaArg(math.Gamma(token.Number))
|
|
||||||
}
|
}
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "GAMMA requires 1 numeric argument")
|
if number.Number <= 0 {
|
||||||
|
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
|
||||||
|
}
|
||||||
|
return newNumberFormulaArg(math.Gamma(number.Number))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GAMMAdotDIST function returns the Gamma Distribution, which is frequently
|
// GAMMAdotDIST function returns the Gamma Distribution, which is frequently
|
||||||
|
@ -8073,23 +8032,14 @@ func (fn *formulaFuncs) GAMMALN(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() != 1 {
|
if argsList.Len() != 1 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument")
|
return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument")
|
||||||
}
|
}
|
||||||
token := argsList.Front().Value.(formulaArg)
|
x := argsList.Front().Value.(formulaArg).ToNumber()
|
||||||
switch token.Type {
|
if x.Type != ArgNumber {
|
||||||
case ArgString:
|
return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument")
|
||||||
arg := token.ToNumber()
|
|
||||||
if arg.Type == ArgNumber {
|
|
||||||
if arg.Number <= 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
|
|
||||||
}
|
|
||||||
return newNumberFormulaArg(math.Log(math.Gamma(arg.Number)))
|
|
||||||
}
|
|
||||||
case ArgNumber:
|
|
||||||
if token.Number <= 0 {
|
|
||||||
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
|
|
||||||
}
|
|
||||||
return newNumberFormulaArg(math.Log(math.Gamma(token.Number)))
|
|
||||||
}
|
}
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "GAMMALN requires 1 numeric argument")
|
if x.Number <= 0 {
|
||||||
|
return newErrorFormulaArg(formulaErrorNA, formulaErrorNA)
|
||||||
|
}
|
||||||
|
return newNumberFormulaArg(math.Log(math.Gamma(x.Number)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GAMMALNdotPRECISE function returns the natural logarithm of the Gamma
|
// GAMMALNdotPRECISE function returns the natural logarithm of the Gamma
|
||||||
|
@ -11709,11 +11659,7 @@ func (fn *formulaFuncs) AND(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() > 30 {
|
if argsList.Len() > 30 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "AND accepts at most 30 arguments")
|
return newErrorFormulaArg(formulaErrorVALUE, "AND accepts at most 30 arguments")
|
||||||
}
|
}
|
||||||
var (
|
and := true
|
||||||
and = true
|
|
||||||
val float64
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
|
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
|
||||||
token := arg.Value.(formulaArg)
|
token := arg.Value.(formulaArg)
|
||||||
switch token.Type {
|
switch token.Type {
|
||||||
|
@ -11726,10 +11672,9 @@ func (fn *formulaFuncs) AND(argsList *list.List) formulaArg {
|
||||||
if token.String == "FALSE" {
|
if token.String == "FALSE" {
|
||||||
return newStringFormulaArg(token.String)
|
return newStringFormulaArg(token.String)
|
||||||
}
|
}
|
||||||
if val, err = strconv.ParseFloat(token.String, 64); err != nil {
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
case ArgNumber:
|
||||||
}
|
and = and && token.Number != 0
|
||||||
and = and && (val != 0)
|
|
||||||
case ArgMatrix:
|
case ArgMatrix:
|
||||||
// TODO
|
// TODO
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||||
|
@ -11845,11 +11790,7 @@ func (fn *formulaFuncs) OR(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() > 30 {
|
if argsList.Len() > 30 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "OR accepts at most 30 arguments")
|
return newErrorFormulaArg(formulaErrorVALUE, "OR accepts at most 30 arguments")
|
||||||
}
|
}
|
||||||
var (
|
var or bool
|
||||||
or bool
|
|
||||||
val float64
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
|
for arg := argsList.Front(); arg != nil; arg = arg.Next() {
|
||||||
token := arg.Value.(formulaArg)
|
token := arg.Value.(formulaArg)
|
||||||
switch token.Type {
|
switch token.Type {
|
||||||
|
@ -11863,10 +11804,9 @@ func (fn *formulaFuncs) OR(argsList *list.List) formulaArg {
|
||||||
or = true
|
or = true
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if val, err = strconv.ParseFloat(token.String, 64); err != nil {
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
case ArgNumber:
|
||||||
}
|
or = token.Number != 0
|
||||||
or = val != 0
|
|
||||||
case ArgMatrix:
|
case ArgMatrix:
|
||||||
// TODO
|
// TODO
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE)
|
||||||
|
@ -11931,20 +11871,6 @@ func calcXor(argsList *list.List) formulaArg {
|
||||||
switch token.Type {
|
switch token.Type {
|
||||||
case ArgError:
|
case ArgError:
|
||||||
return token
|
return token
|
||||||
case ArgString:
|
|
||||||
if b := token.ToBool(); b.Type == ArgNumber {
|
|
||||||
ok = true
|
|
||||||
if b.Number == 1 {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if num := token.ToNumber(); num.Type == ArgNumber {
|
|
||||||
ok = true
|
|
||||||
if num.Number != 0 {
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case ArgNumber:
|
case ArgNumber:
|
||||||
ok = true
|
ok = true
|
||||||
if token.Number != 0 {
|
if token.Number != 0 {
|
||||||
|
@ -12907,7 +12833,7 @@ func (fn *formulaFuncs) CLEAN(argsList *list.List) formulaArg {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "CLEAN requires 1 argument")
|
return newErrorFormulaArg(formulaErrorVALUE, "CLEAN requires 1 argument")
|
||||||
}
|
}
|
||||||
b := bytes.Buffer{}
|
b := bytes.Buffer{}
|
||||||
for _, c := range argsList.Front().Value.(formulaArg).String {
|
for _, c := range argsList.Front().Value.(formulaArg).Value() {
|
||||||
if c > 31 {
|
if c > 31 {
|
||||||
b.WriteRune(c)
|
b.WriteRune(c)
|
||||||
}
|
}
|
||||||
|
@ -13477,7 +13403,7 @@ func (fn *formulaFuncs) TRIM(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() != 1 {
|
if argsList.Len() != 1 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "TRIM requires 1 argument")
|
return newErrorFormulaArg(formulaErrorVALUE, "TRIM requires 1 argument")
|
||||||
}
|
}
|
||||||
return newStringFormulaArg(strings.TrimSpace(argsList.Front().Value.(formulaArg).String))
|
return newStringFormulaArg(strings.TrimSpace(argsList.Front().Value.(formulaArg).Value()))
|
||||||
}
|
}
|
||||||
|
|
||||||
// UNICHAR returns the Unicode character that is referenced by the given
|
// UNICHAR returns the Unicode character that is referenced by the given
|
||||||
|
@ -13584,27 +13510,30 @@ func (fn *formulaFuncs) IF(argsList *list.List) formulaArg {
|
||||||
if cond, err = strconv.ParseBool(token.String); err != nil {
|
if cond, err = strconv.ParseBool(token.String); err != nil {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
return newErrorFormulaArg(formulaErrorVALUE, err.Error())
|
||||||
}
|
}
|
||||||
if argsList.Len() == 1 {
|
case ArgNumber:
|
||||||
return newBoolFormulaArg(cond)
|
cond = token.Number == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if argsList.Len() == 1 {
|
||||||
|
return newBoolFormulaArg(cond)
|
||||||
|
}
|
||||||
|
if cond {
|
||||||
|
value := argsList.Front().Next().Value.(formulaArg)
|
||||||
|
switch value.Type {
|
||||||
|
case ArgNumber:
|
||||||
|
result = value.ToNumber()
|
||||||
|
default:
|
||||||
|
result = newStringFormulaArg(value.String)
|
||||||
}
|
}
|
||||||
if cond {
|
return result
|
||||||
value := argsList.Front().Next().Value.(formulaArg)
|
}
|
||||||
switch value.Type {
|
if argsList.Len() == 3 {
|
||||||
case ArgNumber:
|
value := argsList.Back().Value.(formulaArg)
|
||||||
result = value.ToNumber()
|
switch value.Type {
|
||||||
default:
|
case ArgNumber:
|
||||||
result = newStringFormulaArg(value.String)
|
result = value.ToNumber()
|
||||||
}
|
default:
|
||||||
return result
|
result = newStringFormulaArg(value.String)
|
||||||
}
|
|
||||||
if argsList.Len() == 3 {
|
|
||||||
value := argsList.Back().Value.(formulaArg)
|
|
||||||
switch value.Type {
|
|
||||||
case ArgNumber:
|
|
||||||
result = value.ToNumber()
|
|
||||||
default:
|
|
||||||
result = newStringFormulaArg(value.String)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
|
@ -13676,7 +13605,7 @@ func (fn *formulaFuncs) CHOOSE(argsList *list.List) formulaArg {
|
||||||
if argsList.Len() < 2 {
|
if argsList.Len() < 2 {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires 2 arguments")
|
return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires 2 arguments")
|
||||||
}
|
}
|
||||||
idx, err := strconv.Atoi(argsList.Front().Value.(formulaArg).String)
|
idx, err := strconv.Atoi(argsList.Front().Value.(formulaArg).Value())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires first argument of type number")
|
return newErrorFormulaArg(formulaErrorVALUE, "CHOOSE requires first argument of type number")
|
||||||
}
|
}
|
||||||
|
@ -14075,7 +14004,7 @@ func (fn *formulaFuncs) MATCH(argsList *list.List) formulaArg {
|
||||||
default:
|
default:
|
||||||
return newErrorFormulaArg(formulaErrorNA, lookupArrayErr)
|
return newErrorFormulaArg(formulaErrorNA, lookupArrayErr)
|
||||||
}
|
}
|
||||||
return calcMatch(matchType, formulaCriteriaParser(argsList.Front().Value.(formulaArg).String), lookupArray)
|
return calcMatch(matchType, formulaCriteriaParser(argsList.Front().Value.(formulaArg).Value()), lookupArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TRANSPOSE function 'transposes' an array of cells (i.e. the function copies
|
// TRANSPOSE function 'transposes' an array of cells (i.e. the function copies
|
||||||
|
@ -14237,7 +14166,7 @@ func checkLookupArgs(argsList *list.List) (arrayForm bool, lookupValue, lookupVe
|
||||||
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at most 3 arguments")
|
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires at most 3 arguments")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lookupValue = argsList.Front().Value.(formulaArg)
|
lookupValue = newStringFormulaArg(argsList.Front().Value.(formulaArg).Value())
|
||||||
lookupVector = argsList.Front().Next().Value.(formulaArg)
|
lookupVector = argsList.Front().Next().Value.(formulaArg)
|
||||||
if lookupVector.Type != ArgMatrix && lookupVector.Type != ArgList {
|
if lookupVector.Type != ArgMatrix && lookupVector.Type != ArgList {
|
||||||
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires second argument of table array")
|
errArg = newErrorFormulaArg(formulaErrorVALUE, "LOOKUP requires second argument of table array")
|
||||||
|
|
125
calc_test.go
125
calc_test.go
|
@ -8,7 +8,6 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/xuri/efp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func prepareCalcData(cellData [][]interface{}) *File {
|
func prepareCalcData(cellData [][]interface{}) *File {
|
||||||
|
@ -545,6 +544,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
// GCD
|
// GCD
|
||||||
"=GCD(0)": "0",
|
"=GCD(0)": "0",
|
||||||
"=GCD(1,0)": "1",
|
"=GCD(1,0)": "1",
|
||||||
|
"=GCD(\"0\",1)": "1",
|
||||||
"=GCD(1,5)": "1",
|
"=GCD(1,5)": "1",
|
||||||
"=GCD(15,10,25)": "5",
|
"=GCD(15,10,25)": "5",
|
||||||
"=GCD(0,8,12)": "4",
|
"=GCD(0,8,12)": "4",
|
||||||
|
@ -655,6 +655,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=PRODUCT(3,6)": "18",
|
"=PRODUCT(3,6)": "18",
|
||||||
`=PRODUCT("",3,6)`: "18",
|
`=PRODUCT("",3,6)`: "18",
|
||||||
`=PRODUCT(PRODUCT(1),3,6)`: "18",
|
`=PRODUCT(PRODUCT(1),3,6)`: "18",
|
||||||
|
"=PRODUCT(C1:C2)": "1",
|
||||||
// QUOTIENT
|
// QUOTIENT
|
||||||
"=QUOTIENT(5,2)": "2",
|
"=QUOTIENT(5,2)": "2",
|
||||||
"=QUOTIENT(4.5,3.1)": "1",
|
"=QUOTIENT(4.5,3.1)": "1",
|
||||||
|
@ -798,7 +799,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=SUMSQ(A1,B1,A2,B2,6)": "82",
|
"=SUMSQ(A1,B1,A2,B2,6)": "82",
|
||||||
`=SUMSQ("",A1,B1,A2,B2,6)`: "82",
|
`=SUMSQ("",A1,B1,A2,B2,6)`: "82",
|
||||||
`=SUMSQ(1,SUMSQ(1))`: "2",
|
`=SUMSQ(1,SUMSQ(1))`: "2",
|
||||||
"=SUMSQ(MUNIT(3))": "0",
|
"=SUMSQ(MUNIT(3))": "3",
|
||||||
// SUMX2MY2
|
// SUMX2MY2
|
||||||
"=SUMX2MY2(A1:A4,B1:B4)": "-36",
|
"=SUMX2MY2(A1:A4,B1:B4)": "-36",
|
||||||
// SUMX2PY2
|
// SUMX2PY2
|
||||||
|
@ -927,8 +928,8 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
// CORREL
|
// CORREL
|
||||||
"=CORREL(A1:A5,B1:B5)": "1",
|
"=CORREL(A1:A5,B1:B5)": "1",
|
||||||
// COUNT
|
// COUNT
|
||||||
"=COUNT()": "0",
|
"=COUNT()": "0",
|
||||||
"=COUNT(E1:F2,\"text\",1,INT(2))": "3",
|
"=COUNT(E1:F2,\"text\",1,INT(2),\"0\")": "4",
|
||||||
// COUNTA
|
// COUNTA
|
||||||
"=COUNTA()": "0",
|
"=COUNTA()": "0",
|
||||||
"=COUNTA(A1:A5,B2:B5,\"text\",1,INT(2))": "8",
|
"=COUNTA(A1:A5,B2:B5,\"text\",1,INT(2))": "8",
|
||||||
|
@ -959,19 +960,22 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=DEVSQ(1,3,5,2,9,7)": "47.5",
|
"=DEVSQ(1,3,5,2,9,7)": "47.5",
|
||||||
"=DEVSQ(A1:D2)": "10",
|
"=DEVSQ(A1:D2)": "10",
|
||||||
// FISHER
|
// FISHER
|
||||||
"=FISHER(-0.9)": "-1.47221948958322",
|
"=FISHER(-0.9)": "-1.47221948958322",
|
||||||
"=FISHER(-0.25)": "-0.255412811882995",
|
"=FISHER(-0.25)": "-0.255412811882995",
|
||||||
"=FISHER(0.8)": "1.09861228866811",
|
"=FISHER(0.8)": "1.09861228866811",
|
||||||
"=FISHER(INT(0))": "0",
|
"=FISHER(\"0.8\")": "1.09861228866811",
|
||||||
|
"=FISHER(INT(0))": "0",
|
||||||
// FISHERINV
|
// FISHERINV
|
||||||
"=FISHERINV(-0.2)": "-0.197375320224904",
|
"=FISHERINV(-0.2)": "-0.197375320224904",
|
||||||
"=FISHERINV(INT(0))": "0",
|
"=FISHERINV(INT(0))": "0",
|
||||||
|
"=FISHERINV(\"0\")": "0",
|
||||||
"=FISHERINV(2.8)": "0.992631520201128",
|
"=FISHERINV(2.8)": "0.992631520201128",
|
||||||
// GAMMA
|
// GAMMA
|
||||||
"=GAMMA(0.1)": "9.51350769866873",
|
"=GAMMA(0.1)": "9.51350769866873",
|
||||||
"=GAMMA(INT(1))": "1",
|
"=GAMMA(INT(1))": "1",
|
||||||
"=GAMMA(1.5)": "0.886226925452758",
|
"=GAMMA(1.5)": "0.886226925452758",
|
||||||
"=GAMMA(5.5)": "52.3427777845535",
|
"=GAMMA(5.5)": "52.3427777845535",
|
||||||
|
"=GAMMA(\"5.5\")": "52.3427777845535",
|
||||||
// GAMMA.DIST
|
// GAMMA.DIST
|
||||||
"=GAMMA.DIST(6,3,2,FALSE)": "0.112020903827694",
|
"=GAMMA.DIST(6,3,2,FALSE)": "0.112020903827694",
|
||||||
"=GAMMA.DIST(6,3,2,TRUE)": "0.576809918873156",
|
"=GAMMA.DIST(6,3,2,TRUE)": "0.576809918873156",
|
||||||
|
@ -1097,12 +1101,13 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=LARGE(A1,1)": "1",
|
"=LARGE(A1,1)": "1",
|
||||||
"=LARGE(A1:F2,1)": "36693",
|
"=LARGE(A1:F2,1)": "36693",
|
||||||
// MAX
|
// MAX
|
||||||
"=MAX(1)": "1",
|
"=MAX(1)": "1",
|
||||||
"=MAX(TRUE())": "1",
|
"=MAX(TRUE())": "1",
|
||||||
"=MAX(0.5,TRUE())": "1",
|
"=MAX(0.5,TRUE())": "1",
|
||||||
"=MAX(FALSE())": "0",
|
"=MAX(FALSE())": "0",
|
||||||
"=MAX(MUNIT(2))": "1",
|
"=MAX(MUNIT(2))": "1",
|
||||||
"=MAX(INT(1))": "1",
|
"=MAX(INT(1))": "1",
|
||||||
|
"=MAX(\"0\",\"2\")": "2",
|
||||||
// MAXA
|
// MAXA
|
||||||
"=MAXA(1)": "1",
|
"=MAXA(1)": "1",
|
||||||
"=MAXA(TRUE())": "1",
|
"=MAXA(TRUE())": "1",
|
||||||
|
@ -1117,6 +1122,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=MEDIAN(A1:A5,12)": "2",
|
"=MEDIAN(A1:A5,12)": "2",
|
||||||
"=MEDIAN(A1:A5)": "1.5",
|
"=MEDIAN(A1:A5)": "1.5",
|
||||||
"=MEDIAN(A1:A5,MEDIAN(A1:A5,12))": "2",
|
"=MEDIAN(A1:A5,MEDIAN(A1:A5,12))": "2",
|
||||||
|
"=MEDIAN(\"0\",\"2\")": "1",
|
||||||
// MIN
|
// MIN
|
||||||
"=MIN(1)": "1",
|
"=MIN(1)": "1",
|
||||||
"=MIN(TRUE())": "1",
|
"=MIN(TRUE())": "1",
|
||||||
|
@ -1124,6 +1130,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=MIN(FALSE())": "0",
|
"=MIN(FALSE())": "0",
|
||||||
"=MIN(MUNIT(2))": "0",
|
"=MIN(MUNIT(2))": "0",
|
||||||
"=MIN(INT(1))": "1",
|
"=MIN(INT(1))": "1",
|
||||||
|
"=MIN(2,\"1\")": "1",
|
||||||
// MINA
|
// MINA
|
||||||
"=MINA(1)": "1",
|
"=MINA(1)": "1",
|
||||||
"=MINA(TRUE())": "1",
|
"=MINA(TRUE())": "1",
|
||||||
|
@ -1345,14 +1352,15 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=T(N(10))": "",
|
"=T(N(10))": "",
|
||||||
// Logical Functions
|
// Logical Functions
|
||||||
// AND
|
// AND
|
||||||
"=AND(0)": "FALSE",
|
"=AND(0)": "FALSE",
|
||||||
"=AND(1)": "TRUE",
|
"=AND(1)": "TRUE",
|
||||||
"=AND(1,0)": "FALSE",
|
"=AND(1,0)": "FALSE",
|
||||||
"=AND(0,1)": "FALSE",
|
"=AND(0,1)": "FALSE",
|
||||||
"=AND(1=1)": "TRUE",
|
"=AND(1=1)": "TRUE",
|
||||||
"=AND(1<2)": "TRUE",
|
"=AND(1<2)": "TRUE",
|
||||||
"=AND(1>2,2<3,2>0,3>1)": "FALSE",
|
"=AND(1>2,2<3,2>0,3>1)": "FALSE",
|
||||||
"=AND(1=1),1=1": "TRUE",
|
"=AND(1=1),1=1": "TRUE",
|
||||||
|
"=AND(\"TRUE\",\"FALSE\")": "FALSE",
|
||||||
// FALSE
|
// FALSE
|
||||||
"=FALSE()": "FALSE",
|
"=FALSE()": "FALSE",
|
||||||
// IFERROR
|
// IFERROR
|
||||||
|
@ -1372,10 +1380,11 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=NOT(\"true\")": "FALSE",
|
"=NOT(\"true\")": "FALSE",
|
||||||
"=NOT(ISBLANK(B1))": "TRUE",
|
"=NOT(ISBLANK(B1))": "TRUE",
|
||||||
// OR
|
// OR
|
||||||
"=OR(1)": "TRUE",
|
"=OR(1)": "TRUE",
|
||||||
"=OR(0)": "FALSE",
|
"=OR(0)": "FALSE",
|
||||||
"=OR(1=2,2=2)": "TRUE",
|
"=OR(1=2,2=2)": "TRUE",
|
||||||
"=OR(1=2,2=3)": "FALSE",
|
"=OR(1=2,2=3)": "FALSE",
|
||||||
|
"=OR(\"TRUE\",\"FALSE\")": "TRUE",
|
||||||
// SWITCH
|
// SWITCH
|
||||||
"=SWITCH(1,1,\"A\",2,\"B\",3,\"C\",\"N\")": "A",
|
"=SWITCH(1,1,\"A\",2,\"B\",3,\"C\",\"N\")": "A",
|
||||||
"=SWITCH(3,1,\"A\",2,\"B\",3,\"C\",\"N\")": "C",
|
"=SWITCH(3,1,\"A\",2,\"B\",3,\"C\",\"N\")": "C",
|
||||||
|
@ -1897,6 +1906,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
// PRICEDISC
|
// PRICEDISC
|
||||||
"=PRICEDISC(\"04/01/2017\",\"03/31/2021\",2.5%,100)": "90",
|
"=PRICEDISC(\"04/01/2017\",\"03/31/2021\",2.5%,100)": "90",
|
||||||
"=PRICEDISC(\"04/01/2017\",\"03/31/2021\",2.5%,100,3)": "90",
|
"=PRICEDISC(\"04/01/2017\",\"03/31/2021\",2.5%,100,3)": "90",
|
||||||
|
"=PRICEDISC(\"42826\",\"03/31/2021\",2.5%,100,3)": "90",
|
||||||
// PRICEMAT
|
// PRICEMAT
|
||||||
"=PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%)": "107.170454545455",
|
"=PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%)": "107.170454545455",
|
||||||
"=PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%,0)": "107.170454545455",
|
"=PRICEMAT(\"04/01/2017\",\"03/31/2021\",\"01/01/2017\",4.5%,2.5%,0)": "107.170454545455",
|
||||||
|
@ -2335,7 +2345,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
// _xlfn.DECIMAL
|
// _xlfn.DECIMAL
|
||||||
"=_xlfn.DECIMAL()": "DECIMAL requires 2 numeric arguments",
|
"=_xlfn.DECIMAL()": "DECIMAL requires 2 numeric arguments",
|
||||||
`=_xlfn.DECIMAL("X", 2)`: "strconv.ParseInt: parsing \"X\": invalid syntax",
|
`=_xlfn.DECIMAL("X", 2)`: "strconv.ParseInt: parsing \"X\": invalid syntax",
|
||||||
`=_xlfn.DECIMAL(2000, "X")`: "strconv.Atoi: parsing \"X\": invalid syntax",
|
`=_xlfn.DECIMAL(2000, "X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
|
||||||
// DEGREES
|
// DEGREES
|
||||||
"=DEGREES()": "DEGREES requires 1 numeric argument",
|
"=DEGREES()": "DEGREES requires 1 numeric argument",
|
||||||
`=DEGREES("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
|
`=DEGREES("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
|
||||||
|
@ -2461,10 +2471,11 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=RANDBETWEEN()": "RANDBETWEEN requires 2 numeric arguments",
|
"=RANDBETWEEN()": "RANDBETWEEN requires 2 numeric arguments",
|
||||||
"=RANDBETWEEN(2,1)": "#NUM!",
|
"=RANDBETWEEN(2,1)": "#NUM!",
|
||||||
// ROMAN
|
// ROMAN
|
||||||
"=ROMAN()": "ROMAN requires at least 1 argument",
|
"=ROMAN()": "ROMAN requires at least 1 argument",
|
||||||
"=ROMAN(1,2,3)": "ROMAN allows at most 2 arguments",
|
"=ROMAN(1,2,3)": "ROMAN allows at most 2 arguments",
|
||||||
`=ROMAN("X")`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
|
"=ROMAN(1,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||||
`=ROMAN("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
|
"=ROMAN(\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||||
|
"=ROMAN(\"\",1)": "strconv.ParseFloat: parsing \"\": invalid syntax",
|
||||||
// ROUND
|
// ROUND
|
||||||
"=ROUND()": "ROUND requires 2 numeric arguments",
|
"=ROUND()": "ROUND requires 2 numeric arguments",
|
||||||
`=ROUND("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
|
`=ROUND("X",1)`: "strconv.ParseFloat: parsing \"X\": invalid syntax",
|
||||||
|
@ -2776,6 +2787,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=GAMMA()": "GAMMA requires 1 numeric argument",
|
"=GAMMA()": "GAMMA requires 1 numeric argument",
|
||||||
"=GAMMA(F1)": "GAMMA requires 1 numeric argument",
|
"=GAMMA(F1)": "GAMMA requires 1 numeric argument",
|
||||||
"=GAMMA(0)": "#N/A",
|
"=GAMMA(0)": "#N/A",
|
||||||
|
"=GAMMA(\"0\")": "#N/A",
|
||||||
"=GAMMA(INT(0))": "#N/A",
|
"=GAMMA(INT(0))": "#N/A",
|
||||||
// GAMMA.DIST
|
// GAMMA.DIST
|
||||||
"=GAMMA.DIST()": "GAMMA.DIST requires 4 arguments",
|
"=GAMMA.DIST()": "GAMMA.DIST requires 4 arguments",
|
||||||
|
@ -3289,9 +3301,10 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=T(NA())": "#N/A",
|
"=T(NA())": "#N/A",
|
||||||
// Logical Functions
|
// Logical Functions
|
||||||
// AND
|
// AND
|
||||||
`=AND("text")`: "strconv.ParseFloat: parsing \"text\": invalid syntax",
|
"=AND(\"text\")": "#VALUE!",
|
||||||
`=AND(A1:B1)`: "#VALUE!",
|
"=AND(A1:B1)": "#VALUE!",
|
||||||
"=AND()": "AND requires at least 1 argument",
|
"=AND(\"1\",\"TRUE\",\"FALSE\")": "#VALUE!",
|
||||||
|
"=AND()": "AND requires at least 1 argument",
|
||||||
"=AND(1" + strings.Repeat(",1", 30) + ")": "AND accepts at most 30 arguments",
|
"=AND(1" + strings.Repeat(",1", 30) + ")": "AND accepts at most 30 arguments",
|
||||||
// FALSE
|
// FALSE
|
||||||
"=FALSE(A1)": "FALSE takes no arguments",
|
"=FALSE(A1)": "FALSE takes no arguments",
|
||||||
|
@ -3307,8 +3320,9 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=NOT(NOT())": "NOT requires 1 argument",
|
"=NOT(NOT())": "NOT requires 1 argument",
|
||||||
"=NOT(\"\")": "NOT expects 1 boolean or numeric argument",
|
"=NOT(\"\")": "NOT expects 1 boolean or numeric argument",
|
||||||
// OR
|
// OR
|
||||||
`=OR("text")`: "strconv.ParseFloat: parsing \"text\": invalid syntax",
|
"=OR(\"text\")": "#VALUE!",
|
||||||
`=OR(A1:B1)`: "#VALUE!",
|
"=OR(A1:B1)": "#VALUE!",
|
||||||
|
"=OR(\"1\",\"TRUE\",\"FALSE\")": "#VALUE!",
|
||||||
"=OR()": "OR requires at least 1 argument",
|
"=OR()": "OR requires at least 1 argument",
|
||||||
"=OR(1" + strings.Repeat(",1", 30) + ")": "OR accepts at most 30 arguments",
|
"=OR(1" + strings.Repeat(",1", 30) + ")": "OR accepts at most 30 arguments",
|
||||||
// SWITCH
|
// SWITCH
|
||||||
|
@ -3318,6 +3332,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=TRUE(A1)": "TRUE takes no arguments",
|
"=TRUE(A1)": "TRUE takes no arguments",
|
||||||
// XOR
|
// XOR
|
||||||
"=XOR()": "XOR requires at least 1 argument",
|
"=XOR()": "XOR requires at least 1 argument",
|
||||||
|
"=XOR(\"1\")": "#VALUE!",
|
||||||
"=XOR(\"text\")": "#VALUE!",
|
"=XOR(\"text\")": "#VALUE!",
|
||||||
"=XOR(XOR(\"text\"))": "#VALUE!",
|
"=XOR(XOR(\"text\"))": "#VALUE!",
|
||||||
// Date and Time Functions
|
// Date and Time Functions
|
||||||
|
@ -3595,7 +3610,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=HLOOKUP(D2,D1,1,FALSE)": "HLOOKUP requires second argument of table array",
|
"=HLOOKUP(D2,D1,1,FALSE)": "HLOOKUP requires second argument of table array",
|
||||||
"=HLOOKUP(D2,D:D,FALSE,FALSE)": "HLOOKUP requires numeric row argument",
|
"=HLOOKUP(D2,D:D,FALSE,FALSE)": "HLOOKUP requires numeric row argument",
|
||||||
"=HLOOKUP(D2,D:D,1,FALSE,FALSE)": "HLOOKUP requires at most 4 arguments",
|
"=HLOOKUP(D2,D:D,1,FALSE,FALSE)": "HLOOKUP requires at most 4 arguments",
|
||||||
"=HLOOKUP(D2,D:D,1,2)": "strconv.ParseBool: parsing \"2\": invalid syntax",
|
"=HLOOKUP(D2,D:D,1,2)": "HLOOKUP no result found",
|
||||||
"=HLOOKUP(D2,D10:D10,1,FALSE)": "HLOOKUP no result found",
|
"=HLOOKUP(D2,D10:D10,1,FALSE)": "HLOOKUP no result found",
|
||||||
"=HLOOKUP(D2,D2:D3,4,FALSE)": "HLOOKUP has invalid row index",
|
"=HLOOKUP(D2,D2:D3,4,FALSE)": "HLOOKUP has invalid row index",
|
||||||
"=HLOOKUP(D2,C:C,1,FALSE)": "HLOOKUP no result found",
|
"=HLOOKUP(D2,C:C,1,FALSE)": "HLOOKUP no result found",
|
||||||
|
@ -3616,7 +3631,7 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
"=VLOOKUP(D2,D1,1,FALSE)": "VLOOKUP requires second argument of table array",
|
"=VLOOKUP(D2,D1,1,FALSE)": "VLOOKUP requires second argument of table array",
|
||||||
"=VLOOKUP(D2,D:D,FALSE,FALSE)": "VLOOKUP requires numeric col argument",
|
"=VLOOKUP(D2,D:D,FALSE,FALSE)": "VLOOKUP requires numeric col argument",
|
||||||
"=VLOOKUP(D2,D:D,1,FALSE,FALSE)": "VLOOKUP requires at most 4 arguments",
|
"=VLOOKUP(D2,D:D,1,FALSE,FALSE)": "VLOOKUP requires at most 4 arguments",
|
||||||
"=VLOOKUP(D2,D:D,1,2)": "strconv.ParseBool: parsing \"2\": invalid syntax",
|
"=VLOOKUP(A1:A2,A1:A1,1)": "VLOOKUP no result found",
|
||||||
"=VLOOKUP(D2,D10:D10,1,FALSE)": "VLOOKUP no result found",
|
"=VLOOKUP(D2,D10:D10,1,FALSE)": "VLOOKUP no result found",
|
||||||
"=VLOOKUP(D2,D:D,2,FALSE)": "VLOOKUP has invalid column index",
|
"=VLOOKUP(D2,D:D,2,FALSE)": "VLOOKUP has invalid column index",
|
||||||
"=VLOOKUP(D2,C:C,1,FALSE)": "VLOOKUP no result found",
|
"=VLOOKUP(D2,C:C,1,FALSE)": "VLOOKUP no result found",
|
||||||
|
@ -4210,18 +4225,6 @@ func TestCalcCellValue(t *testing.T) {
|
||||||
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestCalcCellValue.xlsx")))
|
assert.NoError(t, f.SaveAs(filepath.Join("test", "TestCalcCellValue.xlsx")))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCalculate(t *testing.T) {
|
|
||||||
err := `strconv.ParseFloat: parsing "string": invalid syntax`
|
|
||||||
opd := NewStack()
|
|
||||||
opd.Push(efp.Token{TValue: "string"})
|
|
||||||
opt := efp.Token{TValue: "-", TType: efp.TokenTypeOperatorPrefix}
|
|
||||||
assert.EqualError(t, calculate(opd, opt), err)
|
|
||||||
opd.Push(efp.Token{TValue: "string"})
|
|
||||||
opd.Push(efp.Token{TValue: "string"})
|
|
||||||
opt = efp.Token{TValue: "-", TType: efp.TokenTypeOperatorInfix}
|
|
||||||
assert.EqualError(t, calculate(opd, opt), err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCalcWithDefinedName(t *testing.T) {
|
func TestCalcWithDefinedName(t *testing.T) {
|
||||||
cellData := [][]interface{}{
|
cellData := [][]interface{}{
|
||||||
{"A1_as_string", "B1_as_string", 123, nil},
|
{"A1_as_string", "B1_as_string", 123, nil},
|
||||||
|
@ -4812,12 +4815,13 @@ func TestCalcSUMIFSAndAVERAGEIFS(t *testing.T) {
|
||||||
assert.Equal(t, expected, result, formula)
|
assert.Equal(t, expected, result, formula)
|
||||||
}
|
}
|
||||||
calcError := map[string]string{
|
calcError := map[string]string{
|
||||||
"=AVERAGEIFS()": "AVERAGEIFS requires at least 3 arguments",
|
"=AVERAGEIFS()": "AVERAGEIFS requires at least 3 arguments",
|
||||||
"=AVERAGEIFS(H1,\"\")": "AVERAGEIFS requires at least 3 arguments",
|
"=AVERAGEIFS(H1,\"\")": "AVERAGEIFS requires at least 3 arguments",
|
||||||
"=AVERAGEIFS(H1,\"\",TRUE,1)": "#N/A",
|
"=AVERAGEIFS(H1,\"\",TRUE,1)": "#N/A",
|
||||||
"=AVERAGEIFS(H1,\"\",TRUE)": "AVERAGEIF divide by zero",
|
"=AVERAGEIFS(H1,\"\",TRUE)": "AVERAGEIF divide by zero",
|
||||||
"=SUMIFS()": "SUMIFS requires at least 3 arguments",
|
"=SUMIFS()": "SUMIFS requires at least 3 arguments",
|
||||||
"=SUMIFS(D2:D13,A2:A13,1,B2:B13)": "#N/A",
|
"=SUMIFS(D2:D13,A2:A13,1,B2:B13)": "#N/A",
|
||||||
|
"=SUMIFS(D20:D23,A2:A13,\">2\",C2:C13,\"Jeff\")": "#VALUE!",
|
||||||
}
|
}
|
||||||
for formula, expected := range calcError {
|
for formula, expected := range calcError {
|
||||||
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula))
|
assert.NoError(t, f.SetCellFormula("Sheet1", "E1", formula))
|
||||||
|
@ -4906,6 +4910,7 @@ func TestCalcXLOOKUP(t *testing.T) {
|
||||||
"=XLOOKUP()": "XLOOKUP requires at least 3 arguments",
|
"=XLOOKUP()": "XLOOKUP requires at least 3 arguments",
|
||||||
"=XLOOKUP($C3,$C5:$C5,$C6:$C17,NA(),0,2,1)": "XLOOKUP allows at most 6 arguments",
|
"=XLOOKUP($C3,$C5:$C5,$C6:$C17,NA(),0,2,1)": "XLOOKUP allows at most 6 arguments",
|
||||||
"=XLOOKUP($C3,$C5,$C6,NA(),0,2)": "#N/A",
|
"=XLOOKUP($C3,$C5,$C6,NA(),0,2)": "#N/A",
|
||||||
|
"=XLOOKUP(\"?\",B2:B9,C2:C9,NA(),2)": "#N/A",
|
||||||
"=XLOOKUP($C3,$C4:$D5,$C6:$C17,NA(),0,2)": "#VALUE!",
|
"=XLOOKUP($C3,$C4:$D5,$C6:$C17,NA(),0,2)": "#VALUE!",
|
||||||
"=XLOOKUP($C3,$C5:$C5,$C6:$G17,NA(),0,-2)": "#VALUE!",
|
"=XLOOKUP($C3,$C5:$C5,$C6:$G17,NA(),0,-2)": "#VALUE!",
|
||||||
"=XLOOKUP($C3,$C5:$G5,$C6:$F7,NA(),0,2)": "#VALUE!",
|
"=XLOOKUP($C3,$C5:$G5,$C6:$F7,NA(),0,2)": "#VALUE!",
|
||||||
|
|
Loading…
Reference in New Issue