--- /dev/null
+/* ====================================================================\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
--- /dev/null
+/* ====================================================================\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