Thursday, November 26, 2015

Adventures in Amortization, Part 2

Blog Entry #354

Welcome back to our discussion of the Java Goodies Loan Amount script. Having gone through the script's HTML in the previous post, we are now ready to put its JavaScript under the microscope.

Consider an amortizing loan

(1) whose principal is $1000 and

(2) with a yearly interest rate of 5% and

(3) with a period of one year.

The loan is repaid in twelve monthly installments; each payment is the same; the first payment is due one month after the loan start date. For each payment, the monthly interest on the principal balance is paid in full and the rest of the payment is applied to the principal balance. What is the monthly payment?

We enter 1000, 5, and 12 into the left frame's

`Loan Amount`

, `Yearly Interest Rate`

, and `Loan Period (in months)`

fields, which have LoanAmt, Rate, and Period name identifiers, respectively.`<label>Loan Amount ($)<br><input type="text" name="LoanAmt" size="10"></label> ...`

<label>Yearly Interest Rate (%)<br><input type="text" name="Rate" size="10"></label> ...

<label>Loan Period (in months)<br><input type="text" name="Period" size="10"></label>

• The label element has an (%inline;)* content model and therefore can validly contain a

`<br>`

line break.• The

`Loan Amount`

should be a pure number and not begin with a currency symbol of some sort, so I've appended a `($)`

to the label text (I'll append one to the `Payment`

field's label text as well, vide infra).• The

`Yearly Interest Rate`

should be a pure number and expressed as a percentage (vis-à-vis a decimal fraction), so I've appended a `(%)`

to the label text.We click the button to the right of the

`Payment`

field.`<label>Payment ($)<br><input type="text" name="PaymentAmt" size="10"></label> `

<input type="button" value="Calculate" onclick="CalcPayment(this.form);">

Clicking the button calls a CalcPayment( ) function and passes thereto a reference to the parent form (this.form), which is given a form identifier.

`<script type="text/javascript">`

var LoanAmt, Rate, Period, Payment;

function CalcPayment(form) { ... }

CalcPayment( ) first numberifies the LoanAmt.value, Rate.value, and Period.value strings as it'll be doing some math on them in just a bit.

`LoanAmt = parseFloat(form.LoanAmt.value);`

Rate = parseFloat(form.Rate.value);

Period = parseInt(form.Period.value);

The LoanAmt.value and Rate.value are parseFloat( )ed and the resulting values are assigned to the LoanAmt and Rate variables, respectively; the Period.value, for which an integer is expected, is parseInt( )ed and the resulting value is assigned to the Period variable. (FYI: parseInt( ) floors a floating-point number, e.g., a 12.5 input would be converted to 12.)

The parseFloat( ) and parseInt( ) functions both return NaN ("Not-A-Number") for non-numeric inputs. CalcPayment( ) accordingly uses a set of isNaN( ) gates to intercept any NaNs in the LoanAmt/Rate/Period data.

`if (isNaN(LoanAmt)) { window.alert("Missing Loan Amount"); return; }`

if (isNaN(Rate)) { window.alert("Missing Interest Rate"); return; }

if (isNaN(Period)) { window.alert("Missing Period Value"); return; }

What the above operations

*won't*do is stop the user from inputting numbers with commas in them, e.g., a

`Loan Amount ($)`

of 1,000, which would be parseFloat( )ed to 1 - not what we want to happen, needless to say - however, test( )ing for the presence of such commas and replace( )ing them with empty strings is not at all difficult.`LoanAmt = /,/.test(form.LoanAmt.value) ? parseFloat(form.LoanAmt.value.replace(/,/g, "")) : parseFloat(form.LoanAmt.value);`

/* The ?: conditional operator is documented here in the Mozilla JavaScript Reference. */

CalcPayment( ) next converts the Rate percentage into a more usable number as follows:

(1) If Rate is greater than or equal to 1 (and that's certainly going to be the case for most Rates), then it is divided by 100 in order to convert it to a decimal fraction.

(2) Without regard to its original value, Rate is subsequently divided by 12 in order to convert it to a monthly interest rate.

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

Rate /= 12;

/* Division assignment is documented here. */

We are now ready to calculate the monthly Payment, and we do that by plugging LoanAmt, Rate, and Period into the following

annuity formula, which is detailed and (not quite completely) derived in Wikipedia's "Amortization calculator" entry.

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

You would expect the Payment to be proportional to the LoanAmt and Rate, but the (1 + Rate)

^{Period}exponentiations, which are related to the total (principal + interest) cost of the loan, are admittedly not so intuitive.

For our example, the annuity formula gives us a Payment of 85.60748178846674, which is rounded up to the nearest cent with:

`Payment = Math.ceil(Payment * 100) / 100;`

We can more conveniently carry out the Payment truncation via the Number object's toFixed( ) method, which had not been implemented at the time that the script was written.

`Payment = Payment.toFixed(2);`

The last digit of the toFixed( ) return is rounded normally, i.e., it's increased by one if the digit after that is 5-9 and is left alone otherwise. As a result, the ceil( ) and toFixed( ) approaches will be off by a penny some of the time: that's life.

The formatted Payment is displayed in the

`Payment`

field, which has a PaymentAmt name identifier (vide supra).`form.PaymentAmt.value = Payment.toString( );`

For this assignment, there is no need to invoke the toString( ) method as Payment is automatically converted from a number to a string by the JavaScript engine.

CalcPayment( ) concludes with an unnecessary

`return;`

statement, which returns undefined to the CalcPayment( ) call and causes the function to exit.We'll go after the rest of the script's JavaScript in the following entry.

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