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], ...) + *
@@ -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], ...) + *
+ * + * Syntax :
+ * MINIFS ( min_range, criteria_range1, criteria1, + * [criteria_range2, criteria2], ...) + *
* @@ -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); + } + +}