From 72ce4e8eb7b9ab59e3393661d90b99a8b0b2c5ae Mon Sep 17 00:00:00 2001 From: Yegor Kozlov Date: Sat, 11 Dec 2010 12:41:17 +0000 Subject: [PATCH] Added implementation for MROUND(), VAR() and VARP() git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1044642 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 1 + .../poi/ss/formula/atp/AnalysisToolPak.java | 2 +- .../org/apache/poi/ss/formula/atp/MRound.java | 76 ++++++++++++++++++ .../poi/ss/formula/eval/FunctionEval.java | 3 + .../formula/functions/AggregateFunction.java | 16 ++++ .../ss/formula/functions/NumericFunction.java | 2 +- .../poi/ss/formula/functions/StatsLib.java | 15 ++++ .../poi/xssf/usermodel/TestXSSFBugs.java | 27 ++++--- .../apache/poi/ss/formula/atp/TestMRound.java | 71 ++++++++++++++++ .../ss/formula/functions/TestStatsLib.java | 50 ++++++++++++ test-data/spreadsheet/FormulaEvalTestData.xls | Bin 158720 -> 160768 bytes 11 files changed, 248 insertions(+), 15 deletions(-) create mode 100644 src/java/org/apache/poi/ss/formula/atp/MRound.java create mode 100644 src/testcases/org/apache/poi/ss/formula/atp/TestMRound.java diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 9ee283fcb0..a5e4670409 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 48539 - Added implementation for MROUND(), VAR() and VARP() 50446 - Code cleanup and optimizations to keep some IDE quiet 50437 - Support passing ranges to NPV() 50409 - Added implementation for IRR() diff --git a/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java b/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java index dca3774f53..53237caa04 100644 --- a/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java +++ b/src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java @@ -138,7 +138,7 @@ public final class AnalysisToolPak implements UDFFinder { r(m, "JIS", null); r(m, "LCM", null); r(m, "MDURATION", null); - r(m, "MROUND", null); + r(m, "MROUND", MRound.instance); r(m, "MULTINOMIAL", null); r(m, "NETWORKDAYS", null); r(m, "NOMINAL", null); diff --git a/src/java/org/apache/poi/ss/formula/atp/MRound.java b/src/java/org/apache/poi/ss/formula/atp/MRound.java new file mode 100644 index 0000000000..55ed87084c --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/atp/MRound.java @@ -0,0 +1,76 @@ +/* ==================================================================== + 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.atp; + +import org.apache.poi.ss.formula.OperationEvaluationContext; +import org.apache.poi.ss.formula.eval.*; +import org.apache.poi.ss.formula.functions.FreeRefFunction; +import org.apache.poi.ss.formula.functions.NumericFunction; +import org.apache.poi.ss.usermodel.DateUtil; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.regex.Pattern; + +/** + * Implementation of Excel 'Analysis ToolPak' function MROUND()
+ * + * Returns a number rounded to the desired multiple.

+ * + * Syntax
+ * MROUND(number, multiple) + * + *

+ * + * @author Yegor Kozlov + */ +final class MRound implements FreeRefFunction { + + public static final FreeRefFunction instance = new MRound(); + + private MRound() { + // enforce singleton + } + + public ValueEval evaluate(ValueEval[] args, OperationEvaluationContext ec) { + double number, multiple, result; + + if (args.length != 2) { + return ErrorEval.VALUE_INVALID; + } + + try { + number = OperandResolver.coerceValueToDouble(OperandResolver.getSingleValue(args[0], ec.getRowIndex(), ec.getColumnIndex())); + multiple = OperandResolver.coerceValueToDouble(OperandResolver.getSingleValue(args[1], ec.getRowIndex(), ec.getColumnIndex())); + + if( multiple == 0.0 ) { + result = 0.0; + } else { + if(number*multiple < 0) { + // Returns #NUM! because the number and the multiple have different signs + throw new EvaluationException(ErrorEval.NUM_ERROR); + } + result = multiple * Math.round( number / multiple ); + } + NumericFunction.checkValue(result); + return new NumberEval(result); + } catch (EvaluationException e) { + return e.getErrorEval(); + } + } +} 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 01fc068fe9..a7bc15cee6 100644 --- a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java +++ b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java @@ -93,6 +93,8 @@ public final class FunctionEval { retval[37] = BooleanFunction.OR; retval[38] = BooleanFunction.NOT; retval[39] = NumericFunction.MOD; + + retval[46] = AggregateFunction.VAR; retval[48] = TextFunction.TEXT; retval[56] = FinanceFunction.PV; @@ -153,6 +155,7 @@ public final class FunctionEval { retval[184] = NumericFunction.FACT; retval[190] = LogicalFunction.ISNONTEXT; + retval[194] = AggregateFunction.VARP; retval[197] = NumericFunction.TRUNC; retval[198] = LogicalFunction.ISLOGICAL; 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 7a047dc70a..3c465f17b2 100644 --- a/src/java/org/apache/poi/ss/formula/functions/AggregateFunction.java +++ b/src/java/org/apache/poi/ss/formula/functions/AggregateFunction.java @@ -142,4 +142,20 @@ public abstract class AggregateFunction extends MultiOperandNumericFunction { return MathX.sumsq(values); } }; + public static final Function VAR = new AggregateFunction() { + protected double evaluate(double[] values) throws EvaluationException { + if (values.length < 1) { + throw new EvaluationException(ErrorEval.DIV_ZERO); + } + return StatsLib.var(values); + } + }; + public static final Function VARP = new AggregateFunction() { + protected double evaluate(double[] values) throws EvaluationException { + if (values.length < 1) { + throw new EvaluationException(ErrorEval.DIV_ZERO); + } + return StatsLib.varp(values); + } + }; } diff --git a/src/java/org/apache/poi/ss/formula/functions/NumericFunction.java b/src/java/org/apache/poi/ss/formula/functions/NumericFunction.java index 9235850e09..623cf65db3 100644 --- a/src/java/org/apache/poi/ss/formula/functions/NumericFunction.java +++ b/src/java/org/apache/poi/ss/formula/functions/NumericFunction.java @@ -43,7 +43,7 @@ public abstract class NumericFunction implements Function { /** * @throws EvaluationException (#NUM!) if result is NaN or Infinity */ - static final void checkValue(double result) throws EvaluationException { + public static final void checkValue(double result) throws EvaluationException { if (Double.isNaN(result) || Double.isInfinite(result)) { throw new EvaluationException(ErrorEval.NUM_ERROR); } diff --git a/src/java/org/apache/poi/ss/formula/functions/StatsLib.java b/src/java/org/apache/poi/ss/formula/functions/StatsLib.java index 645f7e4384..ebef9d4b23 100644 --- a/src/java/org/apache/poi/ss/formula/functions/StatsLib.java +++ b/src/java/org/apache/poi/ss/formula/functions/StatsLib.java @@ -59,6 +59,21 @@ final class StatsLib { return r; } + public static double var(double[] v) { + double r = Double.NaN; + if (v!=null && v.length > 1) { + r = devsq(v) / (v.length - 1); + } + return r; + } + + public static double varp(double[] v) { + double r = Double.NaN; + if (v!=null && v.length > 1) { + r = devsq(v) /v.length; + } + return r; + } public static double median(double[] v) { double r = Double.NaN; diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java index 5a48a228eb..40cb33fae3 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java @@ -26,16 +26,8 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.openxml4j.opc.OPCPackage; import org.apache.poi.openxml4j.opc.PackagePart; import org.apache.poi.openxml4j.opc.PackagingURIHelper; -import org.apache.poi.ss.usermodel.BaseTestBugzillaIssues; -import org.apache.poi.ss.usermodel.Cell; -import org.apache.poi.ss.usermodel.DataFormatter; -import org.apache.poi.ss.usermodel.Font; -import org.apache.poi.ss.usermodel.FormulaError; -import org.apache.poi.ss.usermodel.FormulaEvaluator; -import org.apache.poi.ss.usermodel.Name; -import org.apache.poi.ss.usermodel.Row; -import org.apache.poi.ss.usermodel.Sheet; -import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.ss.formula.eval.NotImplementedException; import org.apache.poi.xssf.XSSFITestDataProvider; import org.apache.poi.xssf.XSSFTestDataSamples; import org.apache.poi.xssf.model.CalculationChain; @@ -212,8 +204,10 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues { * NameXPtgs. * Blows up on: * IF(B6= (ROUNDUP(B6,0) + ROUNDDOWN(B6,0))/2, MROUND(B6,2),ROUND(B6,0)) + * + * TODO: delete this test case when MROUND and VAR are implemented */ - public void DISABLEDtest48539() throws Exception { + public void test48539() throws Exception { XSSFWorkbook wb = XSSFTestDataSamples.openSampleWorkbook("48539.xlsx"); assertEquals(3, wb.getNumberOfSheets()); @@ -224,14 +218,21 @@ public final class TestXSSFBugs extends BaseTestBugzillaIssues { for(Row r : s) { for(Cell c : r) { if(c.getCellType() == Cell.CELL_TYPE_FORMULA) { - eval.evaluate(c); + CellValue cv = eval.evaluate(c); + if(cv.getCellType() == Cell.CELL_TYPE_NUMERIC) { + // assert that the calculated value agrees with + // the cached formula result calculated by Excel + double cachedFormulaResult = c.getNumericCellValue(); + double evaluatedFormulaResult = cv.getNumberValue(); + assertEquals(c.getCellFormula(), cachedFormulaResult, evaluatedFormulaResult, 1E-7); + } } } } } // Now all of them - XSSFFormulaEvaluator.evaluateAllFormulaCells(wb); + XSSFFormulaEvaluator.evaluateAllFormulaCells(wb); } /** diff --git a/src/testcases/org/apache/poi/ss/formula/atp/TestMRound.java b/src/testcases/org/apache/poi/ss/formula/atp/TestMRound.java new file mode 100644 index 0000000000..5f9acff9f4 --- /dev/null +++ b/src/testcases/org/apache/poi/ss/formula/atp/TestMRound.java @@ -0,0 +1,71 @@ +/* ==================================================================== + 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.atp; + +import junit.framework.TestCase; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.usermodel.*; + +/** + * Testcase for 'Analysis Toolpak' function MROUND() + * + * @author Yegor Kozlov + */ +public class TestMRound extends TestCase { + + /** +=MROUND(10, 3) Rounds 10 to a nearest multiple of 3 (9) +=MROUND(-10, -3) Rounds -10 to a nearest multiple of -3 (-9) +=MROUND(1.3, 0.2) Rounds 1.3 to a nearest multiple of 0.2 (1.4) +=MROUND(5, -2) Returns an error, because -2 and 5 have different signs (#NUM!) * + */ + public static void testEvaluate(){ + Workbook wb = new HSSFWorkbook(); + Sheet sh = wb.createSheet(); + Cell cell1 = sh.createRow(0).createCell(0); + cell1.setCellFormula("MROUND(10, 3)"); + Cell cell2 = sh.createRow(0).createCell(0); + cell2.setCellFormula("MROUND(-10, -3)"); + Cell cell3 = sh.createRow(0).createCell(0); + cell3.setCellFormula("MROUND(1.3, 0.2)"); + Cell cell4 = sh.createRow(0).createCell(0); + cell4.setCellFormula("MROUND(5, -2)"); + Cell cell5 = sh.createRow(0).createCell(0); + cell5.setCellFormula("MROUND(5, 0)"); + + double accuracy = 1E-9; + + FormulaEvaluator evaluator = wb.getCreationHelper().createFormulaEvaluator(); + + assertEquals("Rounds 10 to a nearest multiple of 3 (9)", + 9.0, evaluator.evaluate(cell1).getNumberValue(), accuracy); + + assertEquals("Rounds -10 to a nearest multiple of -3 (-9)", + -9.0, evaluator.evaluate(cell2).getNumberValue(), accuracy); + + assertEquals("Rounds 1.3 to a nearest multiple of 0.2 (1.4)", + 1.4, evaluator.evaluate(cell3).getNumberValue(), accuracy); + + assertEquals("Returns an error, because -2 and 5 have different signs (#NUM!)", + ErrorEval.NUM_ERROR.getErrorCode(), evaluator.evaluate(cell4).getErrorValue()); + + assertEquals("Returns 0 because the multiple is 0", + 0.0, evaluator.evaluate(cell5).getNumberValue()); + } +} diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestStatsLib.java b/src/testcases/org/apache/poi/ss/formula/functions/TestStatsLib.java index 47ce2d5a91..cd0176f222 100644 --- a/src/testcases/org/apache/poi/ss/formula/functions/TestStatsLib.java +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestStatsLib.java @@ -268,4 +268,54 @@ public class TestStatsLib extends AbstractNumericTestCase { x = 3.02765035410; assertEquals("stdev ", x, d); } + + public void testVar() { + double[] v = null; + double d, x = 0; + + v = new double[] {3.50, 5.00, 7.23, 2.99}; + d = StatsLib.var(v); + x = 3.6178; + assertEquals("var ", x, d); + + v = new double[] {34.5, 2.0, 8.9, -4.0}; + d = StatsLib.var(v); + x = 286.99; + assertEquals("var ", x, d); + + v = new double[] {7.0, 25.0, 21.69}; + d = StatsLib.var(v); + x = 91.79203333; + assertEquals("var ", x, d); + + v = new double[] {1345,1301,1368,1322,1310,1370,1318,1350,1303,1299}; + d = StatsLib.var(v); + x = 754.2666667; + assertEquals("var ", x, d); + } + + public void testVarp() { + double[] v = null; + double d, x = 0; + + v = new double[] {3.50, 5.00, 7.23, 2.99}; + d = StatsLib.varp(v); + x = 2.71335; + assertEquals("varp ", x, d); + + v = new double[] {34.5, 2.0, 8.9, -4.0}; + d = StatsLib.varp(v); + x = 215.2425; + assertEquals("varp ", x, d); + + v = new double[] {7.0, 25.0, 21.69}; + d = StatsLib.varp(v); + x = 61.19468889; + assertEquals("varp ", x, d); + + v = new double[] {1345,1301,1368,1322,1310,1370,1318,1350,1303,1299}; + d = StatsLib.varp(v); + x = 678.84; + assertEquals("varp ", x, d); + } } diff --git a/test-data/spreadsheet/FormulaEvalTestData.xls b/test-data/spreadsheet/FormulaEvalTestData.xls index 35d18f700fe7428f6a9f2380c079aee28c33ae17..919bf867e2a7ade7e3a061156ca52160abca0c39 100644 GIT binary patch delta 23477 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 L;j}r%8T0=ELbt&O -- 2.39.5