summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorxuri <xuri.me@gmail.com>2021-04-16 14:45:45 +0000
committerxuri <xuri.me@gmail.com>2021-04-16 14:45:45 +0000
commit80d832022f8633b2829eb3586f35866d393f1959 (patch)
treece59f462fc0529892019c3412e12fdcb789b69d1
parent471f4f8d2ba13a7ee50039aef0bc3df16c329818 (diff)
#65 fn: IRR nad MIRR
-rw-r--r--calc.go109
-rw-r--r--calc_test.go55
2 files changed, 164 insertions, 0 deletions
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)
+ }
+}