From d8c54be1aa1fecf51b5f18d7efb2054ff59cd505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Walter?= Date: Mon, 4 Nov 2013 22:52:06 +0000 Subject: [PATCH] Bug 55024: MIRR Formula implementation review, added error handling and FormulaEvalTest.xls git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1538795 13f79535-47bb-0310-9956-ffa450edef68 --- .../poi/ss/formula/eval/FunctionEval.java | 2 + .../apache/poi/ss/formula/functions/Mirr.java | 109 ++++++++++++ .../poi/ss/formula/functions/TestMirr.java | 162 ++++++++++++++++++ test-data/spreadsheet/FormulaEvalTestData.xls | Bin 174592 -> 175616 bytes test-data/spreadsheet/mirrTest.xls | Bin 0 -> 28160 bytes 5 files changed, 273 insertions(+) create mode 100644 src/java/org/apache/poi/ss/formula/functions/Mirr.java create mode 100644 src/testcases/org/apache/poi/ss/formula/functions/TestMirr.java create mode 100644 test-data/spreadsheet/mirrTest.xls 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 9be8a72fb..a343d759b 100644 --- a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java +++ b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java @@ -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 index 000000000..aa9ec5892 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/functions/Mirr.java @@ -0,0 +1,109 @@ +/* ==================================================================== + 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.EvaluationException; + +/** + * Calculates Modified internal rate of return. Syntax is MIRR(cash_flow_values, finance_rate, reinvest_rate) + * + *

Returns the modified internal rate of return for a series of periodic cash flows. MIRR considers both the cost + * of the investment and the interest received on reinvestment of cash.

+ * + * 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. + * + * + * Finance_rate is the interest rate you pay on the money used in the cash flows. + * Reinvest_rate is the interest rate you receive on the cash flows as you reinvest them. + * + * @author Carlos Delgado (carlos dot del dot est at gmail dot com) + * @author Cédric Walter (cedric dot walter at gmail dot com) + * + * @see Wikipedia on MIRR + * @see Excel MIRR + * @see {@link Irr} + */ +public class Mirr extends MultiOperandNumericFunction { + + public Mirr() { + super(false, false); + } + + @Override + protected int getMaxNumOperands() { + return 3; + } + + @Override + protected double evaluate(double[] values) throws EvaluationException { + + double financeRate = values[values.length-1]; + double reinvestRate = values[values.length-2]; + + double[] mirrValues = new double[values.length - 2]; + System.arraycopy(values, 0, mirrValues, 0, mirrValues.length); + + boolean mirrValuesAreAllNegatives = true; + for (double mirrValue : mirrValues) { + mirrValuesAreAllNegatives &= mirrValue < 0; + } + if (mirrValuesAreAllNegatives) { + return -1.0d; + } + + boolean mirrValuesAreAllPositives = true; + for (double mirrValue : mirrValues) { + mirrValuesAreAllPositives &= mirrValue > 0; + } + if (mirrValuesAreAllPositives) { + throw new EvaluationException(ErrorEval.DIV_ZERO); + } + + return mirr(mirrValues, financeRate, reinvestRate); + } + + private static double mirr(double[] in, double financeRate, double reinvestRate) { + double value = 0; + int numOfYears = in.length - 1; + double pv = 0; + double fv = 0; + + int indexN = 0; + for (double anIn : in) { + if (anIn < 0) { + pv += anIn / Math.pow(1 + financeRate + reinvestRate, indexN++); + } + } + + for (double anIn : in) { + if (anIn > 0) { + fv += anIn * Math.pow(1 + financeRate, numOfYears - indexN++); + } + } + + if (fv != 0 && pv != 0) { + value = Math.pow(-fv / pv, 1d / numOfYears) - 1; + } + return value; + } +} 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 index 000000000..6e1963b69 --- /dev/null +++ b/src/testcases/org/apache/poi/ss/formula/functions/TestMirr.java @@ -0,0 +1,162 @@ +/* ==================================================================== + 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 junit.framework.AssertionFailedError; +import junit.framework.TestCase; +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.EvaluationException; +import org.apache.poi.ss.usermodel.CellValue; + +/** + * Tests for {@link org.apache.poi.ss.formula.functions.Mirr} + * + * @author Carlos Delgado (carlos dot del dot est at gmail dot com) + * @author Cédric Walter (cedric dot walter at gmail dot com) + * @see {@link org.apache.poi.ss.formula.functions.TestIrr} + */ +public final class TestMirr extends TestCase { + + public void testMirr() { + Mirr mirr = new Mirr(); + double mirrValue; + + double financeRate = 0.12; + double reinvestRate = 0.1; + double[] values = {-120000d, 39000d, 30000d, 21000d, 37000d, 46000d, reinvestRate, financeRate}; + try { + mirrValue = mirr.evaluate(values); + } catch (EvaluationException e) { + throw new AssertionFailedError("MIRR should not failed with these parameters" + e); + } + assertEquals("mirr", 0.126094130366, mirrValue, 0.0000000001); + + reinvestRate = 0.05; + financeRate = 0.08; + values = new double[]{-7500d, 3000d, 5000d, 1200d, 4000d, reinvestRate, financeRate}; + try { + mirrValue = mirr.evaluate(values); + } catch (EvaluationException e) { + throw new AssertionFailedError("MIRR should not failed with these parameters" + e); + } + assertEquals("mirr", 0.18736225093, mirrValue, 0.0000000001); + + reinvestRate = 0.065; + financeRate = 0.1; + values = new double[]{-10000, 3400d, 6500d, 1000d, reinvestRate, financeRate}; + try { + mirrValue = mirr.evaluate(values); + } catch (EvaluationException e) { + throw new AssertionFailedError("MIRR should not failed with these parameters" + e); + } + assertEquals("mirr", 0.07039493966, mirrValue, 0.0000000001); + + reinvestRate = 0.07; + financeRate = 0.01; + values = new double[]{-10000d, -3400d, -6500d, -1000d, reinvestRate, financeRate}; + try { + mirrValue = mirr.evaluate(values); + } catch (EvaluationException e) { + throw new AssertionFailedError("MIRR should not failed with these parameters" + e); + } + assertEquals("mirr", -1, mirrValue, 0.0); + + } + + public void testMirrErrors_expectDIV0() { + Mirr mirr = new Mirr(); + + double reinvestRate = 0.05; + double financeRate = 0.08; + double[] incomes = {120000d, 39000d, 30000d, 21000d, 37000d, 46000d, reinvestRate, financeRate}; + try { + mirr.evaluate(incomes); + } catch (EvaluationException e) { + assertEquals(ErrorEval.DIV_ZERO, e.getErrorEval()); + return; + } + throw new AssertionFailedError("MIRR should failed with all these positives values"); + } + + + public void testEvaluateInSheet() { + HSSFWorkbook wb = new HSSFWorkbook(); + HSSFSheet sheet = wb.createSheet("Sheet1"); + HSSFRow row = sheet.createRow(0); + + row.createCell(0).setCellValue(-7500d); + row.createCell(1).setCellValue(3000d); + row.createCell(2).setCellValue(5000d); + row.createCell(3).setCellValue(1200d); + row.createCell(4).setCellValue(4000d); + + row.createCell(5).setCellValue(0.05d); + row.createCell(6).setCellValue(0.08d); + + HSSFCell cell = row.createCell(7); + cell.setCellFormula("MIRR(A1:E1, F1, G1)"); + + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + fe.clearAllCachedResultValues(); + fe.evaluateFormulaCell(cell); + double res = cell.getNumericCellValue(); + assertEquals(0.18736225093, res, 0.00000001); + } + + public void testMirrFromSpreadsheet() { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("mirrTest.xls"); + HSSFSheet sheet = wb.getSheet("Mirr"); + HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb); + StringBuilder failures = new StringBuilder(); + int failureCount = 0; + int[] resultRows = {9, 19, 29, 45}; + + for (int rowNum : resultRows) { + HSSFRow row = sheet.getRow(rowNum); + HSSFCell cellA = row.getCell(0); + try { + CellValue cv = fe.evaluate(cellA); + assertFormulaResult(cv, cellA); + } catch (Throwable e) { + if (failures.length() > 0) failures.append('\n'); + failures.append("Row[").append(cellA.getRowIndex() + 1).append("]: ").append(cellA.getCellFormula()).append(" "); + failures.append(e.getMessage()); + failureCount++; + } + } + + HSSFRow row = sheet.getRow(37); + HSSFCell cellA = row.getCell(0); + CellValue cv = fe.evaluate(cellA); + assertEquals(ErrorEval.DIV_ZERO.getErrorCode(), cv.getErrorValue()); + + if (failures.length() > 0) { + throw new AssertionFailedError(failureCount + " IRR assertions failed:\n" + failures.toString()); + } + + } + + private static void assertFormulaResult(CellValue cv, HSSFCell cell) { + double actualValue = cv.getNumberValue(); + double expectedValue = cell.getNumericCellValue(); // cached formula result calculated by Excel + assertEquals("Invalid formula result: " + cv.toString(), HSSFCell.CELL_TYPE_NUMERIC, cv.getCellType()); + assertEquals(expectedValue, actualValue, 1E-8); + } +} diff --git a/test-data/spreadsheet/FormulaEvalTestData.xls b/test-data/spreadsheet/FormulaEvalTestData.xls index afe3af10a0bcfb94cba54b3e11a127227e2b78ee..29924ae6d0cb0f6a0ac44bfd00eda53e19dc9665 100644 GIT binary patch delta 4671 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 E0rieY#sB~S delta 4139 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 GwEqFOg}R&o diff --git a/test-data/spreadsheet/mirrTest.xls b/test-data/spreadsheet/mirrTest.xls new file mode 100644 index 0000000000000000000000000000000000000000..a8a0b0b12b89bad42b04b11c78eacb20e875b34e GIT binary patch literal 28160 zcmeHQ4RBo5bv|#kl2(?nB-_~d2YD@7wk*p=mV|*CTax7;TqN7I#Xtr#W-YDk9Sg0x zS`m}tTBKzrQz!wMm?=07X+rr8|B1Am{;?Qovy{mvsVb>(z%4vq zsPnY~d@9AP{_VswK&A9+q=Bjs3|UbwhuUAP{HsYzDq!#*OF$kEK99S|o4EGN82w-{ zI+TbFhCiFMMadAcD8n=yZAd&<**f#*}INk zcrhpl>RuJ-R^|Rs-5b<>4(^tmR&`!iko8h8XHY&Qx6|DN^2JK4S0XYZF*zs++<(?=p`tR>FC-u^Vs zNKj@gIs7Eu>?>O``vADClhyb`RcF1Hu(MK2*j1xT zTIDy~%+L!e9k6ee>JSO?yI+)aFaaHrf4NqtG=lO&he zZ_5L+L97F4aSJry)oS8wo%ol~G#XI8!J$vPKJVSadwuYTJSZLcm>z%;>?$-vF3T*Q zrHIcmbmKI;o8%$c?8VMhM?SXR<=pJM5LB{0WK-f@3-LShFtq5Q=q&N9^tmnfD&;Le zmWq@8x$=l$M$+)P=y=1%^nF*np+2}+z9r`ZF_4&u0+asP9vK;r0y|$Gl^S4oj{;}k zhKB*aMjk`|u&$OBu-00{4Ens&eQbFTAS7%Qr#Chu=U-<(>eL6TalXdd`IdEk%bfj^Q5{?$D2Tk^p3$&-)%Q;H6kn94shdidi!a9f_-^0qv= z@Ned!^F$u_(|O>3o(KL1dEnp8120ydZ!0>LIr_2v1fj&%+nsfFcU9k2Ey0tjJR(XO zK0N%~vgejb@U()LBeE+!{}&3Vr2X^3Jn)C|z#s60BW~GnBc335dBPFEYSH)_@!GV0iJAr%uB&`n3DYTn&F&uEpbGO@E$- zXXJl4t>-lTQ+0I?fC`EB# zm?^3P<3mvu7&wZmz=%>*1%{fUDli5WRe{0Dw?dE69~hm=^7jYsqLfK=m@cPhsK-Jk z5r(n?h|mr56JgvdfCybPKM@AW0*KIE^Alm@EPx1II6o1F(*lUlt@9IM>^&=ynLdB( zFi;mj#NRsS6-LD0I*SS;;%^-ch=nxA-#SYQBjRrzY(zOELB>6n*nOwH!{0jCs49er zzjf*hBjRrzjO>L-;%}XBVMP3`v%D}O{?=(Igoto8?b87p3nSuhofU-<@wZM>VMP3` zv$8NE{?=Jl7!iN#G-rr#zH~||Qm%Oz`wkJQ7hXzb!{Z&s;I$ehD%Ie{vcEVc-b6n5 z;DZd2$~58Q$B(D%9nN)_c-qVevv;cwU{0Ea*?U#pn0IDj;HT2DkSSCP;I>el*s0{4 zVd|FMci(+ELUna@5xK%z1Kau@%-9TVY+X6)wnBptWf% zm&@cfx-CaybV`#5UKBXOsPX}){poB3`p2D) zLWrcg{E2iHLc|=OOh@1w_^jvTG$&W<8(bAQ3pccGc9@G$BHN3*x~})udTQp6ayrbX z*NaUn@-?nKY4Qv$I%K3Kt&C8)^WsZfL{3HX_*3egQw}1gdhjmD5l?bftlBA4shJEB z9ps*T^2wZdLYNs(2s7geVc@J%ZjjpuxYK5cJx(XrLU~2r|Ggl0DTKwW6|7I4gEuX0(j;NLSPDyz z4B{^;qmtxXP*}vm+l$Izbok8M@2AT7tn?+RCT{tVO`XZ6J(o>`4;wq?D>miP;SbLI zgEyPGCYz32HYC9!*;={%sHx!%7=3MZeb_iXZKm(-XHI*wInQL{;c2UU*f>3Hrtk0Hde)oGB9o1W zr>*f}rUHX91?wDyj#JoT#025!!SfSH(fzRAYJ(^mMfae7*N$LlBW^k!3Uvhnb= zW*;_APiyb^%UAF5X0yy>RB)Z%n z>hvPK$=cKJO>{+;DE!AH+GG%Q`i#gufA%?VqD@(%@D`KkYJ;fLLqsNr{=}Q;$}Ca% zhDmg-LDcCFB9}k%eQ%|KWy9E?Y{N++U1#lN z5Or1>b%Lo7>P+Cwa6-*}2jzxLjdhcisIktdQJI>ZQ^U>cT)BX!qqZF#8;OiYlJUe1 z;eqJUB# z$*R;`*$#ctTygn4hzx<)Wgt0*=LDcJFglVUTL~YXlu1pt3R8dVj_ioX;r|=mw5`Rd zpzHwExX}Z+v7<#q8#JvsSYBW^^Rj4UC^mX1j3tQC{<2ya>@tYOE=92pK~#I`p*#!y zaM{jdHaU8DB_y-s>xsY|9Qkt)M@Th^*Le`P!)hMlsjmB<OzOUu^V9bSNF0Ob0&? zF|#l2#I|#1qf=R?r)1)!j*Q%Ks(|)q%O#Lsc10(Wi3mcUF=$vzF=*_9OR}X2I3|Oh zy$n*Z9$QYjy{E7*mFbM=L$E=ITXB|vQy+n4AF4CiKMdl=MARk zg7j{sgIt?Cha*OBm(2;tN@C}0yc$`&aSRJd*ZpmHkL-Us!r zgOS}@ouiA@;q2~gkt-vKQN}$x^mn-h4OC+kxe^=_=vO-Qd4+sVFqDRG3tc6*IW}-^ zN9*eFHvl3BRcrS_=y|eZWe6;ip2afe_1#Js| z&s!l}BClK=kdI`J)eIbT#%{1Rl8hh-Y>kc&CSqfpI03P9G?t7-M#6*f@g$B-?2RTT z5~JbaSYkXGz9AY(%+0{Yql59$AqC79OdgIUY*@D7aC{<>JZuykjt!?_cuCeXIywQK zvC(8S5e1_}BpD44#}nb?;b?e$ds{0G5Qj(Nkx{&2@hzK(#zwCP1q8LRM0_}w97j9* zckbOg-@9T8A4Y}br-=E~$v9@iC-FNNT7U}=>9ipI8<3OG^hpP1%fDE14`BB=V6@Dw zfZghVkpKq*`}49|*XL!`0iIp36W=-Qz~1$T-LNb?;vDoV#-RYbtVTW8;Vzi;gyf?K z-~ZeGgL}FTD0qv4uTl3eD##?t=+uPXFVbq=1kCd|9WFdOF2AQr|Gv7fMCoCb-tOX; zC;GWLL**x3s~yk%@eD9oXBmb3pb)#-fRRu`fiWZ`;7Yw!Mhhi)u!@MR3MhVSwJgs8Fr~ng&1yT--BZ^rL8>=}m>cA{#ACq*qmzpulVc^YyVc^Yy z&GUe91Tg9GD4q%9=w!lpP|k$$+?xsGF*Xy%Q)wpbd=J<%1NN~RLDhkFVC)=63OdE_ zVyeks=&kSLY2xT-95HPie}Sw1=WgN{p=-GLr0VgP_5M-exCE);>`3AG3)~l9RyfY6 zYB-@)IQ{}x_ol*eyVUYhWYy2!LlFY3mO0krK05Ss4UBy25DyVoe)Q8IcZ zTl>9f;$|zH))KuFxUu^c_j43ZYl&V8+?SqGxH$@^wFEeE(^CpZi)6J#uLZ9AF@>vD zIBJKo(0d`Z%vZQNg~NnY;poN0g%xhD!f8#=n|q}D=<+Yz_~8rC1mG!XVm9}Jrqc=l zZCeu{q%|>5QQr-VwyU^=cUMLozVM%yZs?0$9W2;b;4c%+|YLw zZjr)iov=5s-CtI?#R{iAhrMD8Ad3}liNfi2vv&|@dk4N*1r^vSUZ}poNH-U>?RJBZZZ|$$1r@fNT4%pftySEYDI8n-Di+5#Lc~yDDZZ6;|8397 z&vHSwt0)}?6|-kA_kcBcz#2VZD?DIL9>>*WC(u ziB{x%{tOEg-uPl3Kw4~9LB{>ORoR9wm*uC>;6exJW`_f18&DN_Xkena_$Fk-QROv zWGFf@u3lCN4h4}OQ@-DNFyfk@o2finq&6q)Gez0Q4w;Q&WW{XeO@ zt^ssXN+k`HG*Hq&NdqMflr&J%KuH574U{xc(m+WA|A#febvzfwTmkc4KG)GauE=-% zT#s|D&x1N#G;__)_xb6gk!p?4BaM9L&vm~3%z|ru?g#KFA&(65XdsUO@~9t=^l=f+ zLwR9b%W*Z}YQ)9=N2&?GD{=7Xb% zx0DLgz+Qa5#V^vr_*|Gr^ZAKzF=>I6s{>u+Mn4LNc<13WmA2bI_WkjxAFg~9XEbOZ z4ZjlH6ZmTQppxT=1{eH`{{bq)pE49$@fI9sVk*a{3jCOj-+;62{2Fyw$wWx@qj|e@ t?!6iO*@xJF3gy3oWUwK{RF30&aeNF<8SH-{sW^Eq{vWg-=KwSO{~MKO!`1)* literal 0 HcmV?d00001