From 47d80f116dd8ce24b9897355612d47bff62395d3 Mon Sep 17 00:00:00 2001 From: PJ Fanning Date: Mon, 16 Aug 2021 11:29:33 +0000 Subject: [PATCH] [github-242] add support for MAXIFS, MINIFS, AVERAGEIFS functions. Thanks to jniewerth. This closes #242 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1892366 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/ss/formula/atp/AnalysisToolPak.java | 6 +- .../poi/ss/formula/functions/Averageifs.java | 82 +++++++++++ .../poi/ss/formula/functions/Baseifs.java | 76 +++++------ .../poi/ss/formula/functions/Countifs.java | 21 ++- .../poi/ss/formula/functions/Maxifs.java | 78 +++++++++++ .../poi/ss/formula/functions/Minifs.java | 78 +++++++++++ .../poi/ss/formula/functions/Sumifs.java | 20 +++ .../ss/formula/functions/TestAverageifs.java | 108 +++++++++++++++ .../poi/ss/formula/functions/TestMaxifs.java | 129 ++++++++++++++++++ .../poi/ss/formula/functions/TestMinifs.java | 129 ++++++++++++++++++ 10 files changed, 682 insertions(+), 45 deletions(-) create mode 100644 poi/src/main/java/org/apache/poi/ss/formula/functions/Averageifs.java create mode 100644 poi/src/main/java/org/apache/poi/ss/formula/functions/Maxifs.java create mode 100644 poi/src/main/java/org/apache/poi/ss/formula/functions/Minifs.java create mode 100644 poi/src/test/java/org/apache/poi/ss/formula/functions/TestAverageifs.java create mode 100644 poi/src/test/java/org/apache/poi/ss/formula/functions/TestMaxifs.java create mode 100644 poi/src/test/java/org/apache/poi/ss/formula/functions/TestMinifs.java diff --git a/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java b/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java index ca0a91df00..703dd3e7a4 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java @@ -69,7 +69,7 @@ public final class AnalysisToolPak implements UDFFinder { r(m, "AMORDEGRC", null); r(m, "AMORLINC", null); r(m, "AVERAGEIF", null); - r(m, "AVERAGEIFS", null); + r(m, "AVERAGEIFS", Averageifs.instance); r(m, "BAHTTEXT", null); r(m, "BESSELI", null); r(m, "BESSELJ", null); @@ -142,7 +142,9 @@ public final class AnalysisToolPak implements UDFFinder { r(m, "ISODD", ParityFunction.IS_ODD); r(m, "JIS", null); r(m, "LCM", null); + r(m, "MAXIFS", Maxifs.instance); r(m, "MDURATION", null); + r(m, "MINIFS", Minifs.instance); r(m, "MROUND", MRound.instance); r(m, "MULTINOMIAL", null); r(m, "NETWORKDAYS", NetworkdaysFunction.instance); @@ -239,7 +241,7 @@ public final class AnalysisToolPak implements UDFFinder { * @throws IllegalArgumentException if the function is unknown or already registered. * @since 3.8 beta6 */ - public static void registerFunction(String name, FreeRefFunction func){ + public static void registerFunction(String name, FreeRefFunction func){ AnalysisToolPak inst = (AnalysisToolPak)instance; if(!isATPFunction(name)) { FunctionMetadata metaData = FunctionMetadataRegistry.getFunctionByName(name); diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Averageifs.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Averageifs.java new file mode 100644 index 0000000000..6aa87b4101 --- /dev/null +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Averageifs.java @@ -0,0 +1,82 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ + +package org.apache.poi.ss.formula.functions; + +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.ValueEval; + +/** + * Implementation for the Excel function AVERAGEIFS

+ * + * Syntax :

+ * AVERAGEIFS ( average_range, criteria_range1, criteria1, + * [criteria_range2, criteria2], ...) + *

+ */ +public final class Averageifs extends Baseifs { + /** + * Singleton + */ + public static final FreeRefFunction instance = new Averageifs(); + + /** + * https://support.microsoft.com/en-us/office/averageifs-function-48910c45-1fc0-4389-a028-f7c5c3001690 + * AVERAGEIFS(average_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) + * need at least 3 arguments and need to have an odd number of arguments (average_range plus x*(criteria_range, criteria)) + */ + @Override + protected boolean hasInitialRange() { + return true; + } + + @Override + protected Aggregator createAggregator() { + return new Aggregator() { + Double sum = 0.0; + Integer count = 0; + + @Override + public void addValue(ValueEval value) { + if(!(value instanceof NumberEval)) return; + + double d = ((NumberEval) value).getNumberValue();; + sum += d; + count++; + + } + + @Override + public ValueEval getResult() { + return count == 0 ? ErrorEval.DIV_ZERO : new NumberEval(sum / count); + } + }; + } +} diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Baseifs.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Baseifs.java index 9bee7efb7c..d3ed93a2f4 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Baseifs.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Baseifs.java @@ -40,11 +40,21 @@ import org.apache.poi.ss.formula.functions.Countif.ErrorMatcher; * @return true if there should be a range argument before the criteria pairs */ protected abstract boolean hasInitialRange(); - + + /** + * Implements the details of a specific aggregation function + */ + protected static interface Aggregator { + void addValue(ValueEval d); + ValueEval getResult(); + } + + protected abstract Aggregator createAggregator(); + public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { final boolean hasInitialRange = hasInitialRange(); final int firstCriteria = hasInitialRange ? 1 : 0; - + if( args.length < (2+firstCriteria) || args.length % 2 != firstCriteria ) { return ErrorEval.VALUE_INVALID; } @@ -54,21 +64,20 @@ import org.apache.poi.ss.formula.functions.Countif.ErrorMatcher; if (hasInitialRange) { sumRange = convertRangeArg(args[0]); } - + // collect pairs of ranges and criteria AreaEval[] ae = new AreaEval[(args.length - firstCriteria)/2]; I_MatchPredicate[] mp = new I_MatchPredicate[ae.length]; for(int i = firstCriteria, k=0; i < args.length; i += 2, k++){ ae[k] = convertRangeArg(args[i]); - + mp[k] = Countif.createCriteriaPredicate(args[i+1], ec.getRowIndex(), ec.getColumnIndex()); } validateCriteriaRanges(sumRange, ae); validateCriteria(mp); - double result = aggregateMatchingCells(sumRange, ae, mp); - return new NumberEval(result); + return aggregateMatchingCells(createAggregator(), sumRange, ae, mp); } catch (EvaluationException e) { return e.getErrorEval(); } @@ -84,16 +93,16 @@ import org.apache.poi.ss.formula.functions.Countif.ErrorMatcher; private static void validateCriteriaRanges(AreaEval sumRange, AreaEval[] criteriaRanges) throws EvaluationException { int h = criteriaRanges[0].getHeight(); int w = criteriaRanges[0].getWidth(); - - if (sumRange != null - && (sumRange.getHeight() != h - || sumRange.getWidth() != w) ) { + + if (sumRange != null + && (sumRange.getHeight() != h + || sumRange.getWidth() != w) ) { throw EvaluationException.invalidValue(); } - + for(AreaEval r : criteriaRanges){ if(r.getHeight() != h || - r.getWidth() != w ) { + r.getWidth() != w ) { throw EvaluationException.invalidValue(); } } @@ -107,7 +116,7 @@ import org.apache.poi.ss.formula.functions.Countif.ErrorMatcher; */ private static void validateCriteria(I_MatchPredicate[] criteria) throws EvaluationException { for(I_MatchPredicate predicate : criteria) { - + // check for errors in predicate and return immediately using this error code if(predicate instanceof ErrorMatcher) { throw new EvaluationException(ErrorEval.valueOf(((ErrorMatcher)predicate).getValue())); @@ -123,12 +132,11 @@ import org.apache.poi.ss.formula.functions.Countif.ErrorMatcher; * @return the computed value * @throws EvaluationException if there is an issue with eval */ - private static double aggregateMatchingCells(AreaEval sumRange, AreaEval[] ranges, I_MatchPredicate[] predicates) + private static ValueEval aggregateMatchingCells(Aggregator aggregator, AreaEval sumRange, AreaEval[] ranges, I_MatchPredicate[] predicates) throws EvaluationException { int height = ranges[0].getHeight(); int width = ranges[0].getWidth(); - double result = 0.0; for (int r = 0; r < height; r++) { for (int c = 0; c < width; c++) { @@ -142,38 +150,22 @@ import org.apache.poi.ss.formula.functions.Countif.ErrorMatcher; matches = false; break; } - } - if(matches) { // sum only if all of the corresponding criteria specified are true for that cell. - result += accumulate(sumRange, r, c); + if(matches) { // aggregate only if all of the corresponding criteria specified are true for that cell. + if(sumRange != null) { + ValueEval value = sumRange.getRelativeValue(r, c); + if (value instanceof ErrorEval) { + throw new EvaluationException((ErrorEval)value); + } + aggregator.addValue(value); + } else { + aggregator.addValue(null); + } } } } - return result; - } - - /** - * For counts, this would return 1, for sums it returns a cell value or zero. - * This is only called after all the criteria are confirmed true for the coordinates. - * @param sumRange if used - * @param relRowIndex - * @param relColIndex - * @return the aggregate input value corresponding to the given range coordinates - * @throws EvaluationException if there is an issue with eval - */ - private static double accumulate(AreaEval sumRange, int relRowIndex, int relColIndex) throws EvaluationException { - if (sumRange == null) return 1.0; // count - - ValueEval addend = sumRange.getRelativeValue(relRowIndex, relColIndex); - if (addend instanceof NumberEval) { - return ((NumberEval) addend).getNumberValue(); - } else if (addend instanceof ErrorEval) { - throw new EvaluationException((ErrorEval)addend); - } else { - // everything else (including string and boolean values) counts as zero - return 0.0; - } + return aggregator.getResult(); } protected static AreaEval convertRangeArg(ValueEval eval) throws EvaluationException { diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Countifs.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Countifs.java index 42c158613e..657e414d8f 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Countifs.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Countifs.java @@ -18,6 +18,9 @@ package org.apache.poi.ss.formula.functions; +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.ValueEval; + /** * Implementation for the function COUNTIFS *

@@ -41,5 +44,21 @@ public class Countifs extends Baseifs { protected boolean hasInitialRange() { return false; } -} + @Override + protected Aggregator createAggregator() { + return new Aggregator() { + double accumulator = 0.0; + + @Override + public void addValue(ValueEval value) { + accumulator += 1.0; + } + + @Override + public ValueEval getResult() { + return new NumberEval(accumulator); + } + }; + } +} diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Maxifs.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Maxifs.java new file mode 100644 index 0000000000..748dbb17b4 --- /dev/null +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Maxifs.java @@ -0,0 +1,78 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ + +package org.apache.poi.ss.formula.functions; + +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.ValueEval; + +/** + * Implementation for the Excel function MAXIFS

+ * + * Syntax :

+ * MAXIFS ( max_range, criteria_range1, criteria1, + * [criteria_range2, criteria2], ...) + *

+ */ +public final class Maxifs extends Baseifs { + /** + * Singleton + */ + public static final FreeRefFunction instance = new Maxifs(); + + /** + * https://support.microsoft.com/en-us/office/maxifs-function-dfd611e6-da2c-488a-919b-9b6376b28883 + * MAXIFS(max_range, criteria_range1, criteria1, [criteria_range2, criteria2], ...) + * need at least 3 arguments and need to have an odd number of arguments (max-range plus x*(criteria_range, criteria)) + */ + @Override + protected boolean hasInitialRange() { + return true; + } + + @Override + protected Aggregator createAggregator() { + return new Aggregator() { + Double accumulator = null; + + @Override + public void addValue(ValueEval value) { + double d = (value instanceof NumberEval) ? ((NumberEval) value).getNumberValue() : 0.0; + if(accumulator == null || accumulator < d) { + accumulator = d; + } + } + + @Override + public ValueEval getResult() { + return new NumberEval(accumulator == null ? 0.0 : accumulator); + } + }; + } +} diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Minifs.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Minifs.java new file mode 100644 index 0000000000..d203c59c51 --- /dev/null +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Minifs.java @@ -0,0 +1,78 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ + +package org.apache.poi.ss.formula.functions; + +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.ValueEval; + +/** + * Implementation for the Excel function MINIFS

+ * + * Syntax :

+ * MINIFS ( min_range, criteria_range1, criteria1, + * [criteria_range2, criteria2], ...) + *

+ */ +public final class Minifs extends Baseifs { + /** + * Singleton + */ + public static final FreeRefFunction instance = new Minifs(); + + /** + * https://support.microsoft.com/en-us/office/minifs-function-6ca1ddaa-079b-4e74-80cc-72eef32e6599 + * MINIFS(min_range, criteria_range1, criteria1, [criteria_range2, criteria2], ... + * need at least 3 arguments and need to have an odd number of arguments (min-range plus x*(criteria_range, criteria)) + */ + @Override + protected boolean hasInitialRange() { + return true; + } + + @Override + protected Aggregator createAggregator() { + return new Aggregator() { + Double accumulator = null; + + @Override + public void addValue(ValueEval value) { + double d = (value instanceof NumberEval) ? ((NumberEval) value).getNumberValue() : 0.0; + if(accumulator == null || accumulator > d) { + accumulator = d; + } + } + + @Override + public ValueEval getResult() { + return new NumberEval(accumulator == null ? 0.0 : accumulator); + } + }; + } +} diff --git a/poi/src/main/java/org/apache/poi/ss/formula/functions/Sumifs.java b/poi/src/main/java/org/apache/poi/ss/formula/functions/Sumifs.java index c23a05cb56..a180c97107 100644 --- a/poi/src/main/java/org/apache/poi/ss/formula/functions/Sumifs.java +++ b/poi/src/main/java/org/apache/poi/ss/formula/functions/Sumifs.java @@ -19,6 +19,9 @@ package org.apache.poi.ss.formula.functions; +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.ValueEval; + /** * Implementation for the Excel function SUMIFS

* @@ -52,4 +55,21 @@ public final class Sumifs extends Baseifs { protected boolean hasInitialRange() { return true; } + + @Override + protected Aggregator createAggregator() { + return new Aggregator() { + double accumulator = 0.0; + + @Override + public void addValue(ValueEval value) { + accumulator += (value instanceof NumberEval) ? ((NumberEval) value).getNumberValue() : 0.0; + } + + @Override + public ValueEval getResult() { + return new NumberEval(accumulator); + } + }; + } } diff --git a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestAverageifs.java b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestAverageifs.java new file mode 100644 index 0000000000..d299d9e04b --- /dev/null +++ b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestAverageifs.java @@ -0,0 +1,108 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ + +package org.apache.poi.ss.formula.functions; + +import static org.apache.poi.ss.util.Utils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.OperationEvaluationContext; +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.NumericValueEval; +import org.apache.poi.ss.formula.eval.StringEval; +import org.apache.poi.ss.formula.eval.ValueEval; +import org.apache.poi.ss.usermodel.FormulaError; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +/** + * Test cases for AVERAGEIFS() + */ +final class TestAverageifs { + + private static final OperationEvaluationContext EC = new OperationEvaluationContext(null, null, 0, 1, 0, null); + + private static ValueEval invokeAverageifs(ValueEval[] args) { + return new Averageifs().evaluate(args, EC); + } + + private static void confirmDouble(double expected, ValueEval actualEval) { + assertTrue(actualEval instanceof NumericValueEval, "Expected numeric result"); + NumericValueEval nve = (NumericValueEval)actualEval; + assertEquals(expected, nve.getNumberValue(), 0); + } + + private static void confirm(double expectedResult, ValueEval[] args) { + confirmDouble(expectedResult, invokeAverageifs(args)); + } + + private static void confirmError(ErrorEval errorEval, ValueEval[] args) { + ValueEval actualEval = invokeAverageifs(args); + assertEquals(errorEval, actualEval); + } + + /** + * Example 1 from + * https://support.microsoft.com/en-us/office/maxifs-function-dfd611e6-da2c-488a-919b-9b6376b28883 + */ + @Test + void testExample1() { + ValueEval[] b2b5 = new ValueEval[] { + new StringEval("Quiz"), + new StringEval("Grade"), + new NumberEval(75), + new NumberEval(94) + }; + + ValueEval[] args; + // "=AVERAGEIFS(B2:B5, B2:B5, ">70", B2:B5, "<90")" + args = new ValueEval[]{ + EvalFactory.createAreaEval("B2:B5", b2b5), + EvalFactory.createAreaEval("B2:B5", b2b5), + new StringEval(">70"), + EvalFactory.createAreaEval("B2:B5", b2b5), + new StringEval("<90") + }; + confirm(75.0, args); + + ValueEval[] c2c5 = new ValueEval[] { + new StringEval("Quiz"), + new StringEval("Grade"), + new NumberEval(85), + new NumberEval(80) + }; + // "=AVERAGEIFS(C2:C5, C2:C5, ">95")" + args = new ValueEval[]{ + EvalFactory.createAreaEval("C2:C5", c2c5), + EvalFactory.createAreaEval("C2:C5", c2c5), + new StringEval(">95") + }; + confirmError(ErrorEval.DIV_ZERO, args); + + } + +} diff --git a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestMaxifs.java b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestMaxifs.java new file mode 100644 index 0000000000..91d8c32b7b --- /dev/null +++ b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestMaxifs.java @@ -0,0 +1,129 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ + +package org.apache.poi.ss.formula.functions; + +import static org.apache.poi.ss.util.Utils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.OperationEvaluationContext; +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.NumericValueEval; +import org.apache.poi.ss.formula.eval.StringEval; +import org.apache.poi.ss.formula.eval.ValueEval; +import org.apache.poi.ss.usermodel.FormulaError; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +/** + * Test cases for MAXIFS() + */ +final class TestMaxifs { + + private static final OperationEvaluationContext EC = new OperationEvaluationContext(null, null, 0, 1, 0, null); + + private static ValueEval invokeMaxifs(ValueEval[] args) { + return new Maxifs().evaluate(args, EC); + } + + private static void confirmDouble(double expected, ValueEval actualEval) { + assertTrue(actualEval instanceof NumericValueEval, "Expected numeric result"); + NumericValueEval nve = (NumericValueEval)actualEval; + assertEquals(expected, nve.getNumberValue(), 0); + } + + private static void confirm(double expectedResult, ValueEval[] args) { + confirmDouble(expectedResult, invokeMaxifs(args)); + } + + /** + * Example 1 from + * https://support.microsoft.com/en-us/office/maxifs-function-dfd611e6-da2c-488a-919b-9b6376b28883 + */ + @Test + void testExample1() { + ValueEval[] a2a7 = new ValueEval[] { + new NumberEval(89), + new NumberEval(93), + new NumberEval(96), + new NumberEval(85), + new NumberEval(91), + new NumberEval(88) + }; + + ValueEval[] b2b7 = new ValueEval[] { + new NumberEval(1), + new NumberEval(2), + new NumberEval(2), + new NumberEval(3), + new NumberEval(1), + new NumberEval(1) + }; + + ValueEval[] args; + // "=MAXIFS(A2:A7,B2:B7,1)" + args = new ValueEval[]{ + EvalFactory.createAreaEval("A2:A7", a2a7), + EvalFactory.createAreaEval("B2:B7", b2b7), + new NumberEval(1) + }; + confirm(91.0, args); + + } + + /** + * Example 2 from + * https://support.microsoft.com/en-us/office/maxifs-function-dfd611e6-da2c-488a-919b-9b6376b28883 + */ + @Test + void testExample2() { + ValueEval[] a2a5 = new ValueEval[] { + new NumberEval(10), + new NumberEval(11), + new NumberEval(100), + new NumberEval(111) + }; + + ValueEval[] b3b6 = new ValueEval[] { + new StringEval("a"), + new StringEval("a"), + new StringEval("b"), + new StringEval("a") + }; + + ValueEval[] args; + + // "=MAXIFS(A2:A5,B3:B6,"a")" + args = new ValueEval[]{ + EvalFactory.createAreaEval("A2:A5", a2a5), + EvalFactory.createAreaEval("B3:B6", b3b6), + new StringEval("a") + }; + confirm(111.0, args); // the support article wrongly states 10.0 + } + +} diff --git a/poi/src/test/java/org/apache/poi/ss/formula/functions/TestMinifs.java b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestMinifs.java new file mode 100644 index 0000000000..4e3f6cfe1f --- /dev/null +++ b/poi/src/test/java/org/apache/poi/ss/formula/functions/TestMinifs.java @@ -0,0 +1,129 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + */ + +package org.apache.poi.ss.formula.functions; + +import static org.apache.poi.ss.util.Utils.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFFormulaEvaluator; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.OperationEvaluationContext; +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.NumericValueEval; +import org.apache.poi.ss.formula.eval.StringEval; +import org.apache.poi.ss.formula.eval.ValueEval; +import org.apache.poi.ss.usermodel.FormulaError; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +/** + * Test cases for MINIFS() + */ +final class TestMinifs { + + private static final OperationEvaluationContext EC = new OperationEvaluationContext(null, null, 0, 1, 0, null); + + private static ValueEval invokeMinifs(ValueEval[] args) { + return new Minifs().evaluate(args, EC); + } + + private static void confirmDouble(double expected, ValueEval actualEval) { + assertTrue(actualEval instanceof NumericValueEval, "Expected numeric result"); + NumericValueEval nve = (NumericValueEval)actualEval; + assertEquals(expected, nve.getNumberValue(), 0); + } + + private static void confirm(double expectedResult, ValueEval[] args) { + confirmDouble(expectedResult, invokeMinifs(args)); + } + + /** + * Example 1 from + * https://support.microsoft.com/en-us/office/minifs-function-6ca1ddaa-079b-4e74-80cc-72eef32e6599 + */ + @Test + void testExample1() { + ValueEval[] a2a7 = new ValueEval[] { + new NumberEval(89), + new NumberEval(93), + new NumberEval(96), + new NumberEval(85), + new NumberEval(91), + new NumberEval(88) + }; + + ValueEval[] b2b7 = new ValueEval[] { + new NumberEval(1), + new NumberEval(2), + new NumberEval(2), + new NumberEval(3), + new NumberEval(1), + new NumberEval(1) + }; + + ValueEval[] args; + // "=MINIFS(A2:A7,B2:B7,1)" + args = new ValueEval[]{ + EvalFactory.createAreaEval("A2:A7", a2a7), + EvalFactory.createAreaEval("B2:B7", b2b7), + new NumberEval(1) + }; + confirm(88.0, args); + + } + + /** + * Example 2 from + * https://support.microsoft.com/en-us/office/minifs-function-6ca1ddaa-079b-4e74-80cc-72eef32e6599 + */ + @Test + void testExample2() { + ValueEval[] a2a5 = new ValueEval[] { + new NumberEval(10), + new NumberEval(11), + new NumberEval(100), + new NumberEval(111) + }; + + ValueEval[] b3b6 = new ValueEval[] { + new StringEval("a"), + new StringEval("a"), + new StringEval("b"), + new StringEval("a") + }; + + ValueEval[] args; + + // "=MINIFS(A2:A5,B3:B6,"a")" + args = new ValueEval[]{ + EvalFactory.createAreaEval("A2:A5", a2a5), + EvalFactory.createAreaEval("B3:B6", b3b6), + new StringEval("a") + }; + confirm(10.0, args); + } + +} -- 2.39.5