]> source.dussan.org Git - poi.git/commitdiff
Bug 55024: MIRR Formula implementation review, added error handling and FormulaEvalTe...
authorCédric Walter <cedricwalter@apache.org>
Mon, 4 Nov 2013 22:52:06 +0000 (22:52 +0000)
committerCédric Walter <cedricwalter@apache.org>
Mon, 4 Nov 2013 22:52:06 +0000 (22:52 +0000)
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1538795 13f79535-47bb-0310-9956-ffa450edef68

src/java/org/apache/poi/ss/formula/eval/FunctionEval.java
src/java/org/apache/poi/ss/formula/functions/Mirr.java [new file with mode: 0644]
src/testcases/org/apache/poi/ss/formula/functions/TestMirr.java [new file with mode: 0644]
test-data/spreadsheet/FormulaEvalTestData.xls
test-data/spreadsheet/mirrTest.xls [new file with mode: 0644]

index 9be8a72fb22da8488e4374546c509cc3d8960efb..a343d759b12fbf6456bee12b545935f81af0cca7 100644 (file)
@@ -110,6 +110,8 @@ public final class FunctionEval {
                retval[59] = FinanceFunction.PMT;
 
       retval[60] = new Rate();
+      retval[61] = new Mirr();
+
                retval[62] = new Irr();
                retval[63] = NumericFunction.RAND;
                retval[64] = new Match();
diff --git a/src/java/org/apache/poi/ss/formula/functions/Mirr.java b/src/java/org/apache/poi/ss/formula/functions/Mirr.java
new file mode 100644 (file)
index 0000000..aa9ec58
--- /dev/null
@@ -0,0 +1,109 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+\r
+\r
+package org.apache.poi.ss.formula.functions;\r
+\r
+import org.apache.poi.ss.formula.eval.ErrorEval;\r
+import org.apache.poi.ss.formula.eval.EvaluationException;\r
+\r
+/**\r
+ * Calculates Modified internal rate of return. Syntax is MIRR(cash_flow_values, finance_rate, reinvest_rate)\r
+ *\r
+ * <p>Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both the cost\r
+ * of the investment and the interest received on reinvestment of cash.</p>\r
+ *\r
+ * Values is an array or a reference to cells that contain numbers. These numbers represent a series of payments (negative values) and income (positive values) occurring at regular periods.\r
+ * <ul>\r
+ *     <li>Values must contain at least one positive value and one negative value to calculate the modified internal rate of return. Otherwise, MIRR returns the #DIV/0! error value.</li>\r
+ *     <li>If an array or reference argument contains text, logical values, or empty cells, those values are ignored; however, cells with the value zero are included.</li>\r
+ * </ul>\r
+ *\r
+ * Finance_rate     is the interest rate you pay on the money used in the cash flows.\r
+ * Reinvest_rate     is the interest rate you receive on the cash flows as you reinvest them.\r
+ *\r
+ * @author Carlos Delgado (carlos dot del dot est at gmail dot com)\r
+ * @author Cédric Walter (cedric dot walter at gmail dot com)\r
+ *\r
+ * @see <a href="http://en.wikipedia.org/wiki/MIRR">Wikipedia on MIRR</a>\r
+ * @see <a href="http://office.microsoft.com/en-001/excel-help/mirr-HP005209180.aspx">Excel MIRR</a>\r
+ * @see {@link Irr}\r
+ */\r
+public class Mirr extends MultiOperandNumericFunction {\r
+\r
+    public Mirr() {\r
+        super(false, false);\r
+    }\r
+\r
+    @Override\r
+    protected int getMaxNumOperands() {\r
+        return 3;\r
+    }\r
+\r
+    @Override\r
+    protected double evaluate(double[] values) throws EvaluationException {\r
+\r
+        double financeRate = values[values.length-1];\r
+        double reinvestRate = values[values.length-2];\r
+\r
+        double[] mirrValues = new double[values.length - 2];\r
+        System.arraycopy(values, 0, mirrValues, 0, mirrValues.length);\r
+\r
+        boolean mirrValuesAreAllNegatives = true;\r
+        for (double mirrValue : mirrValues) {\r
+            mirrValuesAreAllNegatives &= mirrValue < 0;\r
+        }\r
+         if (mirrValuesAreAllNegatives) {\r
+             return -1.0d;\r
+         }\r
+\r
+        boolean mirrValuesAreAllPositives = true;\r
+        for (double mirrValue : mirrValues) {\r
+            mirrValuesAreAllPositives &= mirrValue > 0;\r
+        }\r
+        if (mirrValuesAreAllPositives) {\r
+            throw new EvaluationException(ErrorEval.DIV_ZERO);\r
+        }\r
+\r
+        return mirr(mirrValues, financeRate, reinvestRate);\r
+    }\r
+\r
+    private static double mirr(double[] in, double financeRate, double reinvestRate) {\r
+        double value = 0;\r
+        int numOfYears = in.length - 1;\r
+        double pv = 0;\r
+        double fv = 0;\r
+\r
+        int indexN = 0;\r
+        for (double anIn : in) {\r
+            if (anIn < 0) {\r
+                pv += anIn / Math.pow(1 + financeRate + reinvestRate, indexN++);\r
+            }\r
+        }\r
+\r
+        for (double anIn : in) {\r
+            if (anIn > 0) {\r
+                fv += anIn * Math.pow(1 + financeRate, numOfYears - indexN++);\r
+            }\r
+        }\r
+\r
+        if (fv != 0 && pv != 0) {\r
+            value = Math.pow(-fv / pv, 1d / numOfYears) - 1;\r
+        }\r
+        return value;\r
+    }\r
+}\r
diff --git a/src/testcases/org/apache/poi/ss/formula/functions/TestMirr.java b/src/testcases/org/apache/poi/ss/formula/functions/TestMirr.java
new file mode 100644 (file)
index 0000000..6e1963b
--- /dev/null
@@ -0,0 +1,162 @@
+/* ====================================================================\r
+   Licensed to the Apache Software Foundation (ASF) under one or more\r
+   contributor license agreements.  See the NOTICE file distributed with\r
+   this work for additional information regarding copyright ownership.\r
+   The ASF licenses this file to You under the Apache License, Version 2.0\r
+   (the "License"); you may not use this file except in compliance with\r
+   the License.  You may obtain a copy of the License at\r
+\r
+       http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+   Unless required by applicable law or agreed to in writing, software\r
+   distributed under the License is distributed on an "AS IS" BASIS,\r
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+   See the License for the specific language governing permissions and\r
+   limitations under the License.\r
+==================================================================== */\r
+\r
+package org.apache.poi.ss.formula.functions;\r
+\r
+import junit.framework.AssertionFailedError;\r
+import junit.framework.TestCase;\r
+import org.apache.poi.hssf.HSSFTestDataSamples;\r
+import org.apache.poi.hssf.usermodel.*;\r
+import org.apache.poi.ss.formula.eval.ErrorEval;\r
+import org.apache.poi.ss.formula.eval.EvaluationException;\r
+import org.apache.poi.ss.usermodel.CellValue;\r
+\r
+/**\r
+ * Tests for {@link org.apache.poi.ss.formula.functions.Mirr}\r
+ *\r
+ * @author Carlos Delgado (carlos dot del dot est at gmail dot com)\r
+ * @author Cédric Walter (cedric dot walter at gmail dot com)\r
+ * @see {@link org.apache.poi.ss.formula.functions.TestIrr}\r
+ */\r
+public final class TestMirr extends TestCase {\r
+\r
+    public void testMirr() {\r
+        Mirr mirr = new Mirr();\r
+        double mirrValue;\r
+\r
+        double financeRate = 0.12;\r
+        double reinvestRate = 0.1;\r
+        double[] values = {-120000d, 39000d, 30000d, 21000d, 37000d, 46000d, reinvestRate, financeRate};\r
+        try {\r
+            mirrValue = mirr.evaluate(values);\r
+        } catch (EvaluationException e) {\r
+            throw new AssertionFailedError("MIRR should not failed with these parameters" + e);\r
+        }\r
+        assertEquals("mirr", 0.126094130366, mirrValue, 0.0000000001);\r
+\r
+        reinvestRate = 0.05;\r
+        financeRate = 0.08;\r
+        values = new double[]{-7500d, 3000d, 5000d, 1200d, 4000d, reinvestRate, financeRate};\r
+        try {\r
+            mirrValue = mirr.evaluate(values);\r
+        } catch (EvaluationException e) {\r
+            throw new AssertionFailedError("MIRR should not failed with these parameters" + e);\r
+        }\r
+        assertEquals("mirr", 0.18736225093, mirrValue, 0.0000000001);\r
+\r
+        reinvestRate = 0.065;\r
+        financeRate = 0.1;\r
+        values = new double[]{-10000, 3400d, 6500d, 1000d, reinvestRate, financeRate};\r
+        try {\r
+            mirrValue = mirr.evaluate(values);\r
+        } catch (EvaluationException e) {\r
+            throw new AssertionFailedError("MIRR should not failed with these parameters" + e);\r
+        }\r
+        assertEquals("mirr", 0.07039493966, mirrValue, 0.0000000001);\r
+\r
+        reinvestRate = 0.07;\r
+        financeRate = 0.01;\r
+        values = new double[]{-10000d, -3400d, -6500d, -1000d, reinvestRate, financeRate};\r
+        try {\r
+            mirrValue = mirr.evaluate(values);\r
+        } catch (EvaluationException e) {\r
+            throw new AssertionFailedError("MIRR should not failed with these parameters" + e);\r
+        }\r
+        assertEquals("mirr", -1, mirrValue, 0.0);\r
+\r
+    }\r
+\r
+    public void testMirrErrors_expectDIV0() {\r
+        Mirr mirr = new Mirr();\r
+\r
+        double reinvestRate = 0.05;\r
+        double financeRate = 0.08;\r
+        double[] incomes = {120000d, 39000d, 30000d, 21000d, 37000d, 46000d, reinvestRate, financeRate};\r
+        try {\r
+            mirr.evaluate(incomes);\r
+        } catch (EvaluationException e) {\r
+            assertEquals(ErrorEval.DIV_ZERO, e.getErrorEval());\r
+            return;\r
+        }\r
+        throw new AssertionFailedError("MIRR should failed with all these positives values");\r
+    }\r
+\r
+\r
+    public void testEvaluateInSheet() {\r
+        HSSFWorkbook wb = new HSSFWorkbook();\r
+        HSSFSheet sheet = wb.createSheet("Sheet1");\r
+        HSSFRow row = sheet.createRow(0);\r
+\r
+        row.createCell(0).setCellValue(-7500d);\r
+        row.createCell(1).setCellValue(3000d);\r
+        row.createCell(2).setCellValue(5000d);\r
+        row.createCell(3).setCellValue(1200d);\r
+        row.createCell(4).setCellValue(4000d);\r
+\r
+        row.createCell(5).setCellValue(0.05d);\r
+        row.createCell(6).setCellValue(0.08d);\r
+\r
+        HSSFCell cell = row.createCell(7);\r
+        cell.setCellFormula("MIRR(A1:E1, F1, G1)");\r
+\r
+        HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);\r
+        fe.clearAllCachedResultValues();\r
+        fe.evaluateFormulaCell(cell);\r
+        double res = cell.getNumericCellValue();\r
+        assertEquals(0.18736225093, res, 0.00000001);\r
+    }\r
+\r
+    public void testMirrFromSpreadsheet() {\r
+        HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("mirrTest.xls");\r
+        HSSFSheet sheet = wb.getSheet("Mirr");\r
+        HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);\r
+        StringBuilder failures = new StringBuilder();\r
+        int failureCount = 0;\r
+        int[] resultRows = {9, 19, 29, 45};\r
+\r
+        for (int rowNum : resultRows) {\r
+            HSSFRow row = sheet.getRow(rowNum);\r
+            HSSFCell cellA = row.getCell(0);\r
+            try {\r
+                CellValue cv = fe.evaluate(cellA);\r
+                assertFormulaResult(cv, cellA);\r
+            } catch (Throwable e) {\r
+                if (failures.length() > 0) failures.append('\n');\r
+                failures.append("Row[").append(cellA.getRowIndex() + 1).append("]: ").append(cellA.getCellFormula()).append(" ");\r
+                failures.append(e.getMessage());\r
+                failureCount++;\r
+            }\r
+        }\r
+\r
+        HSSFRow row = sheet.getRow(37);\r
+        HSSFCell cellA = row.getCell(0);\r
+        CellValue cv = fe.evaluate(cellA);\r
+        assertEquals(ErrorEval.DIV_ZERO.getErrorCode(), cv.getErrorValue());\r
+\r
+        if (failures.length() > 0) {\r
+            throw new AssertionFailedError(failureCount + " IRR assertions failed:\n" + failures.toString());\r
+        }\r
+\r
+    }\r
+\r
+    private static void assertFormulaResult(CellValue cv, HSSFCell cell) {\r
+        double actualValue = cv.getNumberValue();\r
+        double expectedValue = cell.getNumericCellValue(); // cached formula result calculated by Excel\r
+        assertEquals("Invalid formula result: " + cv.toString(), HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType());\r
+        assertEquals(expectedValue, actualValue, 1E-8);\r
+    }\r
+}\r
index afe3af10a0bcfb94cba54b3e11a127227e2b78ee..29924ae6d0cb0f6a0ac44bfd00eda53e19dc9665 100644 (file)
Binary files a/test-data/spreadsheet/FormulaEvalTestData.xls and b/test-data/spreadsheet/FormulaEvalTestData.xls differ
diff --git a/test-data/spreadsheet/mirrTest.xls b/test-data/spreadsheet/mirrTest.xls
new file mode 100644 (file)
index 0000000..a8a0b0b
Binary files /dev/null and b/test-data/spreadsheet/mirrTest.xls differ