reptile7's JavaScript blog
Sunday, December 06, 2015
 
Adventures in Amortization, Part 3
Blog Entry #355

We return now to our ongoing deconstruction of the Loan Amount script's JavaScript. In our last episode, we used the script's CalcPayment( ) function to calculate the monthly payment (Payment) for an amortizing loan from the loan's principal (LoanAmt), interest rate (Rate), and period (Period) via the amortization formula below:

Payment = LoanAmt * (Rate * Math.pow(1 + Rate, Period)) / (Math.pow(1 + Rate, Period) - 1);

In today's post, we'll see how to calculate an unknown LoanAmt or Period if we have values for the remaining three variables.

And we got no principals

Suppose we want to borrow at a yearly interest rate of 5% for one year and can only afford a $50 monthly payment. How much loan can we afford?

We respectively enter 5, 12, and 50 into the left frame's Rate, Period, and PaymentAmt fields and then click the LoanAmt field's button, thereby calling a CalcLoanAmt( ) function.

<label>Loan Amount ($)<br><input type="text" name="LoanAmt" size="10"></label>
<input type="button" value="Calculate" onclick="CalcLoanAmt(this.form);">

function CalcLoanAmt(form) { ... }


CalcLoanAmt( ) initially converts the Rate.value, Period.value, and PaymentAmt.value strings to numbers

Rate = parseFloat(form.Rate.value);
Period = parseInt(form.Period.value);
Payment = parseFloat(form.PaymentAmt.value);


and makes sure that there aren't any NaNs in the resulting Rate/Period/Payment data

if (isNaN(Rate)) { window.alert("Missing Interest Rate"); return; }
if (isNaN(Period)) { window.alert("Missing Period Value"); return; }
if (isNaN(Payment)) { window.alert("Missing Payment Amount"); return; }


and adjusts the Rate

if (Rate >= 1) { Rate /= 100; }
Rate /= 12;


à la the CalcPayment( ) function.

The amortization formula is easily solved for the LoanAmt: just divide both sides of the equation by
Rate * (1 + Rate)Period / ((1 + Rate)Period - 1)
and you're there.

LoanAmt = Payment / ((Rate * Math.pow(1 + Rate, Period)) / (Math.pow(1 + Rate, Period) - 1));

Plugging our Payment, Rate, and Period into the preceding equation gives us a LoanAmt of 584.0611002149129, which is truncated to 584.06 and then displayed in the LoanAmt field.

form.LoanAmt.value = (Math.floor(LoanAmt * 100) / 100).toString( );
// Alternatively and preferably: form.LoanAmt.value = LoanAmt.toFixed(2);
return; } // That's it for CalcLoanAmt( ).


You bet your loan life

Suppose we borrow $1000 at a yearly interest rate of 5% and can only afford a $50 monthly payment. How many months will it take to pay off our loan?

We respectively enter 1000, 5, and 50 into the left frame's LoanAmt, Rate, and PaymentAmt fields and then click the Period field's button, thereby calling a CalcPeriod( ) function. CalcPeriod( ) initially converts the LoanAmt.value, Rate.value, and PaymentAmt.value strings to numbers, makes sure that there aren't any NaNs in the resulting LoanAmt/Rate/Payment data, and adjusts the Rate à la the CalcPayment( ) and CalcLoanAmt( ) functions.

<label>Loan Period (in months)<br><input type="text" name="Period" size="10"></label>
<input type="button" value="Calculate" onclick="CalcPeriod(this.form);">


function CalcPeriod(form) { LoanAmt = parseFloat(form.LoanAmt.value); Rate = parseFloat(form.Rate.value); Payment = parseFloat(form.PaymentAmt.value); if (isNaN(LoanAmt)) { window.alert("Missing Loan Amount"); return; } if (isNaN(Rate)) { window.alert("Missing Interest Rate"); return; } if (isNaN(Payment)) { window.alert("Missing Payment Amount"); return; } if (Rate >= 1) { Rate /= 100; } Rate /= 12;

We can convert the solved-for-Payment amortization equation to a solved-for-Period equation as follows:

(1) Multiply both sides of the equation by (1 + Rate)Period - 1.

Payment * (1 + Rate)Period - Payment = LoanAmt * Rate * (1 + Rate)Period

(2) Rearrange the resulting equation to put the (1 + Rate)Period terms on the same side and a Payment term by itself on the opposite side, and then combine/factorize the (1 + Rate)Period terms.

(Payment - LoanAmt * Rate) * (1 + Rate)Period = Payment

(3) Divide both sides of the resulting equation by Payment - LoanAmt * Rate.

(1 + Rate)Period = Payment / (Payment - LoanAmt * Rate)

(4) Take the logarithm of both sides of the resulting equation and, recalling that log(xy) = y * log(x), divide both sides of the logarithmed equation by log(1 + Rate).

Period = log(Payment / (Payment - LoanAmt * Rate)) / log(1 + Rate)

As it happens, setting the principal and interest rate of an amortizing loan places a constraint on the loan payment: to pay off the loan (to reduce the principal), the Payment must be greater than the LoanAmt * Rate product. You can see why this is so via the solved-for-Period amortization equation:
(1) If the Payment is less than LoanAmt * Rate, then Payment / (Payment - LoanAmt * Rate) will be negative, and you can't take the logarithm of a negative number (at least in the real number system, and when you're working with loans, you're definitely restricted to the real number system).
(2) If the Payment is equal to LoanAmt * Rate, then Payment / (Payment - LoanAmt * Rate) will be infinity/undefined, i.e., it'll take an infinite number of months to pay off the loan (and I don't think we've got that long).

CalcPeriod( ) flags the Payment < LoanAmt * Rate situation via the conditional below but it doesn't confront the Payment == LoanAmt * Rate situation.

if (Payment - LoanAmt * Rate < 0) { window.alert("Payment amount too small.\nIt must be at least $" + (Math.ceil(LoanAmt * Rate)).toString( )); return; }

If the if condition is true:
(1) An alert( ) pop-up informs the lendee that the payment is too small and must be at least Math.ceil(LoanAmt * Rate). (It's not necessary to use the toString( ) method to append the payment number to the Payment amount too small ... string - see the Adding Strings and Numbers section of W3Schools' "JavaScript Operators" page.)
(2) A return statement causes CalcPeriod( ) to exit.

For specifying a minimum payment on the alert( ) box, the LoanAmt * Rate baseline is ceil( )ed, which fits the bill as long as LoanAmt * Rate is not an integer. If LoanAmt * Rate is an integer - as would be the case if we increased the yearly interest rate to 6% - then it is unaffected by the ceil( ) operation and consequently becomes the at least payment, which as detailed above isn't going to cut it.

We can handle all too-low Payments in one go with:

if (Payment <= LoanAmt * Rate) { Payment = LoanAmt * Rate != Math.ceil(LoanAmt * Rate) ? Math.ceil(LoanAmt * Rate) : (LoanAmt * Rate) + 1; window.alert("Your payment is too small to reduce the principal.\nBeef it up to at least $" + Payment); return; }

• The LoanAmt * Rate != Math.ceil(LoanAmt * Rate) condition returns true if LoanAmt * Rate is not an integer.
• Following the author's lead, I push insufficient Payments up to the next whole dollar amount, although we could get away with setting them to one cent higher than the LoanAmt * Rate baseline if we wanted to.

Once an acceptable Payment is in hand, CalcPeriod( ) calculates the Period with:

with (Math) { Period = ceil(log(Payment / (Payment - LoanAmt * Rate)) / log(1 + Rate)); }

We want the Period to be a round number of months. Toward this end, we could floor( ), round( ), or ceil( ) the Period: the author chose to ceil( ) it. We'll see below that, besides being one month longer, a ceil( )ed Period results in a lower Payment for the lendee and more total interest for the lender vis-à-vis a floor( )ed Period (unless Period is an integer, which is possible but unlikely).

Other coding points:
• The with statement, whose use Mozilla frowns on, is documented here.
Math.log( ) returns a base-e logarithm although the logarithm base for the preceding statement actually doesn't matter.

The ceil( )ed Period for our example is 21, which is displayed in the Period field.

form.Period.value = Period.toString( );
// Better: form.Period.value = Period;


As the corresponding Payments for an un-ceil( )ed Period and a ceil( )ed Period are slightly different (again assuming that Period is not an integer), CalcPeriod( ) calls the CalcPayment( ) function in order to sync the original Payment with the ceil( )ed Period.

CalcPayment(form);
return; } // That's it for CalcPeriod( ).


CalcPayment( ) outputs a Payment = 49.84 for the 21-month Period, i.e., we'll be paying 16¢/month less than originally planned. BTW, the un-ceil( )ed Period would have been 20.92620601649899: had we floor( )ed it to 20, the CalcPayment( ) Payment would be 52.22.

In the next entry we'll go through the script's CalcRate( ) function, which calculates the Rate from the LoanAmt, Period, and Payment; depending on how long that takes, we may also start discussing the script's functionality.

Comments: Post a Comment

<< Home

Powered by Blogger

Actually, reptile7's JavaScript blog is powered by Café La Llave. ;-)