diff --git a/calc.go b/calc.go index a1a0dcb..ee81dcf 100644 --- a/calc.go +++ b/calc.go @@ -55,6 +55,8 @@ const ( // displays only the leading 15 figures. In the second line, the number one // is added to the fraction, and again Excel displays only 15 figures. const numericPrecision = 1000000000000000 +const maxFinancialIterations = 128 +const financialPercision = 1.0e-08 // cellRef defines the structure of a cell reference. type cellRef struct { @@ -329,6 +331,7 @@ var tokenPriority = map[string]int{ // IMTAN // INT // IPMT +// IRR // ISBLANK // ISERR // ISERROR @@ -359,6 +362,7 @@ var tokenPriority = map[string]int{ // MIDB // MIN // MINA +// MIRR // MOD // MROUND // MULTINOMIAL @@ -7573,6 +7577,76 @@ func (fn *formulaFuncs) ipmt(name string, argsList *list.List) formulaArg { return newNumberFormulaArg(principal) } +// IRR function returns the Internal Rate of Return for a supplied series of +// periodic cash flows (i.e. an initial investment value and a series of net +// income values). The syntax of the function is: +// +// IRR(values,[guess]) +// +func (fn *formulaFuncs) IRR(argsList *list.List) formulaArg { + if argsList.Len() < 1 { + return newErrorFormulaArg(formulaErrorVALUE, "IRR requires at least 1 argument") + } + if argsList.Len() > 2 { + return newErrorFormulaArg(formulaErrorVALUE, "IRR allows at most 2 arguments") + } + values, guess := argsList.Front().Value.(formulaArg).ToList(), newNumberFormulaArg(0.1) + if argsList.Len() > 1 { + if guess = argsList.Back().Value.(formulaArg).ToNumber(); guess.Type != ArgNumber { + return guess + } + } + x1, x2 := newNumberFormulaArg(0), guess + args := list.New().Init() + args.PushBack(x1) + for _, v := range values { + args.PushBack(v) + } + f1 := fn.NPV(args) + args.Front().Value = x2 + f2 := fn.NPV(args) + for i := 0; i < maxFinancialIterations; i++ { + if f1.Number*f2.Number < 0 { + break + } + if math.Abs(f1.Number) < math.Abs((f2.Number)) { + x1.Number += 1.6 * (x1.Number - x2.Number) + args.Front().Value = x1 + f1 = fn.NPV(args) + continue + } + x2.Number += 1.6 * (x2.Number - x1.Number) + args.Front().Value = x2 + f2 = fn.NPV(args) + } + if f1.Number*f2.Number > 0 { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + args.Front().Value = x1 + f := fn.NPV(args) + var rtb, dx, xMid, fMid float64 + if f.Number < 0 { + rtb = x1.Number + dx = x2.Number - x1.Number + } else { + rtb = x2.Number + dx = x1.Number - x2.Number + } + for i := 0; i < maxFinancialIterations; i++ { + dx *= 0.5 + xMid = rtb + dx + args.Front().Value = newNumberFormulaArg(xMid) + fMid = fn.NPV(args).Number + if fMid <= 0 { + rtb = xMid + } + if math.Abs(fMid) < financialPercision || math.Abs(dx) < financialPercision { + break + } + } + return newNumberFormulaArg(xMid) +} + // ISPMT function calculates the interest paid during a specific period of a // loan or investment. The syntax of the function is: // @@ -7609,6 +7683,41 @@ func (fn *formulaFuncs) ISPMT(argsList *list.List) formulaArg { return newNumberFormulaArg(num) } +// MIRR function returns the Modified Internal Rate of Return for a supplied +// series of periodic cash flows (i.e. a set of values, which includes an +// initial investment value and a series of net income values). The syntax of +// the function is: +// +// MIRR(values,finance_rate,reinvest_rate) +// +func (fn *formulaFuncs) MIRR(argsList *list.List) formulaArg { + if argsList.Len() != 3 { + return newErrorFormulaArg(formulaErrorVALUE, "MIRR requires 3 arguments") + } + values := argsList.Front().Value.(formulaArg).ToList() + financeRate := argsList.Front().Next().Value.(formulaArg).ToNumber() + if financeRate.Type != ArgNumber { + return financeRate + } + reinvestRate := argsList.Back().Value.(formulaArg).ToNumber() + if reinvestRate.Type != ArgNumber { + return reinvestRate + } + n, fr, rr, npvPos, npvNeg := len(values), 1+financeRate.Number, 1+reinvestRate.Number, 0.0, 0.0 + for i, v := range values { + val := v.ToNumber() + if val.Number >= 0 { + npvPos += val.Number / math.Pow(float64(rr), float64(i)) + continue + } + npvNeg += val.Number / math.Pow(float64(fr), float64(i)) + } + if npvNeg == 0 || npvPos == 0 || reinvestRate.Number <= -1 { + return newErrorFormulaArg(formulaErrorDIV, formulaErrorDIV) + } + return newNumberFormulaArg(math.Pow(-npvPos*math.Pow(rr, float64(n))/(npvNeg*rr), 1/(float64(n)-1)) - 1) +} + // NOMINAL function returns the nominal interest rate for a given effective // interest rate and number of compounding periods per year. The syntax of // the function is: diff --git a/calc_test.go b/calc_test.go index e80e8b9..a6d5f97 100644 --- a/calc_test.go +++ b/calc_test.go @@ -2518,3 +2518,58 @@ func TestCalcHLOOKUP(t *testing.T) { assert.Equal(t, "", result, formula) } } + +func TestCalcIRR(t *testing.T) { + cellData := [][]interface{}{{-1}, {0.2}, {0.24}, {0.288}, {0.3456}, {0.4147}} + f := prepareCalcData(cellData) + formulaList := map[string]string{ + "=IRR(A1:A4)": "-0.136189509034157", + "=IRR(A1:A6)": "0.130575760006905", + "=IRR(A1:A4,-0.1)": "-0.136189514994621", + } + for formula, expected := range formulaList { + assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula)) + result, err := f.CalcCellValue("Sheet1", "B1") + assert.NoError(t, err, formula) + assert.Equal(t, expected, result, formula) + } + calcError := map[string]string{ + "=IRR()": "IRR requires at least 1 argument", + "=IRR(0,0,0)": "IRR allows at most 2 arguments", + "=IRR(0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=IRR(A2:A3)": "#NUM!", + } + for formula, expected := range calcError { + assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula)) + result, err := f.CalcCellValue("Sheet1", "B1") + assert.EqualError(t, err, expected, formula) + assert.Equal(t, "", result, formula) + } +} + +func TestCalcMIRR(t *testing.T) { + cellData := [][]interface{}{{-100}, {18}, {22.5}, {28}, {35.5}, {45}} + f := prepareCalcData(cellData) + formulaList := map[string]string{ + "=MIRR(A1:A5,0.055,0.05)": "0.025376365108071", + "=MIRR(A1:A6,0.055,0.05)": "0.1000268752662", + } + for formula, expected := range formulaList { + assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula)) + result, err := f.CalcCellValue("Sheet1", "B1") + assert.NoError(t, err, formula) + assert.Equal(t, expected, result, formula) + } + calcError := map[string]string{ + "=MIRR()": "MIRR requires 3 arguments", + "=MIRR(A1:A5,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=MIRR(A1:A5,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=MIRR(B1:B5,0,0)": "#DIV/0!", + } + for formula, expected := range calcError { + assert.NoError(t, f.SetCellFormula("Sheet1", "B1", formula)) + result, err := f.CalcCellValue("Sheet1", "B1") + assert.EqualError(t, err, expected, formula) + assert.Equal(t, "", result, formula) + } +}