reptile7's JavaScript blog
Wednesday, January 06, 2016
 
Adventures in Amortization, Part 5
Blog Entry #357

We are at long last ready to use the Loan Amount script to create an amortization table for an amortizing loan. Toward this end, we will work with the same loan that we worked with in Part 2 of this series, specifically:
(1) LoanAmt.value = 1000
(2) Rate.value = 5
(3) Period.value = 12
(4) PaymentAmt.value = 85.61

Here's the schedule of payments we'll have when we're all done:



The script's ShowAmoritazation( ) function will build and display the schedule; a separate ValToMoney( ) function will format the payment data therein.

Intro

We call the ShowAmoritazation( ) function by clicking the button at the bottom of the CalcForm form.

function ShowAmoritazation(form) { /* ...Function body statements... */ }

<input type="button" value="Amortization Table" onclick="ShowAmoritazation(this.form);">


ShowAmoritazation( ) first declares a set of five variables.

var Counter, Interest, Principal, Bal, TotInterest;

Re the payment schedule, we'll see below that:
(C) Counter tracks the Month number.
(I, P) Interest and Principal respectively store the Interest and Principal amounts we pay each month.
(B) Bal tracks the amortizing principal Balance, i.e., how much principal we still owe as we go from month to month.
(T) TotInterest stores a sum of our Interest payments; the Total Interest is printed out at the bottom of the schedule.

Next, ShowAmoritazation( ) numberifies and vets the loan parameter values and subsequently adjusts the yearly Rate as per usual.

LoanAmt = parseFloat(form.LoanAmt.value);
Rate = parseFloat(form.Rate.value);
Payment = parseFloat(form.PaymentAmt.value);
Period = parseInt(form.Period.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 (isNaN(Period)) { window.alert("Missing Period Amount"); return; }
if (Rate > 1) { Rate /= 100; }
Rate /= 12;


Excepting an unnecessary return statement at the very end of the function, the rest of ShowAmoritazation( ) overwrites the right frame's document with a new document whose body holds the payment schedule.

with (parent.frames[1].document) { open( ); clear( ); writeln("<html><body bgcolor='#ffffff'>"); write("</center>"); /* ...Payment schedule construction... */ writeln("</center></body></html>"); close( ); } return;

• The open( ), clear( ), and close( ) operations are no longer needed in this day and age, if they ever were. Never heard of the clear( ) method? I hadn't either, and there's a reason for that: Netscape implemented it in JavaScript 1.0, the very first version of JavaScript, but dumped it thereafter.
• ShowAmoritazation( )'s writeln( ) commands - both above and in the code to come - can all be converted to write( ) commands: each terminating writeln( ) \n is either ignored (when following an element start tag) or unnecessary (when following a non-inline element end tag, e.g., </tr>).
• The first </center> tag should of course be a <center> tag.

Thead

The display as a whole can be divided into head, body, and foot sections, as though we were working with one big table. In practice, the payment schedule structurally comprises two tables. The first table frames the head section of the display.



write("<table border='0'>");
write("<tr>");
write("<td align='left'>Loan Amount</td>");
write("<td align='left' bgcolor='#cacaca'>$" + LoanAmt.toString( ) + "</td>");
write("<td width='30'></td>");
write("<td align='left'>Period</td>");
if (Period == 1) { write("<td align='left' bgcolor='#cacaca'>1 month</td>"); }
else { write("<td align='left' bgcolor='#cacaca'>" + Period.toString( ) + " months</td>"); }
write("</tr>");
write("<tr>");
write("<td align='left'>Interest Rate</td>");
write("<td align='left' bgcolor='#cacaca'>" + (parseInt(Rate * 1200000) / 1000).toString( ) + "%</td>");
write("<td width='30'></td>");
write("<td align='left'>Monthly Payment</td>");
write("<td align='left' bgcolor='#cacaca'>$" + Payment.toString( ) + "</td>");
write("</tr>");
write("</table>");


• The content of a td cell is left-justified by default. The align='left' attributes are legit but I would remove them as their presence makes the code more crowded than it otherwise would be.
• The bgcolor='#cacaca' and width='30' attributes are not legit and should be converted to corresponding style declarations.
• The toString( ) operations are unnecessary.
(parseInt(Rate * 1200000) / 1000) outputs an Interest Rate having three or fewer post-decimal point digits (e.g., a 5.6789 Rate.value would be truncated to 5.678).

Tbody

The second table frames the display's body and foot sections. The second table's first row holds a horizontal rule, which serves as a section break between the head and body sections.

writeln("<table border='0'>");
writeln("<tr><td colspan='5'><hr></td></tr>");


Below the rule, the body section



begins with a row that specifies a Month, Payment, Interest, Principal, and Balance series of headers.

write("<tr><th align='right'>Month</th>");
write("<th align='right'>&nbsp;&nbsp;Payment</th>");
write("<th align='right'>&nbsp;&nbsp;Interest</th>");
write("<th align='right'>&nbsp;&nbsp;Principal</th>");
writeln("<th align='right'>&nbsp;&nbsp;Balance</th></tr>");


• The &nbsp; spaces in the <th> (and, vide infra, <td>) cells serve to horizontally push the table column contents apart slightly, and the display is nicer with them there; however, I myself would use the table element's cellspacing and/or cellpadding attributes for this sort of thing.

With the header row in place, a for loop calculates and prints out Period rows of data for our loan.

// Generate the monthly payment breakdown
Bal = LoanAmt;
TotInterest = 0;

for (Counter = 1; Counter <= Period; Counter++) { // Calc the interest for the remaining balance Interest = Rate * Bal; TotInterest += Interest; // Calculate the amount of principal paid this month Principal = Payment - Interest; // Reduce the balance Bal -= Principal; if (Bal < 0) { Principal += Bal; Bal = 0; } write("<tr><td align='right'>" + Counter + "</td>"); write("<td align='right'>&nbsp;&nbsp;" + ValToMoney(Payment, 1) + "</td>"); write("<td align='right'>&nbsp;&nbsp;" + ValToMoney(Interest, 0) + "</td>"); write("<td align='right'>&nbsp;&nbsp;" + ValToMoney(Principal, 0) + "</td>"); writeln("<td align='right'>&nbsp;&nbsp;" + ValToMoney(Bal, 0) + "</td></tr>"); }

Just before the loop, the Bal is set to the LoanAmt and the TotInterest is set to 0.

Each value of the loop Counter maps onto a loan payment date; each loop iteration accordingly calculates an Interest payment, a Principal payment, and the remaining principal Bal. The calculation arithmetic is pretty straightforward:
Rate * Bal gives the Interest, which is regularly added to the TotInterest along the way.
Payment - Interest gives the Principal.
• Deducting the Principal from the previous Bal gives the current Bal. If in the last iteration (for the final loan payment) the deduction pushes the Bal below 0, then the Bal is added to the Principal (so as to lower the Principal payment by the amount that the Bal falls below 0) and after that is adjusted to 0.

For each row of the body section, the Payment, Interest, Principal, and Bal values are formatted via a ValToMoney( ) function.

// Format the passed value as a float with two decimal places function ValToMoney(Val, ConverMethod) { var FmtVal, DecPos; if (ConverMethod == 1) { FmtVal = (Math.ceil(Val * 100) / 100).toString( ); } else { FmtVal = (Math.round(Val * 100) / 100).toString( ); } // Make sure it has two decimal places after the decimal point DecPos = FmtVal.indexOf("."); if (DecPos == -1) { FmtVal += ".00"; } else if (DecPos == FmtVal.length - 1) { FmtVal += "00"; } else if (DecPos == FmtVal.length - 2) { FmtVal += "0"; } return FmtVal; }

Per the comment preceding the function, ValToMoney( ) converts its Val arguments to floating-point numbers having two post-decimal point digits; more precisely, the arguments are converted respectively to strings containing such numbers. I am not inclined to discuss this code given that we can equivalently toFixed(2) the payment data instead. (OK, Payment.toFixed(2) will have a round( )ed hundredths digit vs. a ceil( )ed hundredths digit, but so what?)

write("<td align='right'>&nbsp;&nbsp;" + Payment.toFixed(2) + "</td>");
write("<td align='right'>&nbsp;&nbsp;" + Interest.toFixed(2) + "</td>"); // Etc.


• As far as I am aware, JavaScript never outputs a number that ends with a decimal point. If you do hold onto ValToMoney( ) (and you shouldn't), then you can at least get rid of the else if (DecPos == FmtVal.length - 1) { FmtVal += "00"; } clause.

The last loop row is followed by a row that holds a horizontal rule, which serves as a section break between the body and foot sections.

write("<tr><td colspan='5'><hr></td></tr>");

Tfoot

The second table concludes with a foot section that tells us how much TotInterest we'll pay.



write("<tr><td colspan='2'>Total Interest</td>");
write("<td>&nbsp;&nbsp;" + ValToMoney(TotInterest, 0) + "</td></tr>"); // Again, just use TotInterest.toFixed(2) here.
writeln("</table>");


We'll wrap up our Loan Amount discourse - specifically, we'll retool the script some more and roll out a demo - in the following entry.

Comments: Post a Comment

<< Home

Powered by Blogger

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