Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rate function added #17

Merged
merged 21 commits into from Jun 6, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
14 changes: 7 additions & 7 deletions README.md
Expand Up @@ -579,7 +579,7 @@ func main() {
## Rate

```go
func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxIter int64, tolerance, initialGuess decimal.Decimal) (decimal.Decimal, bool)
func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxIter int64, tolerance, initialGuess decimal.Decimal) (decimal.Decimal, error)
```
Params:
```text
Expand All @@ -598,7 +598,7 @@ initialGuess : an initial guess amount to start from
Returns:
```text
rate : a value for the corresponding rate
valid : returns true if rate difference is less than the threshold (returns false conversely)
error : returns nil if rate difference is less than the threshold (returns an error conversely)
```

Rate computes the interest rate to ensure a balanced cashflow equation
Expand Down Expand Up @@ -627,14 +627,14 @@ func main() {
tolerance := decimal.NewFromFloat(1e-6)
initialGuess := decimal.NewFromFloat(0.1),

rate, ok := gofinancial.Rate(pv, fv, pmt, nper, when, maxIter, tolerance, initialGuess)
if ok {
fmt.Printf("rate:%v ", rate)
} else {
rate, err := gofinancial.Rate(pv, fv, pmt, nper, when, maxIter, tolerance, initialGuess)
if err != nil {
fmt.Printf("NaN")
thsubaku9 marked this conversation as resolved.
Show resolved Hide resolved
} else {
fmt.Printf("rate:%v ", rate)
}
// Output:
// rate: 0.06106257989825202
}
```
[Run on go-playground](https://play.golang.org/p/9khVcHwjkh5)
[Run on go-playground](https://play.golang.org/p/H2uybe1dbRj)
1 change: 1 addition & 0 deletions error_codes.go
Expand Up @@ -8,4 +8,5 @@ var (
ErrInvalidFrequency = errors.New("invalid frequency")
ErrNotEqual = errors.New("input values are not equal")
ErrOutOfBounds = errors.New("error in representing data as it is out of bounds")
ErrTolerence = errors.New("nan error as tolerence level exceeded")
)
54 changes: 28 additions & 26 deletions reducing_utils.go
Expand Up @@ -292,9 +292,10 @@ func Npv(rate decimal.Decimal, values []decimal.Decimal) decimal.Decimal {
}

/*
This function computs the ratio that is used to find a single value that sets the non-liner equation to zero
This function computes the ratio that is used to find a single value that sets the non-liner equation to zero

Params:

nper : number of compounding periods
pmt : a (fixed) payment, paid either
at the beginning (when = 1) or the end (when = 0) of each period
Expand Down Expand Up @@ -329,43 +330,44 @@ func getRateRatio(pv, fv, pmt, curRate decimal.Decimal, nper int64, when payment

/*
Rate computes the Interest rate per period by running Newton Rapson to find an approximate value for:

y = fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate*((1+rate)**nper-1)
(0 - y_previous) /(rate - rate_previous) = dy/drate {derivative of y w.r.t. rate}

y = fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate*((1+rate)**nper-1)*(0 - y_previous) /(rate - rate_previous) = dy/drate {derivative of y w.r.t. rate}

Params:
nper : number of compounding periods
nper : number of compounding periods
pmt : a (fixed) payment, paid either
thsubaku9 marked this conversation as resolved.
Show resolved Hide resolved
at the beginning (when = 1) or the end (when = 0) of each period
pv : a present value
fv : a future value
at the beginning (when = 1) or the end (when = 0) of each period
pv : a present value
fv : a future value
when : specification of whether payment is made
at the beginning (when = 1) or the end (when = 0) of each period
maxIter : total number of iterations to perform calculation
tolerance : accept result only if the difference in iteration values is less than the tolerance provided
initialGuess : an initial point to start approximating from
at the beginning (when = 1) or the end (when = 0) of each period
maxIter : total number of iterations to perform calculation
tolerance : accept result only if the difference in iteration values is less than the tolerance provided
initialGuess : an initial point to start approximating from

References:
Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). Open Document
Format for Office Applications (OpenDocument)v1.2, Part 2: Recalculated
Formula (OpenFormula) Format - Annotated Version, Pre-Draft 12.
Organization for the Advancement of Structured Information Standards
(OASIS). Billerica, MA, USA. [ODT Document]. Available:
http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula
OpenDocument-formula-20090508.odt
[WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
Open Document Format for Office Applications (OpenDocument)v1.2,
Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
Pre-Draft 12. Organization for the Advancement of Structured Information
Standards (OASIS). Billerica, MA, USA. [ODT Document].
Available:
http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula
OpenDocument-formula-20090508.odt
*/

func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxIter int64, tolerance, initialGuess decimal.Decimal) (decimal.Decimal, bool) {
func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxIter int64, tolerance, initialGuess decimal.Decimal) (decimal.Decimal, error) {
var nextIterRate, currentIterRate decimal.Decimal = initialGuess, initialGuess

for iter := int64(0); iter < maxIter; iter++ {
thsubaku9 marked this conversation as resolved.
Show resolved Hide resolved
currentIterRate = nextIterRate
nextIterRate = currentIterRate.Sub(getRateRatio(pv, fv, pmt, currentIterRate, nper, when))
// skip further loops if |nextIterRate-currentIterRate| < tolerance
if nextIterRate.Sub(currentIterRate).Abs().LessThan(tolerance) {
break
}
}

if nextIterRate.Sub(currentIterRate).Abs().GreaterThan(tolerance) {
return nextIterRate, false
if nextIterRate.Sub(currentIterRate).Abs().GreaterThanOrEqual(tolerance) {
return decimal.Zero, ErrTolerence
}

return nextIterRate, true
return nextIterRate, nil
}
24 changes: 12 additions & 12 deletions reducing_utils_test.go
Expand Up @@ -368,10 +368,10 @@ func Test_Rate(t *testing.T) {
initialGuess decimal.Decimal
}
tests := []struct {
name string
args args
want decimal.Decimal
validity bool
name string
args args
want decimal.Decimal
anyErr error
}{
{
name: "success", args: args{
Expand All @@ -384,8 +384,8 @@ func Test_Rate(t *testing.T) {
tolerance: decimal.NewFromFloat(1e-7),
initialGuess: decimal.NewFromFloat(0.1),
},
want: decimal.NewFromFloat(0.06106257989825202),
validity: true,
want: decimal.NewFromFloat(0.06106257989825202),
anyErr: nil,
}, {
name: "success", args: args{
thsubaku9 marked this conversation as resolved.
Show resolved Hide resolved
pv: decimal.NewFromInt(-3000),
Expand All @@ -397,8 +397,8 @@ func Test_Rate(t *testing.T) {
tolerance: decimal.NewFromFloat(1e-7),
initialGuess: decimal.NewFromFloat(0.1),
},
want: decimal.NewFromFloat(-0.25968757625671507),
validity: true,
want: decimal.NewFromFloat(-0.25968757625671507),
anyErr: nil,
}, {
name: "failure", args: args{
pv: decimal.NewFromInt(3000),
Expand All @@ -410,14 +410,14 @@ func Test_Rate(t *testing.T) {
tolerance: decimal.NewFromFloat(1e-7),
initialGuess: decimal.NewFromFloat(0.1),
},
want: decimal.NewFromFloat(0.4907342754506849),
validity: false,
want: decimal.Zero,
anyErr: ErrTolerence,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got, isValid := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when, tt.args.maxIter, tt.args.tolerance, tt.args.initialGuess); isValid != tt.validity || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil {
t.Errorf("Rate returned (%v,%v), wanted (%v,%v)", got, isValid, tt.want, tt.validity)
if got, err := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when, tt.args.maxIter, tt.args.tolerance, tt.args.initialGuess); err != tt.anyErr || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil {
t.Errorf("Rate returned (%v,%v), wanted (%v,%v)", got, err, tt.want, tt.anyErr)
}
})
}
Expand Down