CSJoe

Jan 10, 2007, 04:51 PM

I have recently been set a piece of homework that is causing me many a problem.

I need to implement a class named InterestSolver that extends the abstract class InterestSolverDefn. Given any three of the four parameters: yearly interest rate, monthly repayment amount, initial sum borrowed and time of loan in months; these methods will allow the fourth to be computed.

The abstract class Interest SolverDefn follows below followed by the homeowork question

/**

* Abstract class definition for a simple class to compute

* one of the interest rate, monthly repayment amount, initial

* premium and number of months of loan given the other three.

*

* This particular definition prescribes the bisection

* method to solve the resultant non-linear equation for

* obtaining the interest rate. Netwon's method would also be

* a possibility.

**/

public abstract class InterestSolverDefn {

/**

* Mutator for setting the rate of interest

* @param givenRate New rate of interest value

* @throws IllegalArgumentException if parameter not >0 and <100

**/

public abstract void setRate (double givenRate);

/**

* Mutator for setting the monthly repayment amount

* @param givenRepayment the new amount for the monthly repayment

* @throws IllegalArgumentException if parameter <=0

**/

public abstract void setRepayment(double givenRepayment);

/**

* Mutator for setting the number of months in the loan period

* @param givenMonths New length of loan period in months.

* @throws IllegalArgumentException if parameter <=0

**/

public abstract void setMonths(int givenMonths);

/**

* Mutator for setting the amount of the initial loan

* @param givenPremium New amount of initial loan

* @throws IllegalArgumentException if parameter <=0

**/

public abstract void setPremium(double givenPremium);

/**

* Accessor for obtaining the current setting of the interest rate

* @return The current value of the interest rate

**/

public abstract double getRate ();

/**

* Accessor for obtaining the current value of the monthly repayment

* @return The current value of the monthly repayment

**/

public abstract double getRepayment();

/**

* Accessor for obtaining the current loan period in months

* @return The current loan period in months

**/

public abstract int getMonths();

/**

* Accessor for obtaining the current value of the initial loan

* @return The current value of the initial loan

**/

public abstract double getPremium();

/**

* Method to compute the monthly repayment given the rate of interest,

* loan period in months, and value of initial loan

**/

public abstract void computeRepayment();

/**

* Method to compute the value of initial loan given the rate of interest,

* loan period in months, and value of the monthly repayment

**/

public abstract void computePremium();

/**

* Method to compute the loan period in months given the rate of interest,

* the monthly repayment, and value of initial loan

**/

public abstract void computeMonths();

/**

* Method to compute the rate of interest given the monthly repayment,

* loan period in months, and value of initial loan

**/

public abstract void computeRate();

/**

* Implementation of the bisection method to solve the single nonlinear

* equation associated with computing the interest rate given the other

* three values.

* @param a The value of the left hand end of an interval containing

* the root of the function f (see below)

* @param b The value of the right hand end of an interval containing

* the root of the function f (see below)

* a and b must be chosen so that f(a)*f(b)<0. In fact this

* is guaranteed the values are set according to the details

* given in the tutorial sheet. It is sensible to check anyway!

* @param eps Value controlling the accuracy of the final approximation

* to the root. See the description in the tutorial sheet for

* full details on how this should be used.

* @return An approximation to the root of f(J) = 0. This is a simple

* function of the interest rate.

* rate = 1200*(root - 1)

* @throws RuntimeException If f(a)*f(b)>0 on entry or the requested

* accuracy cannot be met

* @throws RuntimeException if size of repayment is too small to cover

* interest, making it impossible to pay off the loan

**/

protected abstract double bisection(double a, double b, double eps);

/**

* Function defining the non-linear equation being used by the bisection

* method to compute the interest rate. This should be the equation

* given in the assessment sheet that is obtained by using the

* geometric sum and clearing the (1-J) denominator to generate a

* simple (m+1) degree polynomial.

* @param J Value at which the function is to be evaluated

* @return The value of f(J)

**/

protected abstract double f(double J);

}

Theory

An amount £P is borrowed at an annual rate of interest of I%. At the end of each month £R is repaid and the whole debt is to be paid off in m months.

We assume that each month is considered to be 1/12 of a year and that the interest is calculated for each month of the loan on the outstanding amount at the start of each month.

If the amount owed at the end of month 1 is denoted by P1 we have

P1 = (P + I * P/1200) − R

= P(1 + I/1200) − R

Similarly at the end of month 2 the amount owed is given by

P2 = P1(1 + I/1200) − R

= (PJ − R)(J) − R

= PJ^2 − RJ − R

where J = (1 + I/1200).

Continuing in the same way we get

P3 = PJ^3 − RJ^2 − RJ − R

and in general

Pm = PJ^m − RJ^m−1 − RJ^m−2 − ... − R

= PJ^m − R(1 + J + J^2 + ...+ J^m−1)

The term in the final brackets is a geometric sum and may be written as

1 − J^m

--------------

1 − J

Note: This is only valid if J != 1 which is alright because this means that I = 0 which is a trivial case we probably aren’t interested in. However, keep this in mind in what follows.

Thus

Pm = PJ^m − R(1 − J^m)

----------------

1 − J

and if we require the loan to be paid off after m months then

Pm = 0 or PJm = R(1 − Jm)

--------------

1 − J

This gives us a way of computing P and R given the other three quantities:

P = R(1 − J^m)

-------------

J^m(1 − J)

R = PJ^m(1 − J)

---------------

1 − J^m

By rearranging we can also isolate m

R − RJ^m = PJ^m(1 − J)

R = J^m(P(1 − J) + R)

Jm = R

------------------

P(1 − J) + R

mlog J = logR − log(P(1 − J) + R)

m = logR − log(P(1 − J) + R)

-------------------------------

log J

Finally we need to be able to compute J (and hence I). Before you start trying to compute I what condition needs to hold between the other three quantities to ensure that the calculationmakes sense?

In what follows we make things as simple as possible so that the function we have to deal with is easy to compute. But note that we have multiplied through by (1 − J) and this leads to a spurious root at 1. This makes us have to think just a little bit later on.

R − RJ^m = PJ^m − PJ^m+1

PJ^m+1 − (R + P)J^m + R = 0

and to compute J requires the solution of the single nonlinear equation

f(J) ≡ PJm+1 − (R + P)Jm + R = 0

Implementation

The assessment requires you to solve this equation using the bisection method. To do this successfully you need to be able to generate two values JL and JR so that the interval

JL ≤ J ≤ JR contains the required root. If the value of I is known to be greater than zero and less than 100, then we know that the value of J is constrained to be in the range

1 < J ≤ 1 1/12 . Thus the ‘obvious’ values to use for the left and right interval points for the bisection method are 1 and 1.08333. However we can’t use 1 as f(1) is zero and we need the function to be of the opposite signs at the end points.

It isn’t hard to convince yourself that the value of f(J) is positive for 0 < J < 1 by computing the turning points of f(J), i.e., the points where the derivative is zero

f′(J) = (m+ 1)PJ^m − (R + P)mJ^m−1 = 0

i.e. J^m−1(JP(m+ 1) − m(R + P)) = 0

whence either

J = 0 or J = m(R + P)

------------

P(m+ 1)

The second value may be used as the left hand end-point (Why?).

Even though the values discussed above for choosing JL and JR should guarantee f(JL) * f(JR) < 0 there is no harm done in testing that this condition does in fact hold within your implementation of the bisection method. This idea of defensive programming is important and, since it’s so cheap anyway, not worth the savings gained from not doing it (Note: this may not always be the case). Such error traps are useful both in the debugging phase of the implementation and during upgrades to the software. What’s more, if everything else has been computed correctly, this test can be used to detect impossible data – what conditions aren’t met?

The way in which the iteration within the bisection method is controlled is important. We really don’t need the interest rate computed to more than 3 significant figures. We might be tempted to use a stopping criterion of the form

|JL − JR |

|---------| <e

| JMIN |

where JL and JR are the end-points and JMIN the mid-point of the current interval. Unfortunately J isn’t the quantity we want, we are really looking for an accurate estimate of J −1. (Why is this

a problem?)

Hence a better stopping criterion is

|(JL − 1) − (JR − 1) |

|------------------ | <e

| JMID − 1 |

which you should be able to simplify a little! Choose a suitable value of ǫ to generate an interest rate of at least 3 significant figures.

Results for the premium and repayment calculations should be given to the nearest penny and, for the month calculation to the number of months necessary to pay off the loan (e.g., 3.1 months would be returned as 4). The annual interest rate should be returned correctly rounded to 2 significant decimal digits (e.g., 10.42%).

The bisection method must be implemented non-recursively and your implementation should seek to use the minimum number of function evaluations.

I need to implement a class named InterestSolver that extends the abstract class InterestSolverDefn. Given any three of the four parameters: yearly interest rate, monthly repayment amount, initial sum borrowed and time of loan in months; these methods will allow the fourth to be computed.

The abstract class Interest SolverDefn follows below followed by the homeowork question

/**

* Abstract class definition for a simple class to compute

* one of the interest rate, monthly repayment amount, initial

* premium and number of months of loan given the other three.

*

* This particular definition prescribes the bisection

* method to solve the resultant non-linear equation for

* obtaining the interest rate. Netwon's method would also be

* a possibility.

**/

public abstract class InterestSolverDefn {

/**

* Mutator for setting the rate of interest

* @param givenRate New rate of interest value

* @throws IllegalArgumentException if parameter not >0 and <100

**/

public abstract void setRate (double givenRate);

/**

* Mutator for setting the monthly repayment amount

* @param givenRepayment the new amount for the monthly repayment

* @throws IllegalArgumentException if parameter <=0

**/

public abstract void setRepayment(double givenRepayment);

/**

* Mutator for setting the number of months in the loan period

* @param givenMonths New length of loan period in months.

* @throws IllegalArgumentException if parameter <=0

**/

public abstract void setMonths(int givenMonths);

/**

* Mutator for setting the amount of the initial loan

* @param givenPremium New amount of initial loan

* @throws IllegalArgumentException if parameter <=0

**/

public abstract void setPremium(double givenPremium);

/**

* Accessor for obtaining the current setting of the interest rate

* @return The current value of the interest rate

**/

public abstract double getRate ();

/**

* Accessor for obtaining the current value of the monthly repayment

* @return The current value of the monthly repayment

**/

public abstract double getRepayment();

/**

* Accessor for obtaining the current loan period in months

* @return The current loan period in months

**/

public abstract int getMonths();

/**

* Accessor for obtaining the current value of the initial loan

* @return The current value of the initial loan

**/

public abstract double getPremium();

/**

* Method to compute the monthly repayment given the rate of interest,

* loan period in months, and value of initial loan

**/

public abstract void computeRepayment();

/**

* Method to compute the value of initial loan given the rate of interest,

* loan period in months, and value of the monthly repayment

**/

public abstract void computePremium();

/**

* Method to compute the loan period in months given the rate of interest,

* the monthly repayment, and value of initial loan

**/

public abstract void computeMonths();

/**

* Method to compute the rate of interest given the monthly repayment,

* loan period in months, and value of initial loan

**/

public abstract void computeRate();

/**

* Implementation of the bisection method to solve the single nonlinear

* equation associated with computing the interest rate given the other

* three values.

* @param a The value of the left hand end of an interval containing

* the root of the function f (see below)

* @param b The value of the right hand end of an interval containing

* the root of the function f (see below)

* a and b must be chosen so that f(a)*f(b)<0. In fact this

* is guaranteed the values are set according to the details

* given in the tutorial sheet. It is sensible to check anyway!

* @param eps Value controlling the accuracy of the final approximation

* to the root. See the description in the tutorial sheet for

* full details on how this should be used.

* @return An approximation to the root of f(J) = 0. This is a simple

* function of the interest rate.

* rate = 1200*(root - 1)

* @throws RuntimeException If f(a)*f(b)>0 on entry or the requested

* accuracy cannot be met

* @throws RuntimeException if size of repayment is too small to cover

* interest, making it impossible to pay off the loan

**/

protected abstract double bisection(double a, double b, double eps);

/**

* Function defining the non-linear equation being used by the bisection

* method to compute the interest rate. This should be the equation

* given in the assessment sheet that is obtained by using the

* geometric sum and clearing the (1-J) denominator to generate a

* simple (m+1) degree polynomial.

* @param J Value at which the function is to be evaluated

* @return The value of f(J)

**/

protected abstract double f(double J);

}

Theory

An amount £P is borrowed at an annual rate of interest of I%. At the end of each month £R is repaid and the whole debt is to be paid off in m months.

We assume that each month is considered to be 1/12 of a year and that the interest is calculated for each month of the loan on the outstanding amount at the start of each month.

If the amount owed at the end of month 1 is denoted by P1 we have

P1 = (P + I * P/1200) − R

= P(1 + I/1200) − R

Similarly at the end of month 2 the amount owed is given by

P2 = P1(1 + I/1200) − R

= (PJ − R)(J) − R

= PJ^2 − RJ − R

where J = (1 + I/1200).

Continuing in the same way we get

P3 = PJ^3 − RJ^2 − RJ − R

and in general

Pm = PJ^m − RJ^m−1 − RJ^m−2 − ... − R

= PJ^m − R(1 + J + J^2 + ...+ J^m−1)

The term in the final brackets is a geometric sum and may be written as

1 − J^m

--------------

1 − J

Note: This is only valid if J != 1 which is alright because this means that I = 0 which is a trivial case we probably aren’t interested in. However, keep this in mind in what follows.

Thus

Pm = PJ^m − R(1 − J^m)

----------------

1 − J

and if we require the loan to be paid off after m months then

Pm = 0 or PJm = R(1 − Jm)

--------------

1 − J

This gives us a way of computing P and R given the other three quantities:

P = R(1 − J^m)

-------------

J^m(1 − J)

R = PJ^m(1 − J)

---------------

1 − J^m

By rearranging we can also isolate m

R − RJ^m = PJ^m(1 − J)

R = J^m(P(1 − J) + R)

Jm = R

------------------

P(1 − J) + R

mlog J = logR − log(P(1 − J) + R)

m = logR − log(P(1 − J) + R)

-------------------------------

log J

Finally we need to be able to compute J (and hence I). Before you start trying to compute I what condition needs to hold between the other three quantities to ensure that the calculationmakes sense?

In what follows we make things as simple as possible so that the function we have to deal with is easy to compute. But note that we have multiplied through by (1 − J) and this leads to a spurious root at 1. This makes us have to think just a little bit later on.

R − RJ^m = PJ^m − PJ^m+1

PJ^m+1 − (R + P)J^m + R = 0

and to compute J requires the solution of the single nonlinear equation

f(J) ≡ PJm+1 − (R + P)Jm + R = 0

Implementation

The assessment requires you to solve this equation using the bisection method. To do this successfully you need to be able to generate two values JL and JR so that the interval

JL ≤ J ≤ JR contains the required root. If the value of I is known to be greater than zero and less than 100, then we know that the value of J is constrained to be in the range

1 < J ≤ 1 1/12 . Thus the ‘obvious’ values to use for the left and right interval points for the bisection method are 1 and 1.08333. However we can’t use 1 as f(1) is zero and we need the function to be of the opposite signs at the end points.

It isn’t hard to convince yourself that the value of f(J) is positive for 0 < J < 1 by computing the turning points of f(J), i.e., the points where the derivative is zero

f′(J) = (m+ 1)PJ^m − (R + P)mJ^m−1 = 0

i.e. J^m−1(JP(m+ 1) − m(R + P)) = 0

whence either

J = 0 or J = m(R + P)

------------

P(m+ 1)

The second value may be used as the left hand end-point (Why?).

Even though the values discussed above for choosing JL and JR should guarantee f(JL) * f(JR) < 0 there is no harm done in testing that this condition does in fact hold within your implementation of the bisection method. This idea of defensive programming is important and, since it’s so cheap anyway, not worth the savings gained from not doing it (Note: this may not always be the case). Such error traps are useful both in the debugging phase of the implementation and during upgrades to the software. What’s more, if everything else has been computed correctly, this test can be used to detect impossible data – what conditions aren’t met?

The way in which the iteration within the bisection method is controlled is important. We really don’t need the interest rate computed to more than 3 significant figures. We might be tempted to use a stopping criterion of the form

|JL − JR |

|---------| <e

| JMIN |

where JL and JR are the end-points and JMIN the mid-point of the current interval. Unfortunately J isn’t the quantity we want, we are really looking for an accurate estimate of J −1. (Why is this

a problem?)

Hence a better stopping criterion is

|(JL − 1) − (JR − 1) |

|------------------ | <e

| JMID − 1 |

which you should be able to simplify a little! Choose a suitable value of ǫ to generate an interest rate of at least 3 significant figures.

Results for the premium and repayment calculations should be given to the nearest penny and, for the month calculation to the number of months necessary to pay off the loan (e.g., 3.1 months would be returned as 4). The annual interest rate should be returned correctly rounded to 2 significant decimal digits (e.g., 10.42%).

The bisection method must be implemented non-recursively and your implementation should seek to use the minimum number of function evaluations.