" + sNl + "
"; var shHeading1 = "
" + sNl + "
" + sNl + "
%s
"; var shHeadGif = "
" + sNl + "
"; var shHeading2 = "
" + sNl + "
" + sNl + "
%s
"; var shBeginTable1 = "
"; var shBeginTable2 = "
"; var shEndTable = "
"; var shBeginRow = "
"; var shEndRow = "
"; var shCell1 = "
%s
"; var shCell2 = "
%s
"; var shCell3 = "
%s
"; var shCell4 = "
%s" + "
"; var shCell5 = "
%s" + "
"; function EmitRow1(s1, s2) { Emit(shBeginRow); Emit1(shCell1, s1); Emit1(shCell2, s2); Emit(shEndRow); } function EmitRow2(s1, s2, sx, s3, s4, s5) { Emit(shBeginRow); Emit1(shCell3, s1); Emit1(shCell3, s2); if (bPrincipal) Emit1(shCell3, sx); Emit1(shCell3, s3); Emit1(shCell3, s4); Emit1(shCell3, s5); Emit(shEndRow); } function EmitRow3(s1, s2, sx, s3, s4, s5) { Emit(shBeginRow); Emit1(shCell2, s1); Emit1(shCell2, s2); if (bPrincipal) Emit1(shCell4, Format(sx, 0, 2)); Emit1(shCell4, Format(s3, 0, 2)); Emit1(shCell4, Format(s4, 0, 2)); Emit1(shCell4, Format(s5, 0, 2)); Emit(shEndRow); } function EmitRow4(s1, s2, sx, s3, s4, s5) { Emit(shBeginRow); Emit1(shCell1, s1); Emit1(shCell1, s2); if (bPrincipal) Emit1(shCell5, Format(sx, 0, 2)); Emit1(shCell5, Format(s3, 0, 2)); Emit1(shCell5, Format(s4, 0, 2)); Emit1(shCell5, Format(s5, 0, 2)); Emit(shEndRow); } function EmitRow5(s1, s2) { Emit(shBeginRow); Emit1(shCell1,s1); Emit1(shCell4, s2); Emit(shEndRow); } function Execute(oForm) { var iError = 0; var nPrincipal, nInputInterest, iYears, iStartMonth; var iStartYear, bShowFull, iPrepayMethod, nPrepay, iPrepayPeriod; var iPrepayUse, iPpy; if ( (nPrincipal = Validate(oForm.apph0001.value, 0)) == nErrorNumber) iError = 100; else if ( (nInputInterest = Validate(oForm.apph0002.value, 0)) == nErrorNumber) iError = 110; else if ( (iYears = Validate(oForm.apph0003.value, 1)) == nErrorNumber) iError = 120; else if ( (iStartMonth = parseInt(oForm.apph0004.options[ oForm.apph0004.selectedIndex].value)) == 0) iError = 130; else if ( (iStartYear = Validate(oForm.apph0005.value, 1)) == nErrorNumber) iError = 140; else { bShowFull = parseInt(oForm.apph0006.options[oForm.apph0006.selectedIndex].value); // var iPpy = parseInt(oForm.apph0007.value); iPpy = 12; iPrepayMethod = parseInt(oForm.apph0008.options[oForm.apph0008.selectedIndex].value); if ( (nPrepay = Validate(oForm.apph0009.value, 0)) == nErrorNumber) iError = 150; else if ( (iPrepayPeriod = Validate(oForm.apph0010.value, 1)) == nErrorNumber) iError = 160; // var iPrepayUse = parseInt(oForm.apph0011.value); var iPrepayUse = 1; // 0=reduce payment; 1=pay off early } var nHalfPrepay = Round(nPrepay/2, 2); var iMonths = 12 * iYears; var iPeriods = iPpy * iYears; var nPercent = nInputInterest / iPpy / 100; var nPrepayPv = 0, nPayment = 0; var bSemaPrepay = 0, bPeriodPrepay = 0; var bAnnPrepay = 0, bSinglePrepay = 0; var sSummPayment = "N/A", sSummInterest1 = "N/A", sSummInterest2 = "N/A", sSummInterest3 = "N/A", sSummYrsReduction = "N/A", sSummAvgInterest1 = "N/A", sSummAvgInterest2 = "N/A", sSummAvgInterest3 = "N/A"; var iReducedMonths = 0; // alert(iError); if (!iError) { switch (iPrepayMethod) { case 0: // None: No Prepayments default: break; case 1: // Monthly: Pre-pay a set amount each month case 2: // Biweekly: Pre-pay a set amount every two // weeks bPeriodPrepay = 1; break; case 3: // Semiannually: Prepay a set amount every 6 months bSemaPrepay = 1; break; case 4: // Annually: Pre-pay a set amount once each year // bSemmPrepay = 1; bAnnPrepay = 1; break; case 5: // One Time: Pre-pay one set amount // after a given # of months bSinglePrepay = 1; break; } var sMessage = ""; if (nPrincipal == 0) sMessage = "Please enter a principal loan balance"; else if (nPrincipal < 0) sMessage = "Principal loan balance must be positive"; else if (nPercent == 0) sMessage = "Please enter an interest rate"; else if (nPercent < 0) sMessage = "Interest rate must be positive"; else if (iYears == 0) sMessage = "Please enter an amortization length"; else if (iYears < 0) sMessage = "Amortization length must be positive"; else if (iYears > 30) sMessage = "Amortization length must be 30 years or less"; else if (iStartYear == 0) sMessage = "Please enter a starting year"; else if (iPrepayMethod && !nPrepay) sMessage = "Please enter a pre-payment amount"; else if (nPrepay && iPrepayMethod == 0) sMessage = "Please select a pre-payment method"; else if (nPrepay < 0) sMessage = "Pre-payment amount must be positive"; else if (bSinglePrepay && (iPrepayPeriod <= 0 || iPrepayPeriod > iPeriods)) sMessage = "Please enter the appropriate month # for " + "one-time pre-payment"; else if (!bSinglePrepay && iPrepayPeriod) sMessage = "The one-time pre-payment field should " + "be left blank unless you have selected the " + "one-time pre-payment method above"; if (sMessage != "") { iError = 170; alert(sMessage); } } if (!iError) { if (bPeriodPrepay) nPrepayPv += PresentValue(nPrepay, nPercent, iPeriods); // if (bSemmPrepay) // nPrepayPv += PresentValue(nHalfPrepay, nPercent/2, // iPeriods*2); if (bSemaPrepay) nPrepayPv += SparsePresentValue(nPrepay, nPercent, iPpy/2, iPeriods); if (bAnnPrepay) nPrepayPv += SparsePresentValue(nPrepay, nPercent, iPpy, iPeriods); if (bSinglePrepay) nPrepayPv += SparsePresentValue(nPrepay, nPercent, iPrepayPeriod, iPrepayPeriod); nPayment = (iPrepayUse ? nPrincipal : (nPrincipal - nPrepayPv)) / PresentValue(1, nPercent, iPeriods); nPayment = Round(nPayment + .005, 2); sSummPayment = nPayment; if (nPrepayPv > 0) { var nFraction = 0; var nBalance = nPrincipal; var nInterest, nReduction; sSummInterest1 = 0; for (var iK = 1; iK <= iMonths; ++iK) { nInterest = nBalance * nPercent + nFraction; nFraction = nInterest - Round(nInterest,2); nInterest = Round(nInterest,2); if (nPayment > nBalance + nInterest) nPayment = nBalance + nInterest; nReduction = nPayment - nInterest; nBalance -= nReduction; sSummInterest1 += nInterest; } nPayment = sSummPayment; sSummAvgInterest1 = sSummInterest1 / iMonths; iReducedMonths = Math.round(Math.log( 1-nPercent*nPrincipal / nPayment) / Math.log(1 - nPercent)); sSummYrsReduction = (iMonths - iReducedMonths) / 12; sSummInterest2 = iReducedMonths * nPayment - nPrincipal + nPrepayPv; sSummInterest3 = sSummInterest1 - sSummInterest2; // sSummAvgInterest2 = sSummInterest3 / iReducedMonths; sSummAvgInterest2 = sSummInterest2 / iMonths; sSummAvgInterest3 = sSummAvgInterest1 - sSummAvgInterest2; sSummYrsReduction = Format(sSummYrsReduction, 0, 1); sSummInterest2 = DollarFormat(sSummInterest2, 0, 2); sSummInterest3 = DollarFormat(sSummInterest3, 0, 2); sSummAvgInterest2 = DollarFormat(sSummAvgInterest2, 0, 2); sSummAvgInterest3 = DollarFormat(sSummAvgInterest3, 0, 2); } } if (!iError) { Emit1(shStart, "
Amortization Schedule Results<\/span><\/center>"); Emit1(shHeading1, "
Amortization Schedule Results<\/span><\/center>"); Emit1(shHeading2, "
MORTGAGE INFORMATION<\/span>"); Emit(shBeginTable2); EmitRow1("Principal", DollarFormat(nPrincipal, 0, 2)); EmitRow1("Interest Rate", nInputInterest + " %"); EmitRow1("Amortization Period", iYears + " Years"); EmitRow1("Starting month", Mmm(iStartMonth)); EmitRow1("Starting year", iStartYear); EmitRow1("Monthly Mortgage Payment", DollarFormat(nPayment, 0, 2)); if (nPrepayPv) EmitRow1( bPeriodPrepay ? "Monthly Pre-Payment Amount" : bSemaPrepay ? "Semiannual Pre-Payment Amount" : bAnnPrepay ? "Annual Pre-Payment Amount" : "One-Time Pre-Payment", DollarFormat(nPrepay, 0, 2) + (bSinglePrepay ? (" paid after month " + iPrepayPeriod) : "")); // if (bDebug) // EmitRow1("nPrepayPv*", DollarFormat(nPrepayPv, 0, 2)); Emit(shEndTable); } if (!iError && bDebug) { Emit1(shHeading2, "ESTIMATED SUMMARY"); Emit(shBeginTable2); EmitRow1("Reduced Months", iReducedMonths); EmitRow1("Prepayment Present Value", DollarFormat(nPrepayPv, 0, 2)); EmitRow1("Monthly Payment", DollarFormat(sSummPayment, 0, 2)); EmitRow1("Total Interest Without Pre-Payment", DollarFormat(sSummInterest1, 0, 2)); EmitRow1("Total Interest With Pre-Payment (if applicable)", sSummInterest2); EmitRow1("Total Interest Saved", sSummInterest3); EmitRow1("Total Reduction in Years of Loan Length " + "(if applicable)", sSummYrsReduction); if (bDisplayAverageInterest) { EmitRow1("Average Interest Per Month Without Pre-Payment", DollarFormat(sSummAvgInterest1, 0, 2)); EmitRow1("Average Interest Per Month With Pre-Payment (if applicable)", sSummAvgInterest2); EmitRow1("Average Interest Per Month Saved (if applicable)", sSummAvgInterest3); } Emit(shEndTable); } if (!iError) { if (nPrepayPv == 0) { sSummInterest1 = 0; sSummAvgInterest1 = 0; } else { sSummInterest2 = sSummInterest3 = 0; sSummAvgInterest2 = sSummAvgInterest3 = 0; } Emit1(shHeading2, "
AMORTIZATION SCHEDULE<\/span>"); Emit(sNl + sNl + sNl); Emit(shBeginTable2); EmitRow2("Year", "Month", "Payment", "Principal", "Interest", "Balance"); var iK, iN; var nBalance = nPrincipal; var iMonth = iStartMonth, iYear = iStartYear; var nInterest, nReduction; var nPrepayment; var nSavedInterest = 0; // if (bSemmPrepay) // nSavedInterest = PresentValue(nHalfPrepay, -nPercent/2, 1); var bDetail = true, bEoy = 0, iStopK = 0; var sCol1, sCol2; var nYrPayment = 0, nYrPrincipal = 0, nYrInterest = 0; var nFraction = 0; for (var iK = 1; !iError && !iStopK && iK <= iPeriods; ++iK) { if (iPpy != 26) { sCol1 = iYear; sCol2 = Mmm(iMonth); } else { sCol1 = "Year # " + Math.floor((iK + iPpy - 1)/iPpy); sCol2 = "Pmt # " + ((iK-1) % iPpy + 1); } nInterest = nBalance * nPercent - nSavedInterest + nFraction; nFraction = nInterest - Round(nInterest,2); nInterest = Round(nInterest,2); nYrInterest += nInterest; nPrepayment = 0; if (bPeriodPrepay || (bAnnPrepay && iK % iPpy == 0) || (bSinglePrepay && iK == iPrepayPeriod)) nPrepayment += nPrepay; // if (bSemmPrepay) // nPrepayment += nHalfPrepay; if (bSemaPrepay && (iK % (iPpy/2) == 0)) nPrepayment += nPrepay; if (nPayment > nBalance + nInterest - nPrepayment) { nPrepayment = nBalance + nInterest - nPayment; if (nPrepayment < 0) { nPayment += nPrepayment; nPrepayment = 0; } } nYrPayment += nPayment + nPrepayment; nReduction = nPayment - nInterest; nBalance -= nReduction; if (bDetail) EmitRow3(sCol1, sCol2, nPayment, nReduction, nInterest, nBalance); if (nPrepayPv == 0) sSummInterest1 += nInterest; else sSummInterest2 += nInterest; nBalance -= nPrepayment; if (bDetail && nPrepayment != 0) EmitRow3(sCol1, sCol2 + " Prepay", nPrepayment, nPrepayment, 0, nBalance); if (Round(nBalance,2) == 0) { nBalance = 0; iStopK = iK; } if (bEoy || iK == iPeriods || iStopK) { nYrPrincipal = nYrPayment - nYrInterest; EmitRow4(sCol1, "TOTALS", nYrPayment, nYrPrincipal, nYrInterest, nBalance); nYrPrincipal = nYrPayment = nYrInterest = 0; } if (iPpy == 26) { bEoy = (iK % 26 == 25); bDetail = (iK > 26); } else { bEoy = 0; if (iPpy == 12 || iK % 2 == 0) if (++iMonth > 12) { iMonth = 1; ++iYear; bDetail = (iK < 3 * iPpy / 4); // alert(iK + "/" + (3 * iPpy / 4) + "/" + bDetail); } if (iMonth == 12 && (iPpy == 12 || iK % 2 == 1)) bEoy = 1; } if (bShowFull || bDebug) bDetail = true; } Emit(shEndTable); } // alert(sOut); if (!iError) { if (nPrepayPv == 0) { sSummAvgInterest1 = sSummInterest1 / iMonths; // sSummAvgInterest1 = DollarFormat(sSummInterest1 / iMonths, // 0, 2); // sSummInterest1 = DollarFormat(sSummInterest1, 0, 2); } else { sSummYrsReduction = Format((iMonths - iStopK)/12, 0, 1); sSummAvgInterest2 = sSummInterest2 / iMonths; sSummInterest3 = sSummInterest1 - sSummInterest2; sSummAvgInterest3 = sSummAvgInterest1 - sSummAvgInterest2; sSummInterest2 = DollarFormat(sSummInterest2 , 0, 2); sSummAvgInterest2 = DollarFormat(sSummAvgInterest2, 0, 2); sSummInterest3 = DollarFormat(sSummInterest3 , 0, 2); sSummAvgInterest3 = DollarFormat(sSummAvgInterest3, 0, 2); } sSummPayment = DollarFormat(sSummPayment, 0, 2); sSummInterest1 = DollarFormat(sSummInterest1, 0, 2); sSummAvgInterest1 = DollarFormat(sSummAvgInterest1, 0, 2); Emit1(shHeading2, "
SUMMARY<\/span>"); Emit(shBeginTable2); EmitRow5("Monthly Payment", sSummPayment); EmitRow5("Total Interest Without Pre-Payment", sSummInterest1); EmitRow5("Total Interest With Pre-Payment (if applicable)", sSummInterest2); EmitRow5("Total Interest Saved (if applicable)", sSummInterest3); EmitRow5("Total Reduction in Years of Loan Length " + "(if applicable)", sSummYrsReduction); if (bDisplayAverageInterest) { EmitRow5("Average Interest Per Month Without Pre-Payment", sSummAvgInterest1); EmitRow5("Average Interest Per Month With " + "Pre-Payment (if applicable)", sSummAvgInterest2); EmitRow5("Average Interest Per Month Saved (if applicable)", sSummAvgInterest3); } Emit(shEndTable); } if (!iError) { // document.open(); document.write(sOut); document.close(); } } function Emit(sStr) { sOut += sStr + sNl ; } function Emit1(sStr, s1) { var iK; if ( (iK = sStr.indexOf(sSub)) >= 0) sStr = sStr.substring(0, iK) + s1 + sStr.substring(iK + sSub.length); Emit(sStr); } function Emitx(sStr, sSubs) { var iK, iL, iPos1 = 0, iPos2 = 0; while ( (iK = sStr.indexOf(iPos1, sSub)) >= 0) { if ( (iL = eSubs.indexOf(iPos2, sSep)) < 0) iL = eSubs.length(); sStr = sStr.substring(0, iK) + sSubs.substring(iPos1, iL) + sStr.substring(iK + sSub.length()); iPos1 = iK + sSub.length(); if ( (iPos2 = iL + eSep.length()) > sSubs.length()) iPos2 = sSubs.length(); } Emit(sStr); } var sBlanks = " " + " "; var sDashes = "---------------------------------------------" + "-----------------------------------------"; var nPowers = [ 0.0000000000000001, 0.000000000000001, 0.00000000000001, 0.0000000000001, 0.000000000001, 0.00000000001, 0.0000000001, 0.000000001, 0.00000001, 0.0000001, 0.000001, 0.00001, 0.0001, 0.001, 0.01, 0.1, 1., 10., 100., 1000., 10000., 100000., 1000000., 10000000., 100000000., 1000000000., 10000000000.]; function Power(n) { return nPowers[n+16]; } var sMonths = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]; function Mmm(iK) { if (!(iK >= 1 && iK <= 12)) return "MMM" + iK; return sMonths[iK-1].substring(0, 3); } function ExpN(nX, iN) // compute x ** n, where n is integral { var nResult = 1; var bSign = 0; if (iN < 0) { bSign = 1; iN = -iN; } while (iN > 0) { if (iN & 1) nResult *= nX; nX *= nX; iN >>= 1; } if (bSign) nResult = 1 / nResult; return nResult; } function PresentValue(nPayment, nPercent, iNumPeriods) { var nAmount = (Math.abs(nPercent) > 1e-20) ? nPayment * (1 - ExpN(1 + nPercent, -iNumPeriods)) / nPercent : nPayment * iNumPeriods; return nAmount; } function SparsePresentValue(nPayment, nPercent, iInterval, iNumPeriods) { var nAmount = (Math.abs(nPercent) > 1e-20) ? nPayment * (1 - ExpN(1 + nPercent, -iNumPeriods)) / (ExpN(1 + nPercent, iInterval) - 1) : nPayment * iNumPeriods; return nAmount; } function Round(nVal, iD) { var iSign = 1; if (nVal < 0) { nVal = - nVal; iSign = -1; } var iInt = Math.round(nVal); if (iD > 0) iInt = Math.floor(nVal); var nFp = nVal - iInt; // alert ('iInt, nFp = ' + iInt + ", " + nFp); if (iD > 0) nFp = Math.round(nFp * Power(iD)) / Power(iD); nVal = iSign * (iInt + nFp); return nVal; } function Validate(sVal, bInt) { var sMessage = ""; var bDot = 0, bE = 0, iState = 0; var sCh, iK; var bInvalid = 0; var nValue = bInt ? parseInt(sVal) : parseFloat(sVal); for (iK = 0; sMessage == "" && iK < sVal.length; ++iK) { sCh = sVal.charAt(iK); if (sCh == " ") { if (iState > 0) iState = 9; } else { if (iState == 9) sMessage = "Number '" + sVal + "' has an embedded blank"; else if (sCh == '.' && !bInt) { if (bDot || bE) bInvalid = 1; else bDot = 1; } else if ((sCh == 'e' || sCh == 'E') && !bInt) { if (bE) bInvalid = 1; else { bE = 1; iState = 6; } } else if (sCh == '+' || sCh == '-') { if (iState == 0 || iState == 6) ++iState; else sMessage = "Number '" + sVal + "' contains a sign " + "in an illegal position" } else if (sCh >= '0' && sCh <= '9') { if (iState == 1 || iState == 7) ++iState; else if (iState == 0 || iState == 6) iState += 2; } else bInvalid = 1; } if (bInvalid) sMessage = "Number '" + sVal + "' contains" + " invalid non-numeric character(s)"; } if (sMessage == "") if (iState == 1 || iState == 6 || iState == 7) sMessage = "Illegal number: " + sVal; else if (iState == 0) nValue = 0; if (sMessage != "") { alert(sMessage); nValue = nErrorNumber; } return nValue } // DollarFormat -- could be jazzed up to produce "CR" or "DB" function DollarFormat(nVal, iW, iD) { return GenFmt(nVal, iW, iD, 1); } function Format(nVal, iW, iD) { return GenFmt(nVal, iW, iD, 0); } function GenFmt(nVal, iW, iD, bDollar) // format val into w chars, // d digs after decimal point { var sOut = ""; var iSign = 0; nVal = Round(nVal, iD); if (nVal < 0) { nVal = - nVal; iSign = 1; } var iInt = Math.round(nVal); if (iD > 0) iInt = Math.floor(nVal); var nFp = nVal - iInt; var iDigs = 1; if (iInt > 9) iDigs = Math.floor(Math.log(iInt+.1)/Math.log(10)) + 1; var iLeft = iW - iSign - (bDollar ? 1 : 0); if (iD > 0) iLeft -= iD + 1; if (iLeft > iDigs) sOut += sBlanks.substring(0, iLeft - iDigs); if (iSign) sOut += '-'; if (bDollar) sOut += '$'; sOut += iInt; if (iD > 0) { nFp = Math.round((1 + nFp) * Power(iD)); sOut += '.' + String(nFp).substring(1); } return sOut; } function PrepadString(sStr, iW) { if (sStr.length < iW) sStr = sBlanks.substring(0, iW - sStr.length) + sStr; return sStr; } function CenterString(sStr, iW) { var iBlanks = iW - sStr.length; if (iBlanks > 0) sStr = sBlanks.substring(0, Math.floor(iBlanks/2)) + sStr + sBlanks.substring(0, iBlanks - Math.floor(iBlanks/2)); return sStr; } //-->
Principal Loan Balance:
Interest Rate:
Amortization Length:
Years (typically 30 or 15)
Starting:
Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
Month
Year
full amortization table?
No
Yes
Pre-Payment Method
:
None: No Pre-payments
Monthly: Pre-pay a set amount each month
Semiannually: Pre-pay a set amount every 6 months
Annually: Pre-pay a set amount once each year
One-Time: Pre-pay one set amount after a given # of months
Pre-Payment Amount
:
Monthly/Semi-Annual/Annual/
One-Time Principal Pre-Payment Amount
One-Time Pre-Payment to be paid after month #
(enter month #)