diff options
author | xuri <xuri.me@gmail.com> | 2021-11-09 00:10:09 +0800 |
---|---|---|
committer | xuri <xuri.me@gmail.com> | 2021-11-09 00:10:09 +0800 |
commit | a68bc34b0c60143bc649415fd7ff7acca70d6bdf (patch) | |
tree | e749f940f7d3a9995020f2c0e53fe56e22e8e3bf | |
parent | 8f82d8b02909ca96a9c7f7c3431d1ae990c90191 (diff) |
ref #65: new formula functions PERCENTRANK.EXC, PERCENTRANK.INC and PERCENTRANK
-rw-r--r-- | calc.go | 91 | ||||
-rw-r--r-- | calc_test.go | 39 | ||||
-rw-r--r-- | lib.go | 11 |
3 files changed, 137 insertions, 4 deletions
@@ -501,6 +501,9 @@ type formulaFuncs struct { // PERCENTILE.EXC // PERCENTILE.INC // PERCENTILE +// PERCENTRANK.EXC +// PERCENTRANK.INC +// PERCENTRANK // PERMUT // PERMUTATIONA // PI @@ -5859,6 +5862,88 @@ func (fn *formulaFuncs) PERCENTILE(argsList *list.List) formulaArg { return newNumberFormulaArg(numbers[int(base)] + ((numbers[int(next)] - numbers[int(base)]) * proportion)) } +// percentrank is an implementation of the formula functions PERCENTRANK and +// PERCENTRANK.INC. +func (fn *formulaFuncs) percentrank(name string, argsList *list.List) formulaArg { + if argsList.Len() != 2 && argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, fmt.Sprintf("%s requires 2 or 3 arguments", name)) + } + array := argsList.Front().Value.(formulaArg).ToList() + x := argsList.Front().Next().Value.(formulaArg).ToNumber() + if x.Type != ArgNumber { + return x + } + numbers := []float64{} + for _, arg := range array { + if arg.Type == ArgError { + return arg + } + num := arg.ToNumber() + if num.Type == ArgNumber { + numbers = append(numbers, num.Number) + } + } + cnt := len(numbers) + sort.Float64s(numbers) + if x.Number < numbers[0] || x.Number > numbers[cnt-1] { + return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) + } + pos, significance := float64(inFloat64Slice(numbers, x.Number)), newNumberFormulaArg(3) + if argsList.Len() == 3 { + if significance = argsList.Back().Value.(formulaArg).ToNumber(); significance.Type != ArgNumber { + return significance + } + if significance.Number < 1 { + return newErrorFormulaArg(formulaErrorNUM, fmt.Sprintf("%s arguments significance should be > 1", name)) + } + } + if pos == -1 { + pos = 0 + cmp := numbers[0] + for cmp < x.Number { + pos++ + cmp = numbers[int(pos)] + } + pos-- + pos += (x.Number - numbers[int(pos)]) / (cmp - numbers[int(pos)]) + } + pow := math.Pow(10, float64(significance.Number)) + digit := pow * float64(pos) / (float64(cnt) - 1) + if name == "PERCENTRANK.EXC" { + digit = pow * float64(pos+1) / (float64(cnt) + 1) + } + return newNumberFormulaArg(math.Floor(digit) / pow) +} + +// PERCENTRANKdotEXC function calculates the relative position, between 0 and +// 1 (exclusive), of a specified value within a supplied array. The syntax of +// the function is: +// +// PERCENTRANK.EXC(array,x,[significance]) +// +func (fn *formulaFuncs) PERCENTRANKdotEXC(argsList *list.List) formulaArg { + return fn.percentrank("PERCENTRANK.EXC", argsList) +} + +// PERCENTRANKdotINC function calculates the relative position, between 0 and +// 1 (inclusive), of a specified value within a supplied array.The syntax of +// the function is: +// +// PERCENTRANK.INC(array,x,[significance]) +// +func (fn *formulaFuncs) PERCENTRANKdotINC(argsList *list.List) formulaArg { + return fn.percentrank("PERCENTRANK.INC", argsList) +} + +// PERCENTRANK function calculates the relative position of a specified value, +// within a set of values, as a percentage. The syntax of the function is: +// +// PERCENTRANK(array,x,[significance]) +// +func (fn *formulaFuncs) PERCENTRANK(argsList *list.List) formulaArg { + return fn.percentrank("PERCENTRANK", argsList) +} + // PERMUT function calculates the number of permutations of a specified number // of objects from a set of objects. The syntax of the function is: // @@ -5993,10 +6078,8 @@ func (fn *formulaFuncs) rank(name string, argsList *list.List) formulaArg { if order.Number == 0 { sort.Sort(sort.Reverse(sort.Float64Slice(arr))) } - for idx, n := range arr { - if num.Number == n { - return newNumberFormulaArg(float64(idx + 1)) - } + if idx := inFloat64Slice(arr, num.Number); idx != -1 { + return newNumberFormulaArg(float64(idx + 1)) } return newErrorFormulaArg(formulaErrorNA, formulaErrorNA) } diff --git a/calc_test.go b/calc_test.go index 26c1ca1..c621f3b 100644 --- a/calc_test.go +++ b/calc_test.go @@ -886,6 +886,24 @@ func TestCalcCellValue(t *testing.T) { // PERCENTILE "=PERCENTILE(A1:A4,0.2)": "0.6", "=PERCENTILE(0,0)": "0", + // PERCENTRANK.EXC + "=PERCENTRANK.EXC(A1:B4,0)": "0.142", + "=PERCENTRANK.EXC(A1:B4,2)": "0.428", + "=PERCENTRANK.EXC(A1:B4,2.5)": "0.5", + "=PERCENTRANK.EXC(A1:B4,2.6,1)": "0.5", + "=PERCENTRANK.EXC(A1:B4,5)": "0.857", + // PERCENTRANK.INC + "=PERCENTRANK.INC(A1:B4,0)": "0", + "=PERCENTRANK.INC(A1:B4,2)": "0.4", + "=PERCENTRANK.INC(A1:B4,2.5)": "0.5", + "=PERCENTRANK.INC(A1:B4,2.6,1)": "0.5", + "=PERCENTRANK.INC(A1:B4,5)": "1", + // PERCENTRANK + "=PERCENTRANK(A1:B4,0)": "0", + "=PERCENTRANK(A1:B4,2)": "0.4", + "=PERCENTRANK(A1:B4,2.5)": "0.5", + "=PERCENTRANK(A1:B4,2.6,1)": "0.5", + "=PERCENTRANK(A1:B4,5)": "1", // PERMUT "=PERMUT(6,6)": "720", "=PERMUT(7,6)": "5040", @@ -2176,6 +2194,27 @@ func TestCalcCellValue(t *testing.T) { "=PERCENTILE(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", "=PERCENTILE(0,-1)": "#N/A", "=PERCENTILE(NA(),1)": "#N/A", + // PERCENTRANK.EXC + "=PERCENTRANK.EXC()": "PERCENTRANK.EXC requires 2 or 3 arguments", + "=PERCENTRANK.EXC(A1:B4,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=PERCENTRANK.EXC(A1:B4,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=PERCENTRANK.EXC(A1:B4,0,0)": "PERCENTRANK.EXC arguments significance should be > 1", + "=PERCENTRANK.EXC(A1:B4,6)": "#N/A", + "=PERCENTRANK.EXC(NA(),1)": "#N/A", + // PERCENTRANK.INC + "=PERCENTRANK.INC()": "PERCENTRANK.INC requires 2 or 3 arguments", + "=PERCENTRANK.INC(A1:B4,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=PERCENTRANK.INC(A1:B4,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=PERCENTRANK.INC(A1:B4,0,0)": "PERCENTRANK.INC arguments significance should be > 1", + "=PERCENTRANK.INC(A1:B4,6)": "#N/A", + "=PERCENTRANK.INC(NA(),1)": "#N/A", + // PERCENTRANK + "=PERCENTRANK()": "PERCENTRANK requires 2 or 3 arguments", + "=PERCENTRANK(A1:B4,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=PERCENTRANK(A1:B4,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=PERCENTRANK(A1:B4,0,0)": "PERCENTRANK arguments significance should be > 1", + "=PERCENTRANK(A1:B4,6)": "#N/A", + "=PERCENTRANK(NA(),1)": "#N/A", // PERMUT "=PERMUT()": "PERMUT requires 2 numeric arguments", "=PERMUT(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax", @@ -381,6 +381,17 @@ func inStrSlice(a []string, x string) int { return -1 } +// inFloat64Slice provides a method to check if an element is present in an +// float64 array, and return the index of its location, otherwise return -1. +func inFloat64Slice(a []float64, x float64) int { + for idx, n := range a { + if x == n { + return idx + } + } + return -1 +} + // boolPtr returns a pointer to a bool with the given value. func boolPtr(b bool) *bool { return &b } |