diff --git a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java index a343d759b1..92f6ec8c91 100644 --- a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java +++ b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java @@ -224,7 +224,8 @@ public final class FunctionEval { retval[325] = AggregateFunction.LARGE; retval[326] = AggregateFunction.SMALL; - + retval[328] = AggregateFunction.PERCENTILE; + retval[330] = new Mode(); retval[336] = TextFunction.CONCATENATE; diff --git a/src/java/org/apache/poi/ss/formula/functions/AggregateFunction.java b/src/java/org/apache/poi/ss/formula/functions/AggregateFunction.java index 52f12210fe..084355d303 100644 --- a/src/java/org/apache/poi/ss/formula/functions/AggregateFunction.java +++ b/src/java/org/apache/poi/ss/formula/functions/AggregateFunction.java @@ -67,6 +67,72 @@ public abstract class AggregateFunction extends MultiOperandNumericFunction { return new NumberEval(result); } } + + /** + * Returns the k-th percentile of values in a range. You can use this function to establish a threshold of + * acceptance. For example, you can decide to examine candidates who score above the 90th percentile. + * + * PERCENTILE(array,k) + * Array is the array or range of data that defines relative standing. + * K is the percentile value in the range 0..1, inclusive. + * + * Remarks + * + */ + private static final class Percentile extends Fixed2ArgFunction { + + protected Percentile() { + } + + public ValueEval evaluate(int srcRowIndex, int srcColumnIndex, ValueEval arg0, + ValueEval arg1) { + double dn; + try { + ValueEval ve1 = OperandResolver.getSingleValue(arg1, srcRowIndex, srcColumnIndex); + dn = OperandResolver.coerceValueToDouble(ve1); + } catch (EvaluationException e1) { + // all errors in the second arg translate to #VALUE! + return ErrorEval.VALUE_INVALID; + } + if (dn < 0 || dn > 1) { // has to be percentage + return ErrorEval.NUM_ERROR; + } + + double result; + try { + double[] ds = ValueCollector.collectValues(arg0); + int N = ds.length; + + if (N == 0 || N > 8191) { + return ErrorEval.NUM_ERROR; + } + + double n = (N - 1) * dn + 1; + if (n == 1d) { + result = StatsLib.kthSmallest(ds, 1); + } else if (n == N) { + result = StatsLib.kthLargest(ds, 1); + } else { + int k = (int) n; + double d = n - k; + result = StatsLib.kthSmallest(ds, k) + d + * (StatsLib.kthSmallest(ds, k + 1) - StatsLib.kthSmallest(ds, k)); + } + + NumericFunction.checkValue(result); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + + return new NumberEval(result); + } + } + static final class ValueCollector extends MultiOperandNumericFunction { private static final ValueCollector instance = new ValueCollector(); public ValueCollector() { @@ -148,6 +214,9 @@ public abstract class AggregateFunction extends MultiOperandNumericFunction { return values.length > 0 ? MathX.min(values) : 0; } }; + + public static final Function PERCENTILE = new Percentile(); + public static final Function PRODUCT = new AggregateFunction() { protected double evaluate(double[] values) { return MathX.product(values); diff --git a/test-data/spreadsheet/FormulaEvalTestData.xls b/test-data/spreadsheet/FormulaEvalTestData.xls index 29924ae6d0..dfa87f7cb7 100644 Binary files a/test-data/spreadsheet/FormulaEvalTestData.xls and b/test-data/spreadsheet/FormulaEvalTestData.xls differ