fn: CEILING, CEILING.MATH

This commit is contained in:
xuri 2020-05-04 21:22:11 +08:00
parent 789adf9202
commit 6f796b88e6
No known key found for this signature in database
GPG Key ID: BA5E5BB1C948EDF7
2 changed files with 126 additions and 4 deletions

101
calc.go
View File

@ -220,7 +220,9 @@ func (f *File) evalInfixExp(sheet string, tokens []efp.Token) (efp.Token, error)
argsList.PushBack(opfdStack.Pop())
}
// call formula function to evaluate
result, err := callFuncByName(&formulaFuncs{}, strings.ReplaceAll(opfStack.Peek().(efp.Token).TValue, "_xlfn.", ""), []reflect.Value{reflect.ValueOf(argsList)})
result, err := callFuncByName(&formulaFuncs{}, strings.NewReplacer(
"_xlfn", "", ".", "").Replace(opfStack.Peek().(efp.Token).TValue),
[]reflect.Value{reflect.ValueOf(argsList)})
if err != nil {
return efp.Token{}, err
}
@ -801,6 +803,103 @@ func (fn *formulaFuncs) BASE(argsList *list.List) (result string, err error) {
return
}
// CEILING function rounds a supplied number away from zero, to the nearest
// multiple of a given number. The syntax of the function is:
//
// CEILING(number,significance)
//
func (fn *formulaFuncs) CEILING(argsList *list.List) (result string, err error) {
if argsList.Len() == 0 {
err = errors.New("CEILING requires at least 1 argument")
return
}
if argsList.Len() > 2 {
err = errors.New("CEILING allows at most 2 arguments")
return
}
var number, significance float64
number, err = strconv.ParseFloat(argsList.Front().Value.(efp.Token).TValue, 64)
if err != nil {
return
}
significance = 1
if number < 0 {
significance = -1
}
if argsList.Len() > 1 {
significance, err = strconv.ParseFloat(argsList.Back().Value.(efp.Token).TValue, 64)
if err != nil {
return
}
}
if significance < 0 && number > 0 {
err = errors.New("negative sig to CEILING invalid")
return
}
if argsList.Len() == 1 {
result = fmt.Sprintf("%g", math.Ceil(number))
return
}
number, res := math.Modf(number / significance)
if res > 0 {
number++
}
result = fmt.Sprintf("%g", number*significance)
return
}
// CEILINGMATH function rounds a supplied number up to a supplied multiple of
// significance. The syntax of the function is:
//
// CEILING.MATH(number,[significance],[mode])
//
func (fn *formulaFuncs) CEILINGMATH(argsList *list.List) (result string, err error) {
if argsList.Len() == 0 {
err = errors.New("CEILING.MATH requires at least 1 argument")
return
}
if argsList.Len() > 3 {
err = errors.New("CEILING.MATH allows at most 3 arguments")
return
}
var number, significance, mode float64 = 0, 1, 1
number, err = strconv.ParseFloat(argsList.Front().Value.(efp.Token).TValue, 64)
if err != nil {
return
}
if number < 0 {
significance = -1
}
if argsList.Len() > 1 {
significance, err = strconv.ParseFloat(argsList.Front().Next().Value.(efp.Token).TValue, 64)
if err != nil {
return
}
}
if argsList.Len() == 1 {
result = fmt.Sprintf("%g", math.Ceil(number))
return
}
if argsList.Len() > 2 {
mode, err = strconv.ParseFloat(argsList.Back().Value.(efp.Token).TValue, 64)
if err != nil {
return
}
}
val, res := math.Modf(number / significance)
_, _ = res, mode
if res != 0 {
if number > 0 {
val++
} else if mode < 0 {
val--
}
}
result = fmt.Sprintf("%g", val*significance)
return
}
// GCD function returns the greatest common divisor of two or more supplied
// integers. The syntax of the function is:
//

View File

@ -67,6 +67,22 @@ func TestCalcCellValue(t *testing.T) {
"=BASE(12,2)": "1100",
"=BASE(12,2,8)": "00001100",
"=BASE(100000,16)": "186A0",
// CEILING
"=CEILING(22.25,0.1)": "22.3",
"=CEILING(22.25,0.5)": "22.5",
"=CEILING(22.25,1)": "23",
"=CEILING(22.25,10)": "30",
"=CEILING(22.25,20)": "40",
"=CEILING(-22.25,-0.1)": "-22.3",
"=CEILING(-22.25,-1)": "-23",
"=CEILING(-22.25,-5)": "-25",
// _xlfn.CEILING.MATH
"=_xlfn.CEILING.MATH(15.25,1)": "16",
"=_xlfn.CEILING.MATH(15.25,0.1)": "15.3",
"=_xlfn.CEILING.MATH(15.25,5)": "20",
"=_xlfn.CEILING.MATH(-15.25,1)": "-15",
"=_xlfn.CEILING.MATH(-15.25,1,1)": "-15", // should be 16
"=_xlfn.CEILING.MATH(-15.25,10)": "-10",
// GCD
"=GCD(1,5)": "1",
"=GCD(15,10,25)": "5",
@ -123,11 +139,11 @@ func TestCalcCellValue(t *testing.T) {
"=ACOS()": "ACOS requires 1 numeric arguments",
// ACOSH
"=ACOSH()": "ACOSH requires 1 numeric arguments",
// ACOT
// _xlfn.ACOT
"=_xlfn.ACOT()": "ACOT requires 1 numeric arguments",
// ACOTH
// _xlfn.ACOTH
"=_xlfn.ACOTH()": "ACOTH requires 1 numeric arguments",
// ARABIC
// _xlfn.ARABIC
"_xlfn.ARABIC()": "ARABIC requires 1 numeric arguments",
// ASIN
"=ASIN()": "ASIN requires 1 numeric arguments",
@ -143,6 +159,13 @@ func TestCalcCellValue(t *testing.T) {
"=BASE()": "BASE requires at least 2 arguments",
"=BASE(1,2,3,4)": "BASE allows at most 3 arguments",
"=BASE(1,1)": "radix must be an integer ≥ 2 and ≤ 36",
// CEILING
"=CEILING()": "CEILING requires at least 1 argument",
"=CEILING(1,2,3)": "CEILING allows at most 2 arguments",
"=CEILING(1,-1)": "negative sig to CEILING invalid",
// _xlfn.CEILING.MATH
"=_xlfn.CEILING.MATH()": "CEILING.MATH requires at least 1 argument",
"=_xlfn.CEILING.MATH(1,2,3,4)": "CEILING.MATH allows at most 3 arguments",
// GCD
"=GCD()": "GCD requires at least 1 argument",
"=GCD(-1)": "GCD only accepts positive arguments",