]> source.dussan.org Git - poi.git/commitdiff
Added implementation for MROUND(), VAR() and VARP()
authorYegor Kozlov <yegor@apache.org>
Sat, 11 Dec 2010 12:41:17 +0000 (12:41 +0000)
committerYegor Kozlov <yegor@apache.org>
Sat, 11 Dec 2010 12:41:17 +0000 (12:41 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1044642 13f79535-47bb-0310-9956-ffa450edef68

src/documentation/content/xdocs/status.xml
src/java/org/apache/poi/ss/formula/atp/AnalysisToolPak.java
src/java/org/apache/poi/ss/formula/atp/MRound.java [new file with mode: 0644]
src/java/org/apache/poi/ss/formula/eval/FunctionEval.java
src/java/org/apache/poi/ss/formula/functions/AggregateFunction.java
src/java/org/apache/poi/ss/formula/functions/NumericFunction.java
src/java/org/apache/poi/ss/formula/functions/StatsLib.java
src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFBugs.java
src/testcases/org/apache/poi/ss/formula/atp/TestMRound.java [new file with mode: 0644]
src/testcases/org/apache/poi/ss/formula/functions/TestStatsLib.java
test-data/spreadsheet/FormulaEvalTestData.xls

index 9ee283fcb039b8ecc7729a5d78d86bfa05da3cf0..a5e46704094385107eccd21580cadc1abe76b957 100644 (file)
@@ -34,6 +34,7 @@
 
     <changes>
         <release version="3.8-beta1" date="2010-??-??">
+           <action dev="POI-DEVELOPERS" type="add">48539 - Added implementation for MROUND(), VAR() and VARP()</action>
            <action dev="POI-DEVELOPERS" type="add">50446 - Code cleanup and optimizations to keep some IDE quiet</action>
            <action dev="POI-DEVELOPERS" type="add">50437 - Support passing ranges to NPV()</action>
            <action dev="POI-DEVELOPERS" type="add">50409 - Added implementation for IRR()</action>
index dca3774f5332d649b2c076c870970bf7a1a491e9..53237caa0404e7fa236de29db9a5905be9fb055f 100644 (file)
@@ -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 (file)
index 0000000..55ed870
--- /dev/null
@@ -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()<br/>
+ *
+ * Returns a number rounded to the desired multiple.<p/>
+ *
+ * <b>Syntax</b><br/>
+ * <b>MROUND</b>(<b>number</b>, <b>multiple</b>)
+ *
+ * <p/>
+ *
+ * @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();
+        }
+       }
+}
index 01fc068fe97d83ab08d4265ed3c95eb1ea472418..a7bc15cee659aee4946ead1b0d043007c908ab7f 100644 (file)
@@ -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;
 
index 7a047dc70ae54183e3125388851f4a917c655010..3c465f17b2e884f7a7632904331fbf13c33a801b 100644 (file)
@@ -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);
+        }
+    };
 }
index 9235850e09b79737b2df98b636c345832523c4a0..623cf65db368a0f8671e6d2a4805321618d263d2 100644 (file)
@@ -43,7 +43,7 @@ public abstract class NumericFunction implements Function {
        /**
         * @throws EvaluationException (#NUM!) if <tt>result</tt> is <tt>NaN</> or <tt>Infinity</tt>
         */
-       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);
                }
index 645f7e43849622861eddc5280a101552dec295c3..ebef9d4b239cb52748e16376d51e3a2f8b773261 100644 (file)
@@ -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;
index 5a48a228eb464439c1321e847ce04162a169f7b4..40cb33fae31e741a1714dd5cac3689bca68f9459 100644 (file)
@@ -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 (file)
index 0000000..5f9acff
--- /dev/null
@@ -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());
+    }
+}
index 47ce2d5a91a8ee673acd63c2a64a44a084339e07..cd0176f22246efca6593ceebbe49ab986f7e8161 100644 (file)
@@ -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);
+    }
 }
index 35d18f700fe7428f6a9f2380c079aee28c33ae17..919bf867e2a7ade7e3a061156ca52160abca0c39 100644 (file)
Binary files a/test-data/spreadsheet/FormulaEvalTestData.xls and b/test-data/spreadsheet/FormulaEvalTestData.xls differ