Browse Source

fn: CEILING, CEILING.MATH

xuri 5 years ago
parent
commit
6f796b88e6
2 changed files with 126 additions and 4 deletions
  1. 100 1
      calc.go
  2. 26 3
      calc_test.go

+ 100 - 1
calc.go

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

+ 26 - 3
calc_test.go

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