John J. Xenakis Xenakis Consulting Services Inc.

John J. Xenakis
100 Memorial Drive Apt 8-13A
Cambridge, MA 02142
Phone: 617-864-0010
E-mail: john@jxenakis.com

File mortgage.txt


=file mortgage.txt
=// Copyright (C) 2001-2003 by Xenakis Consulting Services Inc.
=content mortgage


=data mortgage.main
<#include consul.head "Xenakis Consulting Services Inc. -- Mortgage
Scripts"#>
<#include consul.strip consul.leftcol mortgage.main.txt#>
<#include consul.bottom #>
=eod

=data mortgage.main.txt
=text
<h1>Mortgage Scripts (Javascript)</h1>

I developed these mortgage calculators for a client.  The heart of
the logic is in the mathematical algorithms, written in Javascript.

With these calculators, you can quickly estimate your potential
monthly payment, determine how much income you need to qualify, create
a mortgage amortization schedule, and much more. Each calculator has
an easy-to-understand set of instructions.

To use a calculator, click on the appropriate link below. To return
to this page from a calculator page, click on your browser's "back"
button.

<hr>

<#hreftext mortgage.calc "Amortization Schedule:" amortsc#> Estimate
monthly principal and interest payments and mortgage balances for the
term of your home loan.

<#hreftext mortgage.calc "APR: (Annual Percentage Rate)" apr#>
Calculate the actual APR for a loan, taking into account closing
costs, points, and the quoted interest rate for the loan.

<#hreftext mortgage.calc "Effects of Prepayment:" prepay#> Calculate
the effects of paying additional monthly principal against your loan.

<#hreftext mortgage.calc "Income Requirement:" increq#> Calculate the
income required to qualify for the home of your dreams.

<#hreftext mortgage.calc "Loan Length:" loanlen#> Compare the amount
of time it will take to pay off your loan with various monthly
payment amounts.

<#hreftext mortgage.calc "Mortgage Payment:" monpay#> Calculate a
monthly mortgage payment.

<#hreftext mortgage.calc "Payment Table:" paytab#>  Compare the
monthly mortgage payment for a range of interest rates and principal
amounts.

<#hreftext mortgage.calc "How Much House Can You Afford:" howhou#>
This is an unusual calculator that lets you figure out how much you
can afford to spend on a house.

<#hreftext mortgage.calc "Quick Mortgage Computations:" loanpay#>
Perform various mortgage computations, get quick results.

=eod


=data mortgage.calc
=parameters $(Script),$(Name)
<#include consul.head.txt "Xenakis Consulting Services Inc. -- $(Name)"
                 mortgage.$(Script).js#>
<#include consul.strip consul.leftcol mortgage.$(Script).html#>
<#include consul.bottom #>
=eod

=data mortgage.amortsc.js
=html

<SCRIPT LANGUAGE = "JavaScript">
<!--Hide
// Written by John J. Xenakis, http://www.jxenakis.com

var bDebug = 0 ;        // change to 1 for extra output
var bPrincipal = 1 ;    // display principal column
var sNl = "\r";
var sOut = "" ;         // html output string
var nErrorNumber = -1.2345e-12 ;

var bHtml = 1 ;         // Html versus Text test

var bDisplayAverageInterest = 0 ;

var sSub = "%s" ;

var shStart = "<html>" + sNl
      + "<head><title>%s</title></head>" + sNl
      +"<style type='text/css'>" + sNl
      +"<!--" + sNl
      +"/* make all text the same font */" + sNl
      +"TD {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      +"TH {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      +"-->" + sNl
      +"</style>" + sNl
      + "<body>" + sNl
      + "<BASEFONT FACE='Arial, Helvetica, sans-serif'>" ;

var shHeading1 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<br><TR><TD align=left>" + sNl
      + "<font size=+2><b>%s</b></font></TD></TR></TABLE><br>" ;

var shHeadGif = "<TABLE width=580 cellpadding=0 cellspacing=0>" + sNl
      + "<TR><TD><img src='%s' " + sNl
      + "width=367 height=20></TD></TR></TABLE>" ;

var shHeading2 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<br><TR align=center><TD>" + sNl
      + "<font size='+0'><b>%s</b></font></TD></TR></TABLE><br>" ;

var shBeginTable1 =
      "<TABLE border=0 cellpadding=0 cellspacing=0 width=500>" ;
var shBeginTable2 =
      "<TABLE border=1 cellpadding=2 cellspacing=0 width=500>" ;
var shBeginTable2Text = "<pre>" ;
var shEndTable = "</table>" ;
var shEndTableText = "</pre>" + sNl ;

var shBeginRow = "<tr>" ;
var shEndRow = "</tr>" ;

var shCell1 = "<td><font size='-1'><b>%s</b></font></td>" ;
var shCell2 = "<td><font size='-1'>%s</font></td>" ;
var shCell3 = "<th><font size='-1'><b>%s</b></font></th>" ;
var shCell4 = "<td align='right'><font size='-1'>%s"
                + "</font></td>" ;
var shCell5 = "<th align='right'><font size='-1'>%s"
                + "</font></th>" ;

var oTabFormats = ['l4', 'r12', 'r10', 'r12', 'r10', 'r12'] ;

function EmitRow1(s1, s2)
{
    if (bHtml)
    {
        Emit(shBeginRow) ;
        Emit1(shCell1, s1) ;
        Emit1(shCell2, s2) ;
        Emit(shEndRow) ;
    }
    else
        Emit(LineFormat([s1, s2], ['l40', 'l30'])) ;
}

function EmitRow2(s1, s2, sx, s3, s4, s5)
{
    if (bHtml)
    {
        Emit(shBeginRow) ;
        Emit1(shCell3, s1) ;
        Emit1(shCell3, s2) ;
        if (bPrincipal)
            Emit1(shCell3, sx) ;
        Emit1(shCell3, s3) ;
        Emit1(shCell3, s4) ;
        Emit1(shCell3, s5) ;
        Emit(shEndRow) ;
    }
    else
        Emit(LineFormat([s1, s2, sx, s3, s4, s5], oTabFormats)) ;
}

function EmitRow3(s1, s2, sx, s3, s4, s5)
{
    if (bHtml)
    {
        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) ;
    }
    else
        Emit(LineFormat([s1, s2, Format(sx, 0, 2), Format(s3, 0, 2),
                Format(s4, 0, 2), Format(s5, 0, 2)], oTabFormats)) ;
}

function EmitRow4(s1, s2, sx, s3, s4, s5)
{
    if (bHtml)
    {
        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) ;
    }
    else
        Emit(LineFormat([s1, s2, Format(sx, 0, 2), Format(s3, 0, 2),
                Format(s4, 0, 2), Format(s5, 0, 2)], oTabFormats)) ;
}

function EmitRow5(s1, s2)
{
    if (bHtml)
    {
        Emit(shBeginRow) ;
        Emit1(shCell1, s1) ;
        Emit1(shCell2, s2) ;
        Emit(shEndRow) ;
    }
    else
        Emit(LineFormat([s1, s2], ['l55', 'r15'])) ;
}

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) ;
      bHtml = parseInt(oForm.apph0020.options[
                oForm.apph0020.selectedIndex].value) == 0 ;

//      alert((oForm.apph0020.selectedIndex + '')
//              + oForm.apph0020.options[
//              oForm.apph0020.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") ;

      Emit1(shHeading1, "Amortization Schedule Results") ;
      Emit1(shHeading2, "MORTGAGE INFORMATION") ;

      Emit(bHtml ? shBeginTable2 : shBeginTable2Text) ;
      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(bHtml ? shEndTable : shEndTableText) ;
   }

   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") ;
      Emit(sNl + sNl + sNl) ;
      Emit(bHtml ? shBeginTable2 : shBeginTable2Text) ;

      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(bHtml ? shEndTable : shEndTableText) ;
   }

// 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") ;

      Emit(bHtml ? shBeginTable2 : shBeginTable2Text) ;
      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(bHtml ? shEndTable : shEndTableText) ;
   }

   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 ;
}

=inc mortgage.com.lineformat.js

//-->

</SCRIPT>
=eod

=data mortgage.amortsc.html
=html

<form name="apphform">


<TABLE border=0 cellpadding=3 cellspacing=0 width=500>
<TR><TD align=left><font size=+2>
         <b>Amortization Schedule Calculator</b></font></TD></TR>
</TABLE>
<BR>

<TABLE border=1 cellpadding=3 cellspacing=0 width=500>
  
  <TR>
    <TD colSpan=2 vAlign=top><font size="-1"><B>Principal Loan 
Balance:</B></font></TD>
    <TD><INPUT maxLength=10 name=apph0001 size=10 value=200000></TD></TR>
  <TR>
    <TD colSpan=2 vAlign=top><font size="-1"><B>Interest Rate:</B></font></TD>
    <TD><INPUT maxLength=6 name=apph0002 size=7 value=8.25></TD></TR>
  <TR>
    <TD colSpan=2 vAlign=top><font size="-1"><B>Amortization 
                        Length:</B></font></TD>
    <TD><INPUT maxLength=4 name=apph0003 size=4 value=30><font
    size="-1"> Years
(typically 30 or 
      15)</font></TD></TR>
  <TR>
=//    <TD colSpan=2 rowSpan=2 vAlign=top><font 
    <TD colSpan=2 vAlign=top><font 
size="-1"><B>Starting:</B></font></TD>
    <TD><font size="-1"><SELECT name=apph0004 size=1>
         <OPTION selected value=1>Jan</OPTION>
         <OPTION value=2>Feb </OPTION>
         <OPTION value=3>Mar </OPTION>
         <OPTION value=4>Apr </OPTION>
         <OPTION value=5>May </OPTION>
         <OPTION value=6>Jun </OPTION>
         <OPTION value=7>Jul </OPTION>
         <OPTION value=8>Aug </OPTION>
         <OPTION value=9>Sep </OPTION>
         <OPTION value=10>Oct</OPTION>
         <OPTION value=11>Nov</OPTION>
         <OPTION value=12>Dec</OPTION>
      </SELECT> Month </font>
=//     </TD></TR>
=//     <TR><TD>
        <INPUT maxLength=5 name=apph0005 size=5 
      value=2002><font size="-1"> Year </font></TD></TR>

  <TR>
    <TD colSpan=2 vAlign=top><font size="-1"><B>Show full amortization 
      table?</B></font></TD>
    <TD><font size="-1"><SELECT name=apph0006 size=1> <OPTION value=0
selected>No</OPTION> 
        <OPTION value=1>Yes</OPTION></SELECT></font></TD></TR>

  <TR>
    <TD colSpan=2 vAlign=top><font size="-1">
        <B>Amortization table format</B></font></TD>
    <TD><font size="-1">
        <SELECT name=apph0020 size=1>
            <OPTION value=0 selected>HTML</OPTION> 
            <OPTION value=1>Text</OPTION>
        </SELECT></font></TD></TR>


  <TR>
    <TD colSpan=2 vAlign=top><font size="-1"><B>Pre-Payment 
            Method</B>:</font></TD>
    <TD><font size="-1"><SELECT name=apph0008 size=1>
        <OPTION value=0 selected>None: No 
        Pre-payments</OPTION> 
        <OPTION value=1>Monthly: Pre-pay a set amount each month
                        </OPTION> 
        <OPTION value=3>Semiannually: Pre-pay a set amount 
                every 6 months</OPTION> 
        <OPTION value=4>Annually: Pre-pay a set amount once each
        year</OPTION> 
        <OPTION value=5>One-Time: Pre-pay one set amount
                after a given # of months</OPTION></SELECT></font>
      </TD>
   </TR>

  <TR>
    <TD colSpan=2 rowSpan=2 vAlign=top><font size="-1"><B>Pre-Payment
                        Amount</B>:</font></TD>

    <TD colSpan=2 vAlign=top><INPUT maxLength=10 name=apph0009 
              size=10 value=""><BR><font size="-1">
                Monthly/Semi-Annual/Annual/One-Time 
                Principal Pre-Payment Amount</font></TD>

   </TR>

   <TR>
      <TD colSpan=2><INPUT maxLength=3 name=apph0010 
         size=10 value=""><BR><font size="-1">
         One-Time Pre-Payment to be paid after month #
                <br>(enter month #)</font>
      </TD>
   </TR>

   <p></P></TABLE>

   <TABLE width=500><BR>
      <TR align=center>
         <TD><font size="-1"><input type=button 
            value="Amortize Now" onClick = "Execute(document.apphform)">
            </font>
         </TD>
      </TR>     
   </TABLE>

</FORM>


=eod

=data mortgage.com.lineformat.js

function LineFormat(oVals, oFormats)
{
    var sLine = '', sVal, sFmt ;
    var sItem ;

    for (var iK = 0 ; iK < oVals.length ; ++iK)
    {
        sVal = oVals[iK] ;
        sFmt = 'L10' ;  // default format
        if (iK < oFormats.length)
            sFmt = oFormats[iK] ;
       sItem = DataFormat(iK > 0, sVal, sFmt) ;
//      sAlign = sFmt.substring(0, 1).toLowerCase() ;
//      iWidth = parseInt(sFmt.substring(1)) ;
//      iBlanks = iWidth - sVal.length ;
//      if (iBlanks < 0)
//          iBlanks = 0 ;
//      if (sAlign == 'c')
//          sItem = sBlanks.substring(0, Math.floor(iBlanks/2))
//                      + sVal
//                  + sBlanks.substring(0, iBlanks - Math.floor(iBlanks/2))
//      else if (sAlign == 'r')
//          sItem = sBlanks.substring(0, iBlanks) + sVal ;
//      else
//          sItem = sVal + sBlanks.substring(0, iBlanks) ;
//      if (iK > 0)
//          sLine += ' ' ;
        sLine += sItem ;
    }
    return sLine ;      // LineFormat() exit
}

function DataFormat(bSpace, sVal, sFmt)
{
   var sItem = bSpace ? ' ' : '' ;

   var sAlign, iWidth, iSize, iBlanks ;

   sAlign = sFmt.substring(0, 1).toLowerCase() ;
   iWidth = parseInt(sFmt.substring(1)) ;
   iBlanks = iWidth - sVal.length ;
   if (iBlanks < 0)
      iBlanks = 0 ;
   if (sAlign == 'c')
      sItem += sBlanks.substring(0, Math.floor(iBlanks/2))
                        + sVal
                    + sBlanks.substring(0, iBlanks - Math.floor(iBlanks/2))
   else if (sAlign == 'r')
      sItem += sBlanks.substring(0, iBlanks) + sVal ;
   else
      sItem += sVal + sBlanks.substring(0, iBlanks) ;

    return sItem ;      // DataFormat() exit
}


=eod


=data mortgage.apr.js
=html

<SCRIPT LANGUAGE = "JavaScript">
<!--Hide
// Written by John J. Xenakis, http://www.jxenakis.com

var bDebug = 0 ;        // change to 1 for extra output
var sNl = "\r";
var sOut = "" ;         // html output string
var nErrorNumber = -1.2345e-12 ;

var sSub = "%s" ;

var shStart = "<html>" + sNl
      + "<head><title>%s</title>" + sNl
      + "<style type='text/css'>" + sNl
      + "<!--" + sNl
      + "/* make all text the same font */" + sNl
      + "TD {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "TH {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "-->" + sNl
      + "</style>" + sNl
      + "</head>" + sNl
      + "<body>" + sNl
      + "<BASEFONT FACE='Arial, Helvetica, sans-serif'>" ;

var shHeading1 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<TR><TD><br>"
      + "<font size=6><b>%s</b></font></TD></TR></TABLE><br>" ;

var shHeadGif = "<TABLE width=500 cellpadding=0 cellspacing=0>" + sNl
      + "<TR><TD><img src='%s' " + sNl
      + "width=278 height=20></TD></TR></TABLE>" ;

var shHeading2 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<br><TR align=center><TD align=left>"
      + "<font size=+2><b>%s</b></font></TD></TR></TABLE><br>" ;

var shBeginTable1 =
      "<TABLE width=500 border=1 cellpadding=3 cellspacing=0><BR>" ;
var shEndTable = "</table>" ;

var shBeginRow1 = "<tr>" ;
var shBeginRow2 = "<tr align=center>" ;
var shEndRow = "</tr>" ;

var shCell1 = "<TD colSpan=2><font size=3><B>%s</B></font></TD>" ;
var shCell2 = "<td><font size=-1><B>%s</B></font></td>" ;
var shCell3 = "<td align=right><font size=-1>%s</font></td>" ;

function EmitRow1(s1)
{
   Emit(shBeginRow2) ;
   Emit1(shCell1, s1) ;
   Emit(shEndRow) ;
}

function EmitRow2(s1, s2)
{
   Emit(shBeginRow1) ;
   Emit1(shCell2, s1) ;
   Emit1(shCell3, s2) ;
   Emit(shEndRow) ;
}

function Execute(oForm)
{
   var iError = 0 ;
   var nPrincipal, nInputInterest, iYears ;
   var nPoints, nClosingCosts ;

   if ( (nPrincipal = Validate(oForm.appg0001.value, 0)) == nErrorNumber)
      iError = 100 ;
   else if ( (nInputInterest = Validate(oForm.appg0002.value, 0)) == nErrorNumber)
      iError = 110 ;
   else if ( (nPoints = Validate(oForm.appg0003.value, 0)) == nErrorNumber)
      iError = 120 ;
   else if ( (nClosingCosts = Validate(oForm.appg0004.value, 0)) == nErrorNumber)
      iError = 130 ;
   else if ( (iYears = Validate(oForm.appg0005.value, 1)) == nErrorNumber)
      iError = 140 ;

   var iPpy = 12 ;
   var iMonths = 12 * iYears ;
   var iPeriods = iPpy * iYears ;
   var nPercent = nInputInterest / iPpy / 100 ;


   if (!iError)
   {
      var sMessage = "" ;
      if (nPrincipal == 0)
         sMessage = "Please enter a loan amount" ;
      else if (nPrincipal < 0)
         sMessage = "Loan amount must be positive" ;
      else if (nPercent == 0)
         sMessage = "Please enter a quoted interest rate" ;
      else if (nPercent < 0)
         sMessage = "Quoted interest rate must be positive" ;
//    else if (nPoints == 0)
//       sMessage = "Please enter points (%)" ;
      else if (nPoints < 0)
         sMessage = "Points (%) must be zero or positive" ;
//    else if (nClosingCosts == 0)
//       sMessage = "Please enter other closing costs" ;
      else if (nClosingCosts < 0)
         sMessage = "Other closing costs must be zero or positive" ;
      else if (iYears == 0)
         sMessage = "Please enter length of loan (in years)" ;
      else if (iYears < 0)
         sMessage = "Length of loan (in years) must be positive" ;
      if (sMessage != "")
      {
         iError = 170 ;
         alert(sMessage) ;
      }
   }

   if (!iError)
   {
      var nPayment = nPrincipal / PresentValue(1, nPercent, iPeriods) ;
      nPayment = Round(nPayment + .005, 2) ;

      var nPointsCost = nPoints * nPrincipal / 100 ;
      var nUpfront = nClosingCosts + nPointsCost ;
      var nEffectiveLoan = nPrincipal + nUpfront ;
      var nEffectivePayment = nEffectiveLoan
                                / PresentValue(1, nPercent, iPeriods) ;

      var iCount = 25 ;         // loop limit
      var nX = nPercent, nPv ;

      while (iCount-- > 0 && Math.abs(nPrincipal
                - (nPv = PresentValue(nEffectivePayment,
                                        nX, iMonths))) > 5e-5)
         nX *= 1 + (nPv - nPrincipal) / nPrincipal ;

      nEffectivePc = Round(1200 * nX - .00005, 4) ;

      Emit1(shStart, "APR Calculator Results") ;

//    Emit1(shHeading1, "Amortization Schedule Results") ;
//    Emit1(shHeadGif, "gifs/apr_calc_results.gif") ;

      Emit1(shHeading2, "APR Calculator Results") ;

      Emit(shBeginTable1) ;
      EmitRow1("$" + nPrincipal + " Fixed Loan at "
                + nInputInterest + " % for " + iYears + " Years") ;
      EmitRow2("Principal & Interest Monthly Payment",
                DollarFormat(nPayment, 0, 2)) ;
      EmitRow2("Loan Amount Before Closing Costs",
                DollarFormat(nPrincipal, 0, 2)) ;
      EmitRow2("Points at " + nPoints + " %",
                DollarFormat(nPointsCost, 0, 2)) ;
      EmitRow2("Other Costs",
                DollarFormat(nClosingCosts, 0, 2)) ;
      EmitRow2("Total Upfront Costs",
                DollarFormat(nUpfront, 0, 2)) ;
      EmitRow2("Effective Loan Amount",
                DollarFormat(nEffectiveLoan, 0, 2)) ;
      EmitRow2("Monthly Payment for Effective Loan Amount",
                DollarFormat(nEffectivePayment, 0, 2)) ;
      EmitRow2("Actual APR",
                Format(nEffectivePc, 0, 4) + " %") ;
      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 ;
}


//-->

</SCRIPT>

=eod


=data mortgage.apr.html
=html


=//<BODY bgcolor=#FFFFFF>
<form name=appgform>

<TABLE border=0 cellpadding=3 cellspacing=0 width=500>

<TR><TD><B><font size=+2>APR Calculator</font></B>
                <BR><BR></TD></TR>
<TR><TD><font size=-1>Unlike an interest rate, the Annual Percentage Rate (APR)
for a loan gives you a bigger picture when shopping for the best deal on a loan. 
The APR lets you see the total cost of a mortgage, including closing fees and points, 
over the life of the loan - not just the interest due. Enter the following information 
to calculate the actual APR for a loan:<BR><BR></font></TD></TR></TABLE>

<TABLE width=350 border=1 cellpadding=3 cellspacing=0>

  <TR>
    <TD><font size=-1><B>Loan Amount</B></font></TD>    
    <TD><INPUT name=appg0001 size=10 value=200000></TD></TR>
  <TR>
    <TD><font size=-1><B>Quoted Interest Rate (%)</B></font></TD>   
    <TD><INPUT name=appg0002 size=6 value=8.25></TD></TR> 
  <TR>
    <TD><font size=-1><B>Points (%)</B></font></TD>
    <TD><INPUT name=appg0003 size=6 value=2.0></TD></TR>
  <TR>
    <TD><font size=-1><B>Other Closing Costs to Include
                                </B></font></TD>    
    <TD><INPUT name=appg0004 size=6 value=1200></TD></TR>
  <TR>
    <TD><font face="Arial, Helvetica, sans-serif" size=-1>
                <B>Length of Loan (in Years)</B></font></TD>
    <TD><INPUT name=appg0005 size=6 value=30></TD></TR></TABLE>

<TABLE width=350><BR>
<TR align=center><TD><font size=-1>
    <input type=button value="Calculate APR"
                onClick = "Execute(document.appgform)"></font>
        </TD></TR></TABLE> 

</FORM>


=eod

=data mortgage.prepay.js
=html

<SCRIPT LANGUAGE = "JavaScript">
<!--Hide
// Written by John J. Xenakis, http://www.jxenakis.com

var bDebug = 1 ;        // change to 1 for extra output
var sNl = "\r";
var sOut = "", sDump = "" ;         // html output string
var nErrorNumber = -1.2345e-12 ;

var sSub = "%s" ;

var shStart = "<html>" + sNl
      + "<head><title>%s</title>" + sNl
      + "<style type='text/css'>" + sNl
      + "<!--" + sNl
      + "/* make all text the same font */" + sNl
      + "TD {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "TH {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "-->" + sNl
      + "</style>" + sNl
      + "</head>" + sNl
      + "<body>" + sNl
      + "<BASEFONT FACE='Arial, Helvetica, sans-serif'>" ;

var shHeading1 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=400>" + sNl
      + "<TR><TD><br>"
      + "<font size=6><b>%s</b></font></TD></TR></TABLE><br>" ;

var shHeadGif = "<TABLE width=500 cellpadding=0 cellspacing=0>" + sNl
      + "<TR><TD><img src='%s' " + sNl
      + "width=487 height=25></TD></TR></TABLE>" ;

var shHeading2 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<br><TR><TD align=left>"
      + "<font size=+2><b>%s</b></font></TD></TR></TABLE>" ;

var shBeginTable =
      "<TABLE width=400 border=1 cellpadding=3 cellspacing=0><BR><BR>" ;
var shEndTable = "</table>" ;

var shBeginRow1 = "<tr>" ;
var shBeginRow2 = "<tr align=center>" ;
var shEndRow = "</tr>" ;

var shCell1 = "<TD colSpan=5 align=center><font size=-1><B>%s"
                + "</B></font></TD></TR>" ;


function EmitRow1(s1)
{
   Emit(shBeginRow1) ;
   Emit1(shCell1, s1) ;
   Emit(shEndRow) ;
}

function Execute(oForm)
{
   var iError = 0 ;
   var nPrincipal, nInputInterest, iYears, iOption ;
   var nPayment, iAltYears = 0, nAltPayment = 0 ;

   if ( (nPrincipal = Validate(oForm.appl0001.value, 0)) == nErrorNumber)
      iError = 100 ;
   else if ( (nInputInterest = Validate(oForm.appl0002.value, 0)) == nErrorNumber)
      iError = 110 ;
   else if ( (iYears = Validate(oForm.appl0003.value, 1)) == nErrorNumber)
      iError = 120 ;
   else if ( (iOption = parseInt(oForm.appl0004.options[
                oForm.appl0004.selectedIndex].value)) == 0)
      iError = 130 ;

   else if (iOption == 1 && (iAltYears = Validate(
                        oForm.appl0005.value, 1)) == nErrorNumber)
      iError = 140 ;

   else if (iOption == 2 && (nAltPayment = Validate(
                        oForm.appl0005.value, 0)) == nErrorNumber)
      iError = 150 ;

   var iMonths = 12 * iYears ;
   var nPercent = nInputInterest / 1200 ;

   if (!iError)
   {
      var sMessage = "" ;
      if (nPrincipal == 0)
         sMessage = "Please enter a loan amount" ;
      else if (nPrincipal < 0)
         sMessage = "Loan amount must be positive" ;
      else if (nInputInterest == 0)
         sMessage = "Please enter an interest rate" ;
      else if (nInputInterest < 0)
         sMessage = "Interest rate must be positive" ;
      else if (iYears == 0)
         sMessage = "Please enter amortized length (in Years)" ;
      else if (iYears < 0)
         sMessage = "Amortized length (in Years) must be positive" ;
      else
      {
         nPayment = nPrincipal / PresentValue(1, nPercent, iMonths) ;
         nPayment = Round(nPayment + .005, 2) ;

         if (iOption == 1 && iAltYears == 0)
            sMessage = "Please enter a desired loan length" ;
         else if (iOption == 1 && iAltYears < 0)
            sMessage = "Desired loan length must be positive" ;

         else if (iOption == 1 && iAltYears >= iYears)
            sMessage = "Your desired length of loan must be "
                + "smaller than the amortized length" ;

         else if (iOption == 2 && nAltPayment <= nPayment)
            sMessage = "Your desired monthly payment for this mortgage "
                + "needs to be at least "
                + DollarFormat(nPayment, 0, 2) ;
      }


      if (sMessage != "")
      {
         iError = 170 ;
         alert(sMessage) ;
      }
   }

   if (!iError)
   {
      if (iOption == 1)
      {
         nAltPayment = nPrincipal / PresentValue(1, nPercent,
                                                12 * iAltYears) ;
         nAltPayment = Round(nAltPayment + .005, 2) ;
      }
      else
         iAltYears = iYears ;

      var nTotalInterest = 0, nAltInterest = 0 ;
      var iAltMonths = 0 ;

      var nFraction = 0 ;
      var nBalance = nPrincipal ;
      var nInterest, nReduction, iK ;
      var nPay = nPayment ;

      for (iK = 1 ; iK <= iMonths ; ++iK)
      {
         nInterest = nBalance * nPercent + nFraction ;
         nFraction = nInterest - Round(nInterest,2) ;
         nInterest = Round(nInterest,2) ;
         if (nPay > nBalance + nInterest)
            nPay = nBalance + nInterest ;
         nReduction = nPay - nInterest ;
         nBalance -= nReduction ;
         nTotalInterest += nInterest ;
      }

      nFraction = 0 ;
      nBalance = nPrincipal ;
      nPay = nAltPayment ;

      for (iK = 1 ; !iAltMonths && iK < 2000 ; ++iK)
      {
         nInterest = nBalance * nPercent + nFraction ;
         nFraction = nInterest - Round(nInterest,2) ;
         nInterest = Round(nInterest,2) ;
         if (nPay > nBalance + nInterest)
         {
            nPay = nBalance + nInterest ;
            iAltMonths = iK ;
         }
         nReduction = nPay - nInterest ;
         nBalance -= nReduction ;
         nAltInterest += nInterest ;
      }

//    Dump("iAltMonths = " + iAltMonths) ;
      iAltYears = Round(iAltMonths/12, 1) ;

      var nSavedInterest = nTotalInterest - nAltInterest ;
      var nExtraPayment = nAltPayment - nPayment ;
      var sText ;
      
      Emit1(shStart, "Effects of Prepayment Calculator Results") ;

//    Emit1(shHeadGif, "gifs/effects_calc_results.gif") ;
      Emit1(shHeading2, "Effects of Prepayment Calculator Results") ;

      Emit(shBeginTable) ;

      sText = "Your normal monthly payment on a $" + nPrincipal 
            + " loan<BR>at " + nInputInterest + " % for " + iYears
            + " years would be " + DollarFormat(nPayment, 0, 2) ;
      EmitRow1(sText) ;

      if (iOption == 1)
         sText = "To pay off the same loan in " + iAltYears
            + " years,<BR>you would need to increase your monthly"
            + " <BR>payment by " + DollarFormat(nExtraPayment, 0, 2)
            + " to " + DollarFormat(nAltPayment, 0, 2) ;

      else
         sText = "By paying an extra "
            + DollarFormat(nExtraPayment, 0, 2) + " each month,"
            + " <BR>you would pay your loan off in "
            + iAltYears + " years" ;

      EmitRow1(sText) ;

      sText = "Your total interest savings would be "
            + DollarFormat(nSavedInterest, 0, 2) ;
      EmitRow1(sText) ;
      Emit(shEndTable) ;
   }

   if (!iError)
   {
    if (bDebug && sDump != "")
         sOut += sNl + "<pre>" + sDump + "</pre>" ;
//    document.open() ;
      document.write(sOut) ;
      document.close() ;

   }

}

function Dump(sStr) { if (bDebug) sDump += sStr + sNl ;}

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 Round2(nVal, iD, iType)
// iType: -1 = floor, 0 = round, +1 = ceil
{
   var nPow = Power(iD) ;
   nVal = Math.round(nVal * nPow + iType * .5) / nPow ;
// alert(nVal + "/" + iD + "/" + iType + "/" + nPow + "/" + nVal) ;
   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 ;
}


//-->

</SCRIPT>
=eod

=data mortgage.prepay.html
=html

<form name=applform>


<TABLE border=0 cellspacing=0 cellpadding=0 width=500>
<TR><TD align=left><font size=+2><b>Effects of Prepayment Calculator</b></font></TD></TR>
<TR><TD><BR><font size=-1>

Find out how much you can save on interest costs by making
prepayments on your mortgage.  This calculator takes a principal
amount, interest rate, normal length of the loan, and either how long
you want to pay or how much total a month to
pay.</font></TD></TR></TABLE><BR>

<TABLE border=1 cellspacing=0 cellpadding=3 width=400>
  <TR>
    <TD><font size=-1><B>Your Loan Amount</B></font></TD>
    <TD><INPUT name=appl0001 size=10 value=200000></TD></TR>
  <TR>
    <TD><font size=-1><B>Your Interest Rate</B></font></TD>
    <TD><INPUT name=appl0002 size=5 value=8.25></TD></TR>
  <TR>
    <TD><font size=-1><B>Amortized Length of Mortgage</B></font></TD>
    <TD><INPUT name=appl0003 size=5 value=30></TD></TR>
  <TR>
    <TD><font size=-1>
        <SELECT name=appl0004>
            <OPTION value=1 selected>Desired Length of Loan (in years)
            <OPTION value=2>Desired Monthly Payment</OPTION>
        </SELECT></font> 
    <TD><INPUT name=appl0005 size=5 value=20></TD></TR></TABLE>

<TABLE width=400><BR>
<TR align=center><TD><font size=-1>
<input type=button value="Calculate"
                onClick = "Execute(document.applform)">
</font></TR></TD></TABLE>
 
</FORM>

=eod

=data mortgage.increq.js
=html

<SCRIPT LANGUAGE = "JavaScript">
<!--Hide
// Written by John J. Xenakis, http://www.jxenakis.com

var bDebug = 1 ;        // change to 1 for extra output
var sNl = "\r";
var sOut = "", sDump = "" ;         // html output string
var nErrorNumber = -1.2345e-12 ;

var sSub = "%s" ;

var shStart = "<html>" + sNl
      + "<head><title>%s</title>" + sNl
      + "<style type='text/css'>" + sNl
      + "<!--" + sNl
      + "/* make all text the same font */" + sNl
      + "TD {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "TH {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "-->" + sNl
      + "</style>" + sNl
      + "</head>" + sNl
      + "<body>" + sNl
      + "<BASEFONT FACE='Arial, Helvetica, sans-serif'>" ;

var shHeading1 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<TR><TD><br>"
      + "<font size=6><b>%s</b></font></TD></TR></TABLE><br>" ;

var shHeadGif = "<TABLE width=500 cellpadding=0 cellspacing=0>" + sNl
      + "<TR><TD><img src='%s' " + sNl
      + "width=471 height=24></TD></TR></TABLE>" ;

var shHeading2 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<TR><TD>"
      + "<font size=+2><b>%s</b></font></TD></TR></TABLE>" ;

var shBeginTable1 =
      "<TABLE width=500 border=1 cellpadding=3 cellspacing=0><BR><BR>" ;
var shBeginTable2 =
      "<TABLE border=0 cellpadding=0 cellspacing=0 width=500><BR>" ;

var shEndTable = "</table>" ;

var shBeginRow1 = "<tr>" ;
var shBeginRow2 = "<tr align=center>" ;
var shEndRow = "</tr>" ;

var shCell1 = "<TD colSpan=5><font size=-1><B>%s"
                + "</B></font></TD></TR>" ;

var shCell1 = "<TD><font size=-1><B>%s</B></font></TD>" ;
var shCell2 = "<TD align=right><font size=-1>%s</font></TD>" ;

function EmitRow1(s1, s2)
{
   Emit(shBeginRow1) ;
   Emit1(shCell1, s1) ;
   Emit1(shCell2, s2) ;
   Emit(shEndRow) ;
}

var shLongText = ""

   + "<TR><TD><font size=-1>Your <B>total monthly lender" + sNl
   + "payment</B> includes monthly mortgage principal and" + sNl
   + "interest plus monthly property tax and home insurance" + sNl
   + "costs (also known as \"PITI\").</font></TD></TR>" + sNl + sNl

   + "<TR><TD><font size=-1><BR>Your <B>total monthly debt" + sNl
   + "payment</B> includes monthly mortgage principal and" + sNl
   + "interest plus monthly property tax and home insurance" + sNl
   + "costs, PLUS any other debt payments you must pay every" + sNl
   + "month.</font></TD></TR>" + sNl + sNl

   + "<TR><TD><font size=-1><BR>A <B>monthly PMI payment</B>" + sNl
   + "appears above if your down payment is less than 20% of" + sNl
   + "your home value. That means you must pay an extra .7%" + sNl
   + "of your beginning loan balance per year for private" + sNl
   + "mortgage insurance (\"PMI\").  In many cases, the lender" + sNl
   + "will allow cancellation of PMI when the loan is paid" + sNl
   + "down to 80% of the original property" + sNl
   + "value.</font></TD></TR>" + sNl + sNl

   + "<TR><TD><font size=-1><BR>The <B>28% Qualifying Annual" + sNl
   + "Income</B> is the amount that you need to earn to meet" + sNl
   + "the housing expense guideline used by lenders to" + sNl
   + "determine the mortgage amount they will lend to a home" + sNl
   + "buyer.  Lenders generally say that the <b>total monthly lender" + sNl
   + "payment</b> plus the <b>total monthly PMI payment</b> (if applicable)" + sNl
   + "should not exceed 28 percent of the homeowner's gross monthly" + sNl
   + "income.</font></TD></TR>" + sNl + sNl

   + "<TR><TD><font size=-1><BR>The <B>36% Qualifying Annual" + sNl
   + "Income</B> is the amount that you need to earn to meet" + sNl
   + "the long term debt guideline used by lenders to" + sNl
   + "determine the mortgage amount they will lend to a home" + sNl
   + "buyer.  Lenders generally say that the <b>total monthly debt" + sNl
   + "payment</b> plus the <b>total monthly PMI payment</b> (if applicable)" + sNl
   + "should not exceed 36 percent of the homeowner's gross monthly" + sNl
   + "income.</font></TD></TR>" + sNl + sNl

   + "<TR><TD><font size=-1><BR>As long as your total gross" + sNl
   + "annual income (salary plus interest, rental and" + sNl
   + "dividend income) meets <B>BOTH</B> the 28% Qualifying" + sNl
   + "Annual Income and the 36% Qualifying Annual Income," + sNl
   + "you will likely qualify.</font></TD></TR>" + sNl + sNl ;

function Initialize(oForm)
{
    if (0 && bDebug)
    {
        oForm.appn0001.value = 800 ;
        oForm.appn0002.value = 1616 ;
        oForm.appn0003.value = 1000 ;
        oForm.appn0004.value = 1000 ;
        oForm.appn0005.value = 100000 ;
        oForm.appn0006.value = 20000 ;
    }
}

function Execute(oForm)
{
   var iError = 0 ;
   var nPayment = 0, nAnnualTax, nAnnualInsurance, nMonthlyDebt ;
   var nSalePrice, nDownPayment ;
   var nPrincipal, nInputInterest = 0, iYears = 0 ;

   var nLenderPayment = 0, nDebtPayment = 0, nPmiPayment = 0 ;
   var nQualify28 = 0, nQualify36 = 0, nQualifyMinimum = 0 ;

// if ( (nPayment = Validate(oForm.appn0001.value, 0)) == nErrorNumber)
//    iError = 100 ;

    var cType = oForm.appn0010.value ;  // I means interest/term is
                        // given, P means payment given

   if ( (nAnnualTax = Validate(oForm.appn0002.value, 0)) == nErrorNumber)
      iError = 110 ;

   else if ( (nAnnualInsurance = Validate(oForm.appn0003.value, 0)) == nErrorNumber)
      iError = 120 ;

   else if ( (nMonthlyDebt = Validate(oForm.appn0004.value, 0)) == nErrorNumber)
      iError = 130 ;

   else if ( (nSalePrice = Validate(oForm.appn0005.value, 0)) == nErrorNumber)
      iError = 140 ;

   else if ( (nDownPayment = Validate(oForm.appn0006.value, 0)) == nErrorNumber)
      iError = 150 ;

// else if ( (nPrincipal = Validate(oForm.appn0007.value, 0)) == nErrorNumber)
//    iError = 160 ;

   else if (cType == 'I'
            && (nInputInterest = Validate(oForm.appn0008.value, 0)) == nErrorNumber)
      iError = 170 ;

   else if (cType == 'I'
            && (iYears = Validate(oForm.appn0009.value, 1)) == nErrorNumber)
      iError = 180 ;

   else if (cType == 'P'
            && (nPayment = Validate(oForm.appn0011.value, 0)) == nErrorNumber)
      iError = 190 ;

   if (!iError)
   {
      var sMessage = "" ;

      if (cType == 'P')
      {
         if (nPayment == 0)
            sMessage = "Please enter Monthly Mortgage Principal/Interest" ;
         else if (nPayment < 0)
            sMessage = "Monthly Mortgage Principal/Interest must be positive" ;
      }

      if (sMessage == '')
      {
         if (nAnnualTax == 0)
            sMessage = "Please enter Expected Annual Property Tax" ;
         else if (nAnnualTax < 0)
            sMessage = "Expected Annual Property Tax must be positive" ;
         else if (nAnnualInsurance == 0)
            sMessage = "Please enter Expected Annual Home Insurance Costs" ;
         else if (nAnnualInsurance < 0)
            sMessage = "Expected Annual Home Insurance Costs must be positive" ;
         //    else if (nMonthlyDebt == 0)
            //   sMessage = "Please enter Expected Monthly Debt Payments" ;
         else if (nMonthlyDebt < 0)
            sMessage = "Expected Monthly Debt Payments must be positive or zero" ;
         else if (nSalePrice == 0)
            sMessage = "Please enter Sale Price of Home" ;
         else if (nSalePrice < 0)
            sMessage = "Sale Price of Home must be positive" ;
//         else if (nDownPayment == 0)
//            sMessage = "Please enter Down Payment" ;
         else if (nDownPayment < 0)
            sMessage = "Down Payment must be positive" ;
//         else if (nDownPayment < .03*nSalePrice)
//            sMessage = "Down Payment must be equal to at least 3% of "
//                        + "the sale price of the home" ;
      }

      if (sMessage == '' && cType == 'I')
      {
         if (nInputInterest == 0)
            sMessage = "Please enter an Interest Rate" ;
         else if (nInputInterest < 0)
            sMessage = "Interest rate must be positive" ;
         else if (iYears == 0)
            sMessage = "Please enter amortized length (in Years)" ;
         else if (iYears < 0)
            sMessage = "Amortized length (in Years) must be positive" ;
      }
      nPrincipal = nSalePrice - nDownPayment ;
      if (nPrincipal < 0)
         nPrincipal = 0 ;
      var iMonths = 12 * iYears ;
      var nPercent = nInputInterest / 1200 ;

      if (sMessage != "")
      {
         iError = 170 ;
         alert(sMessage) ;
      }
   }

   if (!iError)
   {
      if (cType == 'I')
      {
         nPayment = nPrincipal / PresentValue(1, nPercent, iMonths) ;
         nPayment = Round(nPayment + .005, 2) ;
      }
      nLenderPayment = Round(nPayment + nAnnualTax/12
                                + nAnnualInsurance/12, 2) ;
      nDebtPayment = nLenderPayment + nMonthlyDebt ;

//    nPmiPayment = Round((nDownPayment >= .2 * nSalePrice) ? 0
//                      : .07 / 12 * (nSalePrice - nDownPayment), 2) ;
//    nPmiPayment = Round((nDownPayment >= .2 * nSalePrice) ? 0
//                      : .07 / 12 * nSalePrice, 2) ;
      nPmiPayment = Round((nDownPayment >= .2 * nSalePrice) ? 0
                        : .007 / 12 * (nSalePrice - nDownPayment), 2) ;

      nQualify28 = Round((nLenderPayment + nPmiPayment) * (12/.28), 2) ;

      nQualify36 = Round((nDebtPayment + nPmiPayment) * (12/.36), 2) ;

      nQualifyMinimum = nQualify28 ;
      if (nQualifyMinimum < nQualify36)
         nQualifyMinimum = nQualify36 ;

      Emit1(shStart, "Income Requirement Calculator Results") ;

//    Emit1(shHeadGif, "gifs\income_calc_results.gif") ;
      Emit1(shHeading2, "Income Requirement Calculator Results") ;

      Emit(shBeginTable1) ;

      EmitRow1("Monthly Principal and Interest",
                                DollarFormat(nPayment, 0, 2)) ;
      EmitRow1("Total Monthly Lender Payment",
                                DollarFormat(nLenderPayment, 0, 2)) ;
      EmitRow1("Total Monthly Debt Payment",
                                DollarFormat(nDebtPayment, 0, 2)) ;
      EmitRow1("Total Monthly PMI Payment",
                                DollarFormat(nPmiPayment, 0, 2)) ;
      EmitRow1("28% Qualifying Annual Income",
                                DollarFormat(nQualify28, 0, 2)) ;
      EmitRow1("36% Qualifying Annual Income",
                                DollarFormat(nQualify36, 0, 2)) ;
      EmitRow1("Minimum Qualifying Annual Income",
                                DollarFormat(nQualifyMinimum, 0, 2)) ;
      Emit(shEndTable) ;

      Emit(shBeginTable2) ;
      Emit(shLongText) ;
      Emit(shEndTable) ;

   }

   if (!iError)
   {
    if (bDebug && sDump != "")
         sOut += sNl + "<pre>" + sDump + "</pre>" ;
//    document.open() ;
      document.write(sOut) ;
      document.close() ;

   }

}

function Dump(sStr) { if (bDebug) sDump += sStr + sNl ;}

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 Round2(nVal, iD, iType)
// iType: -1 = floor, 0 = round, +1 = ceil
{
   var nPow = Power(iD) ;
   nVal = Math.round(nVal * nPow + iType * .5) / nPow ;
// alert(nVal + "/" + iD + "/" + iType + "/" + nPow + "/" + nVal) ;
   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 ;
}


//-->

</SCRIPT>
=eod


=data mortgage.increq.html
=html


=//<BODY bgcolor=#FFFFFF onLoad="Initialize(document.appnform)">
<form name=appnform>


<TABLE border=0 cellpadding=0 cellspacing=0 width=500>

<TR><TD><font size=+2><b>Income Requirement Calculator</b></font></TD></TR>

<TR><TD><BR><font size=-1>Calculate what kind of income lenders will
expect you to be making to qualify for the specific values that you
enter below.  Keep in mind that property tax and home insurance costs
vary greatly by region and construction
type.</font></TD></TR></TABLE><BR>

<TABLE border=1 cellpadding=3 cellspacing=0 width=400>

  <TR>
    <TD><font size=-1><B>Sale Price of Home</B></font></TD>
    <TD><INPUT name=appn0005 value=200000 size=10
         ></TD></TR>
  <TR>
    <TD><font size=-1><B>Down Payment</B></font></TD>
    <TD><INPUT name=appn0006 value=40000 size=10
         ></TD></TR>
 <TR>
    <input type="hidden" name="appn0010" value="I">
    <TD><font size=-1><B>Interest Rate</B></font></TD>
    <TD><INPUT name=appn0008 value=8.25 size=10
         ></TD></TR>
 <TR>
    <TD><font size=-1><B>Term (in years)</B></font></TD>
    <TD><INPUT name=appn0009 value=30 size=10
         ></TD></TR>
  <TR>
    <TD><font size=-1><B>Expected Annual Property Tax</B></font></TD>
    <TD><INPUT name=appn0002 value=3000 size=10
         ></TD></TR>
  <TR>
    <TD><font size=-1><B>Expected Annual Home Insurance Costs</B></font></TD>
    <TD><INPUT name=appn0003 value=1000 size=10
         ></TD></TR>
  <TR>
    <TD><font size=-1><B>Expected Monthly Debt Payments <BR>
                (car/student loan, credit cards, etc.)</B></font></TD>
    <TD><INPUT name=appn0004 value=1000 size=10
         ></TD></TR></TABLE><BR>

<TABLE width=400><TR align=center><TD><font size=-1>

<input type=button value="Calculate Required Income"
                onClick = "Execute(document.appnform)">

</font></TD></TR></TABLE>

</FORM>

=eod

=data mortgage.loanlen.js
=html


<SCRIPT LANGUAGE = "JavaScript">
<!--Hide
// Written by John J. Xenakis, http://www.jxenakis.com

var bDebug = 0 ;        // change to 1 for extra output
var sNl = "\r";
var sOut = "" ;         // html output string
var nErrorNumber = -1.2345e-12 ;

var sSub = "%s" ;

var iStopMonths = 12 * 300 ;    // approximate length after this
var bLongTest = false ;         // if true, init for long test

var shStart = "<html>" + sNl
      + "<head><title>%s</title>" + sNl
      + "<style type='text/css'>" + sNl
      + "<!--" + sNl
      + "/* make all text the same font */" + sNl
      + "TD {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "TH {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "-->" + sNl
      + "</style>" + sNl
      + "</head>" + sNl
      + "<body>" + sNl
      + "<BASEFONT FACE='Arial, Helvetica, sans-serif'>" ;

var shHeading1 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<br><TR><TD align=left>"
      + "<font size=+2><b>%s</b></font></TD></TR></TABLE>" ;

var shHeadGif = "<TABLE width=580 cellpadding=0 cellspacing=0>" + sNl
      + "<TR><TD><img src='%s' " + sNl
      + "width=374 height=25></TD></TR></TABLE>" ;

var shHeading2 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<br><TR align=center><TD>"
      + "<font size='+0'><b>%s</b></font></TD></TR></TABLE><br>" ;

var shBeginTable1 =
      "<TABLE width=500 border=1 cellpadding=2 cellspacing=0><BR>" ;
var shEndTable = "</table>" ;

var shBeginRow = "<tr>" ;
var shEndRow = "</tr>" ;

var shCell1 = "<th><font face='Arial, Helvetica, sans-serif' "
    + "size='-1'>%s</font></th>" ;

var shCell2 = "<td><font size=-1><B>%s</B></font></td>" ;
var shCell3 = "<td align=right><font size=-1>%s</font></td>" ;

function EmitRow1(s2, s3)
{
   Emit(shBeginRow) ;
   Emit1(shCell1, "&nbsp;") ;
   Emit1(shCell1, s2) ;
   Emit1(shCell1, s3) ;
   Emit(shEndRow) ;
}


function EmitRow2(s1,s2,s3)
{
   Emit(shBeginRow) ;
   Emit1(shCell2, s1) ;
   Emit1(shCell3, s2) ;
   Emit1(shCell3, s3) ;
   Emit(shEndRow) ;
}


function Initialize(f)
{
    if (bLongTest)
    {
        f.appo0001.value=100000;        // beg balance
        f.appo0002.value=250.01;        // min monthly payment
        f.appo0003.value=500;   // max monthly payment
        f.appo0004.value=3;     // interest rate
    }
}


function Execute(oForm)
{
   var iError = 0 ;
   var nPrincipal, nInputInterest, iYears ;
   var nMinPayment, nMaxPayment ;

   if ( (nPrincipal = Validate(oForm.appo0001.value, 0)) == nErrorNumber)
      iError = 100 ;
   else if ( (nMinPayment = Validate(oForm.appo0002.value, 0)) == nErrorNumber)
      iError = 110 ;
   else if ( (nMaxPayment = Validate(oForm.appo0003.value, 0)) == nErrorNumber)
      iError = 120 ;
   else if ( (nInputInterest = Validate(oForm.appo0004.value, 0)) == nErrorNumber)
      iError = 130 ;

   var iPpy = 12 ;
// var iMonths = 12 * iYears ;
// var iPeriods = iPpy * iYears ;
   var nPercent = nInputInterest / iPpy / 100 ;


   if (!iError)
   {
      var sMessage = "" ;
      if (nPrincipal == 0)
         sMessage = "Please enter a beginning balance" ;
      else if (nPrincipal < 0)
         sMessage = "Beginning balance must be positive" ;
      else if (nMinPayment == 0)
         sMessage = "Please enter a minimum monthly payment" ;
      else if (nMinPayment < 0)
         sMessage = "Minimum monthly payment must be positive" ;
      else if (nMaxPayment == 0)
         sMessage = "Please enter a maximum monthly payment" ;
      else if (nMaxPayment < 0)
         sMessage = "Maximum monthly payment 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 (nMinPayment > nMaxPayment)
         sMessage = "Minimum monthly payment must be smaller "
                        + "than maximum monthly payment" ;
      else if (nMinPayment <= nPrincipal * nPercent)
         sMessage = "Minimum monthly payment is too"
                + " small because it is less than the monthly"
                + " interest required by this loan" ;
      if (sMessage != "")
      {
         iError = 170 ;
         alert(sMessage) ;
      }
   }

   if (!iError)
   {
      var iK, iN ;
      var nFraction = 0 ;
      var nBalance = nPrincipal ;
      var nInterest, nReduction ;
      var iMonths1 = 0, iMonths2 = 0 ;
      var nTotalInt1, nTotalInt2 ;

      for (iN = 1 ; iN >= 0 ; --iN)
      {
         nPayment = iN ? nMinPayment : nMaxPayment ;
         nFraction = 0 ;
         nBalance = nPrincipal ;
         iMonths2 = 0 ;
         nTotalInt2 = 0 ;
         for (iK = 1 ; !iMonths2 && iK <= iStopMonths ; ++iK)
         {
            nInterest = nBalance * nPercent + nFraction ;
            nFraction = nInterest - Round(nInterest,2) ;
            nInterest = Round(nInterest,2) ;
            if (nPayment > nBalance + nInterest)
            {
               nPayment = nBalance + nInterest ;
               iMonths2 = iK ;
            }
            nReduction = nPayment - nInterest ;
            nBalance -= nReduction ;
            nTotalInt2 += nInterest ;
         }

         if (!iMonths2)         // looping algorithm failed
         {
            nPayment = iN ? nMinPayment : nMaxPayment ;
            iMonths2 = Math.floor(
                        Math.log(1-nPercent*nPrincipal / nPayment)
                        / Math.log(1 - nPercent)) - 1 ;
            nTotalInt2 = nPayment * iMonths2 - nPrincipal ;
         }

         if (iN)
         {
            nTotalInt1 = nTotalInt2 ;
            iMonths1 = iMonths2 ;
         }
      }

      var nAvgInt1 = Round(nTotalInt1 / iMonths1, 2) ;
      var nAvgInt2 = Round(nTotalInt2 / iMonths2, 2) ;
      var nYears1 = Round(iMonths1/iPpy, 3) ;
      var nYears2 = Round(iMonths2/iPpy, 3) ;


      Emit1(shStart, "Loan Length Calculator Results") ;

//    Emit1(shHeadGif, "gifs/loan_length_calc_results.gif") ;

      Emit1(shHeading1, "Loan Length Calculator Results") ;
      Emit1(shHeading2, "LOAN LENGTH COMPARISON") ;

      Emit(shBeginTable1) ;
      EmitRow1("Minimum Monthly Payment", "Maximum Monthly Payment") ;
      EmitRow2("Monthly Payment", DollarFormat(nMinPayment, 0, 2),
                        DollarFormat(nMaxPayment, 0, 2)) ;
      EmitRow2("Beginning Balance of Loan",
               DollarFormat(nPrincipal, 0, 2),
               DollarFormat(nPrincipal, 0, 2)) ;
      EmitRow2("Interest Rate (%)<BR>(fixed loan)", nInputInterest + " %",
                nInputInterest + " %") ;
      EmitRow2("Average Monthly Interest",
               DollarFormat(nAvgInt1, 0, 2),
               DollarFormat(nAvgInt2, 0, 2)) ;
      EmitRow2("Total Interest",
               DollarFormat(nTotalInt1, 0, 2),
               DollarFormat(nTotalInt2, 0, 2)) ;
      EmitRow2("Loan Length in Months", iMonths1 + " Months",
                        iMonths2 + " Months") ;
      EmitRow2("Loan Length in Years", Format(nYears1, 0, 3) + " Years",
               Format(nYears2, 0, 3) + " Years") ;

      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 ;
}


//-->

</SCRIPT>

=eod

=data mortgage.loanlen.html
=html

=// <BODY onLoad="Initialize(document.appoform)">
<form name="appoform">

<TABLE width=500 cellpadding=0 cellspacing=0>

<TR><TD><font size=+2><b>Loan Length Calculator</b></font></TD></TR>

<TR><TD><BR><font size=-1>Enter a loan amount, how much
   you want to spend each month, and an interest rate,
   and find out how long it will take you to pay off 
   the loan!<br><br></font></TD></TR></TABLE>

<TABLE width=300 border=1 cellpadding=3 cellspacing=0>
 <TR>
    <TD><font size=-1><B>Beginning Balance of Loan</B></font></TD>
    <TD><INPUT name=appo0001 size=10 value=200000></TD></TR>
  <TR>
    <TD><font size=-1><B>Minimum Monthly Payment</B></font> </TD>
    <TD><INPUT name=appo0002 size=10 value=1500></TD></TR>
  <TR>
    <TD><font size=-1><B>Maximum Monthly Payment</B></font> </TD>
    <TD><INPUT name=appo0003 size=10 value=2000></TD></TR>
  <TR>
    <TD><font size=-1><B>Interest Rate (%) (fixed loan)</B></font> </TD>
    <TD><INPUT name=appo0004 size=10 value=8.25></TD></TR></TABLE><BR>

<TABLE width=300><TR align=center><TD><font size=-1>
         <input type=button value="Calculate Loan Length"
                onClick = "Execute(document.appoform)"></font>
      </TD></TR></TABLE>

</FORM>
=eod

=data mortgage.loanpay.js
=html

<SCRIPT LANGUAGE = "JavaScript">
<!--Hide
// Written by John J. Xenakis, http://www.jxenakis.com

var bDebug = 0 ;        // change to 1 for extra output
var sNl = "\r";
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 iTotalWidth = 70 ;
var iColumns = [5, 15, 15, 15, 15] ;
var sHeadings1 = ["#", "Payment", "Interest", "Principal", "Balance"] ;
var sHeadings2 = ["", "Amount", "Amount", "Reduction", "Due"] ;

function Execute(oForm)
{
   var iError = 0 ;
   var sOut = CenterString("Loan Repayment Schedule", iTotalWidth) + sNl
        + sDashes.substring(0, iTotalWidth) + sNl ;
   var nPrincipal = parseFloat(oForm.amt.value) ;
   var nInputPercent = parseFloat(oForm.per.value) ;
   var nPercent = nInputPercent / 1200 ;
   var iMonths = Math.round(parseFloat(oForm.years.value) * 12) ;
//   var iMonths = parseInt(oForm.years.value) * 12 ;
   var nPayment = parseFloat(oForm.payment.value) ;

// if (nPrincipal < 0 || nPercent < 0 || iMonths < 0 || nPayment < 0)
// {
//    iError = 100 ;
//    sOut += "Error: No fields may be negative." + sNl ;
// }

//   if (nPercent < 0 || iMonths < 0)
   if (iMonths < 0)
   {
      iError = 100 ;
      sOut += "Error: Percent and years may not be negative." + sNl ;
   }

   var iCount = (nPrincipal ? 1 : 0) + (nPercent ? 1 : 0)
                        + (iMonths ? 1 : 0) + (nPayment ? 1 : 0) ;
   if (iCount < 3)
   {
      iError = 110 ;
      sOut += "Error: At most one field must be set to zero." ;
   }

   var nX, nY, iCount = 25 ;

   if (!iError)
   {
      if (!iMonths)             // number of years not specified
      {
         if (nPercent * nPrincipal >= nPayment)
         {
            iError = 200 ;
            sOut += "Error: Loan payment amount is too small "
                        + "to repay loan." + sNl ;
         }

         else
         {
            iMonths = Math.floor(Math.log(1-nPercent*nPrincipal / nPayment)
                        / Math.log(1 - nPercent)) - 1 ;
            if (iMonths < 1)
               iMonths = 1 ;
            while (iCount-- > 0 && (nX = PresentValue(nPayment,
                                        nPercent, iMonths)) < nPrincipal)
            {
               if (bDebug)
                  sOut += "iMonths, nX: " + iMonths + ", " + nX + sNl ;
               ++iMonths ;
            }
         }
      }

      if (!nPercent)
      {
         var nX = 1 ;
         if (PresentValue(nPayment, 0, iMonths) < nPrincipal)
         {
            iError = 210 ;
            sOut += "Error: Loan payment amount is too small "
                        + "to repay loan." + sNl ;
         }

         else
         {
            while (iCount-- > 0 && Math.abs(nPrincipal
                - (nPv = PresentValue(nPayment, nX, iMonths))) > .005)
            {
               if (bDebug)
                  sOut += "nX, nPv: " + nX + ", " + nPv + sNl ;
               nX *= 1 + (nPv - nPrincipal) / nPrincipal ;
            }

            if (bDebug)
               sOut += "Exit: nX, nPv: " + nX + ", " + nPv + sNl ;

            nInputPercent = Round(1200 * nX - .00005, 4) ;
            nPercent = nInputPercent / 1200 ;
         }
      }

      if (!nPrincipal)
      {
         nPrincipal = PresentValue(nPayment, nPercent, iMonths) ;
         nPrincipal = Round(nPrincipal, 2) ;
      }

      if (!nPayment)
      {
         nPayment = nPrincipal / PresentValue(1, nPercent, iMonths) ;
         nPayment = Round(nPayment + .005, 2) ;
      }
   }

   if (!iError && iMonths > 12000)
   {
      iError = 250 ;
      sOut += "Error: Can't print out schedule for more than "
                        + "12000 months." + sNl ;
   }

   if (!iError)
   {
      sOut += "Repay $" + nPrincipal + " at " + nInputPercent
                + "% annual interest in "
                + iMonths + " months:" + sNl
                + "     Monthly Payment = $" + nPayment
                + sNl + sDashes.substring(0,iTotalWidth) + sNl ;

      for (var iRow = 1 ; iRow <= 2 ; ++iRow)
      {
         for (var iCol = 0 ; iCol < 5 ; ++iCol)
            sOut += PrepadString( iRow == 1 ? sHeadings1[iCol]
                     : sHeadings2[iCol], iColumns[iCol]) ;
         sOut += sNl ;
      }

      sOut += sDashes.substring(0, iTotalWidth) + sNl ;

      var nInterest, nReduction, nBalance = nPrincipal ;
      var nFraction = 0, nTotalInterest = 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 ;
         nTotalInterest += nInterest ;

         sOut += Format(iK, iColumns[0], 0)
                        + DollarFormat(nPayment, iColumns[1], 2) ;
         sOut += DollarFormat(nInterest, iColumns[2], 2) ;
         sOut += DollarFormat(nReduction, iColumns[3], 2) ;
         sOut += DollarFormat(nBalance, iColumns[4], 2) + sNl ;
      }

      sOut += sNl + "Future value of loan amount = "
                + DollarFormat(FutureValue(nPrincipal,
                                nPercent, iMonths), 0, 2) + sNl ;
      sOut += sNl + "Total interest paid = "
                + DollarFormat(nTotalInterest, 0, 2) ;
   }

// oForm.sched.rows = 1000 ;
   oForm.sched.rows = iError ? 20 : 
                (iMonths < 500) ? (iMonths + 12) : 500 ;
// alert(oForm.sched.ROWS) ;
// alert(oForm.sched.toString()) ;
   oForm.sched.value = sOut ;

}

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 FutureValue(nPayment, nPercent, iNumPeriods)
{
   var nAmount = nPayment * ExpN(1 + nPercent, 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 ;
}

// 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 ;
   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 ;
}

//-->

</SCRIPT>
=eod

=data mortgage.loanpay.html
=text
<h1>Quick Loan Calculator</h1>

This calculator lets you make a number of different loan calculations
quickly, one after another.

A loan calculation involves the following four values:

<ul><li>Loan Length -- number of years</li>
<li>Loan Amount -- number of dollars</li>
<li>Interest Rate -- rate at which loan is compounded annually</li>
<li>Monthly Payment -- dollar amount to repay loan in the specified
time.</li></ul>

This calculator can compute any one of the above four values.  In the
form below, specify any three of the values, and specify 0 for the
fourth value.  The calculator computes the fourth value, and prints
out a loan amortization table.

If you prefer, you can specify values for all four fields,  In that
case, the calculator will simply print a loan amortization table
based on those values.

=html

<FORM NAME = "oInForm">
<TABLE WIDTH = 486 BORDER = 0>
<TR><TD COLSPAN = 4 VALIGN = TOP ALIGN = CENTER>
</TD>

<TR>
   <TD ALIGN = CENTER><BR>Number of Years of Loan<BR></TD>
   <TD ALIGN = CENTER><BR>Compound Annual Interest Rate<BR></TD>
   <TD ALIGN = CENTER><BR>Total Amount of Loan<BR></TD>
   <TD ALIGN = CENTER><BR>Monthly Payment<BR></TD>
</TR>

<TR>
   <TD ALIGN = CENTER><INPUT TYPE = TEXT NAME = "years"
                                VALUE = "30" SIZE = 5><BR></TD>
   <TD ALIGN = CENTER><INPUT TYPE = TEXT NAME = "per"
                                VALUE = "8.25" SIZE = 6>%<BR></TD>
   <TD ALIGN = CENTER>$<INPUT TYPE = TEXT VALUE = "100000"
                        NAME = "amt" SIZE = 12><BR></TD>
   <TD ALIGN = CENTER>$<INPUT TYPE = TEXT VALUE = "0"
                        NAME = "payment" SIZE = 12><BR></TD>
</TR>

<TR>

<TD COLSPAN = 4 ALIGN = CENTER>
<INPUT TYPE = "button"
                VALUE = "Click Here to Get Loan Repayment Schedule"
                onClick = "Execute(document.oInForm)"><BR>


<BR><TEXTAREA NAME = "sched" ROWS = 20 COLS = 75>
</TEXTAREA></TD></TR>
</TABLE>

=eod

=data mortgage.monpay.js
=html

=//<script src='monpay.js'></script>

=//<script
=//src='http://www.jxenakis.com/cgi-local/D.PL?d=mortgage.monpay.jsinsert&content='>
=//</script>

<SCRIPT LANGUAGE = "JavaScript">
<#include mortgage.monpay.jsinsert#>
</script>

=eod

=data mortgage.monpay.html
=html


<h1>Monthly Payment Calculator</h1>

<form name='apppform'>

<TABLE width=500 cellpadding=0 cellspacing=0>

=//<TR><TD><img src='gifs/mort_pay_calc.gif'
=// width=353 height=25></TD></TR>


<TR><TD><font size=-1><BR>This worksheet has been
created to help you calculate a monthly 
payment.<BR>Please enter the loan amount,
the payment term in years, and the 
interest rate.<br><br></font></TD></TR></TABLE>


<TABLE width=300 border=1 cellpadding=3 cellspacing=0>
  <TR>
    <TD colSpan=2><font size=-1><B>(Note: Please enter
                only numbers)</B></font></TD></TR>
  <TR>
    <TD width=150><font size=-1><B>Loan Amount</B>
                                </font></TD>
    <TD width=150><INPUT name=appp0001 value=200000
                        size=10></TD></TR>
  <TR>
    <TD width=150><font size=-1><B>Term (in years)</B>
                                        </font></TD>
    <TD width=150><INPUT name=appp0002 size=10 value=30>
                                </TD></TR>
  <TR>
    <TD width=150><font size=-1><B>Interest Rate</B>
                                        </font></TD>
    <TD width=150><INPUT name=appp0003 size=10 value=8.25>
                        </TD></TR></TABLE><BR>

<TABLE width=300>
<TR align=center><TD><font size=-1>
<input type=button value='Calculate Monthly Payment'
        onClick = 'Execute(document.apppform)'>
        </font></TD></TR></TABLE>
</FORM>

=eod

=data mortgage.monpay.jsinsert
=html
<!--Hide
// Written by John J. Xenakis, http://www.jxenakis.com

var bDebug = 0 ;        // change to 1 for extra output
var sNl = "\r";
var sOut = "" ;         // html output string
var nErrorNumber = -1.2345e-12 ;

var sSub = "%s" ;

var sScr = "http://www.jxenakis.com/cgi-local/D.PL?"
    + "d=mortgage.monpay.jsinsert&content=" ;

var shStart = "<html>" + sNl
      + "<head><title>%s</title>" + sNl
      + "<style type='text/css'>" + sNl
      + "<!--" + sNl
      + "/* make all text the same font */" + sNl
      + "TD {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "TH {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "-->" + sNl
      + "</style>" + sNl
=//      + "<scr" + "ipt src='monpay.js'></scr" + "ipt>" + sNl
      + "<scr" + "ipt src='" + sScr + "'></scr" + "ipt>" + sNl


      + "</head>" + sNl
      + "<body>" + sNl
      + "<BASEFONT FACE='Arial, Helvetica, sans-serif'>" ;

var shHeading1 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<TR><TD><br>"
      + "<font size=6><b>%s</b></font></TD></TR></TABLE><br>" ;

var shHeadGif = "<TABLE width=580 cellpadding=0 cellspacing=0>" + sNl
      + "<TR><TD><img src='%s' " + sNl
      + "width=448 height=25></TD></TR></TABLE>" ;

var shHeading2 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<br><TR align=center><TD>"
      + "<font size='+0'><b>%s</b></font></TD></TR></TABLE><br>" ;

var shBeginTable1 =
      "<TABLE width=300 border=1 cellpadding=3 cellspacing=0><BR>" ;
var shEndTable = "</table>" ;

var shBeginRow = "<tr>" ;
var shEndRow = "</tr>" ;


var shCell1 = "<td width=150><font size=-1><B>%s</B></font></td>" ;
var shCell2 = "<td width=150><font size=-1>%s</font></td>" ;

function EmitRow1(s1, s2)
{
   Emit(shBeginRow) ;
   Emit1(shCell1, s1) ;
   Emit1(shCell2, s2) ;
   Emit(shEndRow) ;
}


var shSelf =  
     "<TABLE width=300 cellpadding=0 cellspacing=0>" + sNl
   + "<tr><td><font size=-1><br>To recalculate the" + sNl
   + "scenario, enter new numbers and click on the" + sNl
   + "'Re-Calculate' button below:<br></font></td></tr>" + sNl
   + "</TABLE>" + sNl
   + "<form name='apppform'>" + sNl
   + "<TABLE width=300 border=1 cellpadding=3 cellspacing=0>" + sNl
   + "  <TR>" + sNl
   + "    <TD colSpan=2><font size=-1><B>(Note: Please enter" + sNl
   + "          only numbers)</B></font></TD></TR>" + sNl
   + "  <TR>" + sNl
   + "    <TD width=150><font size=-1><B>Loan Amount</B>" + sNl
   + "                          </font></TD>" + sNl
   + "    <TD width=150><INPUT name=appp0001" + sNl
   + "                  size=10></TD></TR>" + sNl
   + "  <TR>" + sNl
   + "    <TD width=150><font size=-1><B>Term (in years)</B>" + sNl
   + "                                  </font></TD>" + sNl
   + "    <TD width=150><INPUT name=appp0002 size=10>" + sNl
   + "                          </TD></TR>" + sNl
   + "  <TR>" + sNl
   + "    <TD width=150><font size=-1><B>Interest Rate</B>" + sNl
   + "                                  </font></TD>" + sNl
   + "    <TD width=150><INPUT name=appp0003 size=10>" + sNl
   + "                  </TD></TR></TABLE><BR>" + sNl

   + "<TABLE width=300>" + sNl
   + "<TR align=center><TD><font size=-1>" + sNl
   + "<input type=button value='Re-Calculate'" + sNl
   + "  onClick = 'Execute(document.apppform)'>" + sNl
   + "  </font></TD></TR></TABLE>" + sNl
   + "</FORM>" ;




function Execute(oForm)
{
   var iError = 0 ;
   var nPrincipal, nInputInterest, iYears ;

   if ( (nPrincipal = Validate(oForm.appp0001.value, 0)) == nErrorNumber)
      iError = 100 ;
   else if ( (iYears = Validate(oForm.appp0002.value, 1)) == nErrorNumber)
      iError = 110 ;
   else if ( (nInputInterest = Validate(oForm.appp0003.value, 0)) == nErrorNumber)
      iError = 120 ;

   var iPpy = 12 ;
   var iMonths = 12 * iYears ;
   var iPeriods = iPpy * iYears ;
   var nPercent = nInputInterest / iPpy / 100 ;

   var nPayment = 0 ;


   if (!iError)
   {
      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 number of years" ;
      else if (iYears < 0)
         sMessage = "Number of years must be positive" ;
//    else if (iYears > 30)
//       sMessage = "Amortization length must be 30 years or less" ;
      if (sMessage != "")
      {
         iError = 170 ;
         alert(sMessage) ;
      }
   }

   if (!iError)
   {
      nPayment = nPrincipal / PresentValue(1, nPercent, iPeriods) ;
      nPayment = Round(nPayment + .005, 2) ;

      Emit1(shStart, "Mortgage Payment Results") ;

      Emit1(shHeading1, "Amortization Schedule Results") ;
//      Emit1(shHeadGif, "gifs/mort_pay_calc_results.gif") ;

//    Emit1(shHeading2, "MORTGAGE INFORMATION") ;

      Emit(shBeginTable1) ;
      EmitRow1("Loan Amount", DollarFormat(nPrincipal, 0, 2)) ;
      EmitRow1("Interest Rate", nInputInterest + " %") ;
      EmitRow1("Loan Term", iYears + " Years") ;
      EmitRow1("Monthly Payment", DollarFormat(nPayment, 0, 2)) ;

      Emit(shEndTable) ;
//      Emit(shSelf) ;
   }

   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 ;
}


//-->
=eod







=data mortgage.paytab.js
=html

<SCRIPT LANGUAGE = "JavaScript">
<!--Hide
// Written by John J. Xenakis, http://www.jxenakis.com

// set the number of rows and columns in the generated table
var iColumns = 9, iRows = 21 ;

// set a flag to indicate if row and column headings should
// be rounded
var bRoundedHeadings = 1 ;

var bDebug = 0 ;        // change to 1 for extra output
var sNl = "\r";
var sOut = "", sDump = "" ;         // html output string
var nErrorNumber = -1.2345e-12 ;

var bHtml = 1 ;         // Html versus Text test

var sSub = "%s" ;

var shStart = "<html>" + sNl
      + "<head><title>%s</title>" + sNl
      + "<style type='text/css'>" + sNl
      + "<!--" + sNl
      + "/* make all text the same font */" + sNl
      + "TD {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "TH {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "-->" + sNl
      + "</style>" + sNl
      + "</head>" + sNl
      + "<body>" + sNl
      + "<BASEFONT FACE='Arial, Helvetica, sans-serif'>" ;

var shHeading1 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<TR><TD><br>"
      + "<font size=6><b>%s</b></font></TD></TR></TABLE><br>" ;

var shHeadGif = "<TABLE width=500 cellpadding=0 cellspacing=0>" + sNl
      + "<TR><TD><img src='%s' " + sNl
      + "width=389 height=24></TD></TR></TABLE>" ;

var shHeading2 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<br><TR><TD align=left>"
      + "<font size=+2><b>%s</b></font></TD></TR></TABLE>" ;

var shBeginTable1 =
      "<TABLE width=500 border=0 cellpadding=0 cellspacing=0><BR>" ;
var shBeginTable2 =
      "<TABLE width=500 border=1 cellpadding=3 cellspacing=0><BR>" ;
var shBeginTable2Text = "<pre>" ;
var shEndTable = "</table>" ;
var shEndTableText = "</pre>" + sNl ;

var shBeginRow1 = "<tr>" ;
var shBeginRow2 = "<tr align=center>" ;
var shEndRow = "</tr>" ;

var shBeginRow1Text = "" ;
var shBeginRow2Text = "" ;
var shEndRowText = sNl ;

var shCell1 = "<TD><font size=-1><B>&nbsp;&nbsp;&nbsp;&nbsp;"
            + "&nbsp;&nbsp;&nbsp;&nbsp;%s</B></font></TD>" ;
var shCell2 = "<TD><font size=-1><B>%s</B></font></TD>" ;

var shCell3 = "<TD align=right><font size=-1><B>%s</B></font></TD>" ;
var shCell4 = "<TD align=right><font size=-1>%s</font></TD>" ;

function EmitRow1(s1)
{
//   if (bHtml)
//   {
      Emit(shBeginRow1) ;
      Emit1(shCell1, s1) ;
      Emit(shEndRow) ;
//   }
//   else
//      Emit(LineFormat([s1, s2], ['l40', 'l30'])) ;
}

function EmitData(sBlank, s1, nVal)
{
   if (bHtml)
      Emit1(s1, nVal)
   else
      Emitt(DataFormat(sBlank, nVal, 'r8')) ;
}

function Execute(oForm)
{
   var iError = 0 ;
   var nMinP, nMaxP, nMinI, nMaxI, iYears ;

   bHtml = parseInt(oForm.appq0020.options[
                oForm.appq0020.selectedIndex].value) == 0 ;

   if ( (nMinP = Validate(oForm.appq0001.value, 0)) == nErrorNumber)
      iError = 100 ;
   else if ( (nMaxP = Validate(oForm.appq0002.value, 0)) == nErrorNumber)
      iError = 100 ;
   else if ( (nMinI = Validate(oForm.appq0003.value, 0)) == nErrorNumber)
      iError = 100 ;
   else if ( (nMaxI = Validate(oForm.appq0004.value, 0)) == nErrorNumber)
      iError = 100 ;
   else if ( (iYears = Validate(oForm.appq0005.value, 1)) == nErrorNumber)
      iError = 140 ;

   var iPpy = 12 ;
   var iMonths = 12 * iYears ;
   var iPeriods = iPpy * iYears ;

   if (!iError)
   {
      var sMessage = "" ;
      if (nMinP == 0)
         sMessage = "Please enter a minimum principal amount" ;
      else if (nMinP < 0)
         sMessage = "Minimum principal amount must be positive" ;
      else if (nMaxP == 0)
         sMessage = "Please enter a maximum principal amount" ;
      else if (nMaxP < 0)
         sMessage = "Maximum principal amount must be positive" ;
      else if (nMinP >= nMaxP)
         sMessage = "Minimum principal amount must be smaller "
                + "than maximum principal amount" ;
      else if (nMinP + 100 > nMaxP)
         sMessage = "Minimum principal amount must be at least"
                + " $100 smaller "
                + "than maximum principal amount" ;
      else if (nMinI == 0)
         sMessage = "Please enter a minimum interest rate" ;
      else if (nMinI < 0)
         sMessage = "Minimum interest rate must be positive" ;
      else if (nMaxI == 0)
         sMessage = "Please enter a maximum interest rate" ;
      else if (nMaxI < 0)
         sMessage = "Maximum interest rate must be positive" ;
      else if (nMinI >= nMaxI)
         sMessage = "Minimum interest rate must be smaller "
                + "than maximum interest rate" ;
      else if (nMinI + .01 > nMaxI)
         sMessage = "Minimum interest rate must be at least"
                + " 0.01% smaller "
                + "than maximum interest rate" ;
      else if (iYears == 0)
         sMessage = "Please enter length of loan (in years)" ;
      else if (iYears < 0)
         sMessage = "Length of loan (in years) must be positive" ;
      if (sMessage != "")
      {
         iError = 170 ;
         alert(sMessage) ;
      }
   }

   if (!iError)
   {
      var iR, iC ;
      var nInterests = new Array(iColumns) ;
      var nPrincipals = new Array(iRows) ;

//    bRoundedHeadings = 0 ;    // ***** temp
      if (bRoundedHeadings)
      {
         Heads(nMinI, nMaxI, nInterests) ;
         Heads(nMinP, nMaxP, nPrincipals) ;
      }
      else
      {
         for (iR = 0 ; iR < iRows ; ++iR)
            nPrincipals[iR] = Round(nMinP + iR * (nMaxP - nMinP)
                                        / (iRows - 1), 0) ;

         for (iC = 0 ; iC < iColumns ; ++iC)
            nInterests[iC] = Round(nMinI + iC * (nMaxI - nMinI)
                                / (iColumns - 1), 3) ;
      }
      
      Emit1(shStart, "Mortgage Payment Table Results") ;

//    Emit1(shHeadGif, "gifs/mort_table_results.gif") ;

      Emit1(shHeading2, "Mortgage Payment Table Results") ;

      Emit(shBeginTable1) ;
      EmitRow1("Monthly Payment Comparison Table") ;
      EmitRow1("Based on " + iYears + " Year Fixed Rate Mortgage") ;
      EmitRow1("Interest Rate Range: " + nMinI + " % to "
        + nMaxI + " %") ;
      EmitRow1("Principal Amount Range: $" + nMinP + " to $"
        + nMaxP) ;
      Emit(shEndTable) ;

      Emit(bHtml ? shBeginTable2 : shBeginTable2Text) ;
      bHtml ? Emit(shBeginRow1) : Emitt(shBeginRow1Text) ;
      EmitData(0, shCell2, bHtml ? "&nbsp;" : " ") ;
//      Emit1(shCell2, "&nbsp;") ;
      for (iC = 0 ; iC < iColumns ; ++iC)
//       Emit1(shCell2, Format(nInterests[iC], 0, 3)) ;
         EmitData(1, shCell2, Format(nInterests[iC], 0, 3)) ;
//      Emit(shEndRow) ;
      bHtml ? Emit(shEndRow) : Emitt(shEndRowText) ;

      for (iR = 0 ; iR < iRows ; ++iR)
      {
         bHtml ? Emit(shBeginRow1) : Emitt(shBeginRow1Text) ;
//       Emit(shBeginRow1) ;
         EmitData(0, shCell3, Format(nPrincipals[iR], 0, 0)) ;
//       Emit1(shCell3, Format(nPrincipals[iR], 0, 0)) ;
         for (iC = 0 ; iC < iColumns ; ++iC)
         {
            nPayment = nPrincipals[iR] / PresentValue(1,
                                nInterests[iC]/1200, iMonths) ;
            nPayment = Round(nPayment + .005, 2) ;
//          Emit1(shCell4, Format(nPayment, 0, 2)) ;
            EmitData(1, shCell4, Format(nPayment, 0, 2)) ;
         }
         bHtml ? Emit(shEndRow) : Emitt(shEndRowText) ;
//       Emit(shEndRow) ;
      }
      Emit(bHtml ? shEndTable : shEndTableText) ;
   }

   if (!iError)
   {
    if (bDebug && sDump != "")
         sOut += sNl + "<pre>" + sDump + "</pre>" ;
//    document.open() ;
      document.write(sOut) ;
      document.close() ;

   }

}



function Heads(nMin, nMax, nHeads)
{
   var iCount = nHeads.length ;
   var nDiff = nMax - nMin, nInterval, nLower, nUpper ;
   var iPow = -Math.floor(Math.log(nDiff)/Math.log(10)) - 2 ;
   var bIterate = true ;
   var iSlots, iMinSlot, iK, iValues ;
   while (bIterate)
   {
      nInterval = ExpN(10, -iPow) ;
      iSlots = iCount ;
      iMinSlot = 0 ;
      nLower = Round2(nMin-ExpN(10, -iPow-5), iPow, 1) ;
      nUpper = Round2(nMax+ExpN(10, -iPow-5), iPow, -1) ;
      if (nLower != Round2(nMin, iPow + 5, 0))
         --iSlots, ++iMinSlot, nHeads[0] = nMin ;
      if (nUpper != Round2(nMax, iPow + 5, 0))
         --iSlots, nHeads[iCount-1] = nMax ;
      iValues = Math.round((nUpper - nLower)/nInterval) + 1 ;
      if (iValues < iSlots)
         ++iPow ;
      else
      {
         for (iK = 0 ; iK < iSlots ; ++iK)
            nHeads[iK+iMinSlot] = nLower + nInterval
                  * Math.round((iValues-1) * iK / (iSlots-1)) ;
         bIterate = false ;
      }
   }
}

function Dump(sStr) { if (bDebug) sDump += sStr + sNl ;}

function Emit(sStr) { sOut += sStr + sNl ; }

function Emitt(sStr) { sOut += sStr ; }

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 Round2(nVal, iD, iType)
// iType: -1 = floor, 0 = round, +1 = ceil
{
   var nPow = Power(iD) ;
   nVal = Math.round(nVal * nPow + iType * .5) / nPow ;
// alert(nVal + "/" + iD + "/" + iType + "/" + nPow + "/" + nVal) ;
   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 ;
}

=inc mortgage.com.lineformat.js

//-->

</SCRIPT>

=eod

=data mortgage.paytab.html
=html

=//<BODY bgcolor=#FFFFFF>
<form name=appqform>

<TABLE border=0 cellpadding=0 cellspacing=0 width=460>
<TR><TD align=left><font size=+2><b>Mortgage Payment Table Calculator</b></font>
                </TD></TR>
<TR><TD><BR><font size=-1>This calculator will create a
        table that will compare the monthly mortgage payment
        for a range of interest rates and principal amounts.
        </font></TD></TR></TABLE><BR>

<TABLE width=300 border=1 cellpadding=3 cellspacing=0>
  <TR>
    <TD><font size=-1><B>Value</B></font></TD>
    <TD><font size=-1><B>Minimum</B></font></TD>
    <TD><font size=-1><B>Maximum</B></font></TD></TR>
  <TR>
    <TD><font size=-1><B>Principal</B></font></TD>
    <TD><INPUT name=appq0001 size=10 value=200000></TD>
    <TD><INPUT name=appq0002 size=10 value=210000></TD></TR>
  <TR>
    <TD><font size=-1><B>Interest</B></font></TD>
    <TD><INPUT name=appq0003 size=10 value=8.00></TD>
    <TD><INPUT name=appq0004 size=10 value=8.25></TD></TR></TABLE><BR>

<TABLE width=300 border=1 cellpadding=3 cellspacing=0>
<TR><TD align=center><font size=-1><B>Number of Years&nbsp;&nbsp;
        </B></font><INPUT name=appq0005 size=3 value=30> 
    <TD><font size="-1">Format
        <SELECT name=appq0020 size=1>
            <OPTION value=0 selected>HTML</OPTION> 
            <OPTION value=1>Text</OPTION>
        </SELECT></font></TD>
        </TR></TABLE><BR>

<TABLE width=300><TR align=center><TD><font size=-1>
<input type=button value="Display Payment Table"
                onClick = "Execute(document.appqform)">
               </font></TD></TR></TABLE>

</FORM>

=eod


=data mortgage.howhou.js
=html


<SCRIPT LANGUAGE = "JavaScript">
<!--Hide
// Written by John J. Xenakis, http://www.jxenakis.com

var bDebug = 0 ;        // change to 1 for extra output
var bZeroOk = 0 ;       // zero tax/insurance is ok
var bSuppressChecking = 0 ;     // suppress some error checking
var sNl = "\r";
var sOut = "", sDump = "" ;         // html output string
var nErrorNumber = -1.2345e-12 ;

var sSub = "%s" ;

var shStart = "<html>" + sNl
      + "<head><title>%s</title>" + sNl
      + "<style type='text/css'>" + sNl
      + "<!--" + sNl
      + "/* make all text the same font */" + sNl
      + "TD {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "TH {FONT-FAMILY: Arial, Helvetica, sans-serif} " + sNl
      + "-->" + sNl
      + "</style>" + sNl
      + "</head>" + sNl
      + "<body>" + sNl
      + "<BASEFONT FACE='Arial, Helvetica, sans-serif'>" ;


var shHeading1 = "<TABLE border=0 cellpadding=0 cellspacing=0 "
         + "width=525>" + sNl
         + "<TR><TD><font size=+2><B>%s</B><BR><BR></TD></TR></TABLE>" ;

var shHeadGif = "<TABLE width=500 cellpadding=0 cellspacing=0>" + sNl
      + "<TR><TD><img src='%s' " + sNl
      + "width=471 height=24></TD></TR></TABLE>" ;

var shHeading2 =  "<TABLE border=0 cellpadding=0 "
                + "cellspacing=0 width=500>" + sNl
      + "<TR><TD>"
      + "<font size=+2><b>%s</b></font></TD></TR></TABLE>" ;

var shBeginTable1 =
      "<TABLE border=1 cellpadding=3 cellspacing=0 width=500>" ;
var shBeginTable2 =
      "<TABLE border=0 cellpadding=0 cellspacing=0 width=500><BR>" ;

var shEndTable = "</table>" ;

var shBeginRow1 = "<tr>" ;
var shBeginRow2 = "<tr align=center>" ;
var shEndRow = "</tr>" ;

var shCell1 = "<TD align=right><font size=-1><B>%s</B></font></TD>" ;
var shCell2 = "<TD><font size=-1><B>%s</B></font></TD>" ;
var shCell3 = "<TD align=right><font size=-1>%s</font></TD>" ;

function EmitRow1(s1)
{
   Emit(shBeginRow1) ;
   Emit1(shCell1, "&nbsp;") ;
   Emit1(shCell1, s1) ;
// Emit1(shCell1, s2) ;
   Emit(shEndRow) ;
}

function EmitRow2D(s1, s2)
{
   Emit(shBeginRow1) ;
   Emit1(shCell2, s1) ;
   Emit1(shCell3, DollarFormat(s2, 0, 2)) ;
// Emit1(shCell3, DollarFormat(s3, 0, 2)) ;
   Emit(shEndRow) ;
}

function EmitRow2P(s1, s2)
{
   Emit(shBeginRow1) ;
   Emit1(shCell2, s1) ;
   Emit1(shCell3, s2 + " %") ;
// Emit1(shCell3, s3 + " %") ;
   Emit(shEndRow) ;
}

var shLongText = ""


   + "<TR><TD><font size=-1>The <B>maximum home price" + sNl
   + "</B> is the amount that you can afford based on" + sNl
   + "the following housing expense, long term debt, and cash-available" + sNl 
   + "guidelines that are used by lenders to determine the mortgage amount" + sNl
   + "they will lend to a home buyer:  <ul><li>Housing expenses" + sNl
   + "(monthly mortgage principal and interest, plus monthly property tax" + sNl
   + "and home insurance costs (also known as \"PITI\"), plus the monthly PMI" + sNl
   + "payment (if applicable)) should not exceed 28 percent of the homeowner's" + sNl
   + "gross monthly income.<br><br><li>Housing expenses (monthly mortgage principal" + sNl
   + "and interest, plus monthly property tax and home insurance costs (also" + sNl
   + "known as \"PITI\"), plus the monthly PMI payment (if applicable)) PLUS all" + sNl
   + "other monthly debt payments should not exceed 36 percent of the homeowner's" + sNl
   + "gross monthly income.<br><br><li>An estimate" + sNl
   + "of your maximum loan amount can be determined by dividing" + sNl 
   + "your available cash by the minimum total percentage of the" + sNl 
   + "home price that will need to go toward the down payment" + sNl 
   + "and all closing costs.</ul></font></TD></TR>" + sNl + sNl

   + "<TR><TD><font size=-1><br>A <B>monthly PMI payment</B>" + sNl
   + "appears above if your down payment is less than 20% of" + sNl
   + "your home value. That means you must pay an extra .7%" + sNl
   + "of your beginning loan balance per year for private" + sNl
   + "mortgage insurance (\"PMI\").  In many cases, the lender" + sNl
   + "will allow cancellation of PMI when the loan is paid" + sNl
   + "down to 80% of the original property" + sNl
   + "value.</font></TD></TR>" + sNl + sNl

   + "<TR><TD><font size=-1><BR>Note: For this calculation," + sNl
   + "we assume a minimum down payment of 5% and use estimated closing costs" + sNl
   + "of 2% of the loan amount.</font></TD></TR>" + sNl + sNl ;
 

function Initialize(oForm)
{
    if (0 && bDebug)
    {
        oForm.appm0001.value = 65714 ;
        oForm.appm0002.value = 1000 ;
        oForm.appm0003.value = 20000 ;
        oForm.appm0004.value = 2 ;
        oForm.appm0005.value = 1.5 ;
        oForm.appm0006.value = 8.5 ;
//      oForm.appm0007.value =  ;
//      oForm.appm0008.value =  ;
    }
}

function Execute(oForm)
{
   var iError = 0 ;

   var nAnnualIncome, nMonthlyDebt, nCash, nDownPc ;
   var nPropertyTaxPc, nInsurancePc, nInputInterest, iYears ;

   if ( (nAnnualIncome = Validate(oForm.appm0001.value, 0)) == nErrorNumber)
      iError = 100 ;

   else if ( (nMonthlyDebt = Validate(oForm.appm0002.value, 0)) == nErrorNumber)
      iError = 110 ;

   else if ( (nCash = Validate(oForm.appm0003.value, 0)) == nErrorNumber)
      iError = 120 ;

// else if ( (nDownPc = Validate(oForm.appm0004.value, 0)) == nErrorNumber)
//    iError = 130 ;

   else if ( (nPropertyTaxPc = Validate(oForm.appm0004.value, 0)) == nErrorNumber)
      iError = 140 ;

   else if ( (nInsurancePc = Validate(oForm.appm0005.value, 0)) == nErrorNumber)
      iError = 150 ;

   else if ( (nInputInterest = Validate(oForm.appm0006.value, 0)) == nErrorNumber)
      iError = 160 ;

   else if ( (iYears = Validate(oForm.appm0007.value, 1)) == nErrorNumber)
      iError = 170 ;

   if (!iError)
   {
      var sMessage = "" ;
      if (nAnnualIncome == 0)
         sMessage = "Please enter Gross Annual Income" ;
      else if (nAnnualIncome < 0)
         sMessage = "Gross Annual Income must be positive" ;
//    else if (nMonthlyDebt == 0)
//       sMessage = "Please enter Monthly Debt Payments" ;
      else if (nMonthlyDebt < 0)
         sMessage = "Monthly Debt Payments must be zero or positive" ;
      else if (nCash == 0)
         sMessage = "Please enter Cash Available" ;
      else if (nCash < 0)
         sMessage = "Cash Available must be positive" ;
//    else if (nDownPc == 0)
//       sMessage = "Please enter Desired Down Payment Percentage" ;
//    else if (nDownPc < 3)
//       sMessage = "Desired Down Payment Percentage must be at least 3%" ;
      else if (!bZeroOk && nPropertyTaxPc == 0)
         sMessage = "Please enter Property Tax Rate" ;
      else if (nPropertyTaxPc < 0)
         sMessage = "Property Tax Rate must be positive" ;
      else if (!bZeroOk && nInsurancePc == 0)
         sMessage = "Please enter Home Insurance Rate" ;
      else if (nInsurancePc < 0)
         sMessage = "Home Insurance Rate must be positive" ;
      else if (nInputInterest == 0)
         sMessage = "Please enter Interest Rate" ;
      else if (nInputInterest < 0)
         sMessage = "Interest Rate must be positive" ;
      else if (iYears == 0)
         sMessage = "Please enter Length of Loan (in years)" ;
      else if (iYears < 0)
         sMessage = "Length of Loan (in years) must be positive" ;

      if (sMessage != "")
      {
         iError = 190 ;
         alert(sMessage) ;
      }
   }

   if (!iError)
   {
      var iK, nLoanMp, nPmiMp, nFactor ;
      var bC36, bReject ;
      var nClosePc = .02, nMaxLoanPc = .97 ;
      var nNumer, nDenom ;
      var nPayment, nLenderPayment, nDebtPayment, nPmiPayment ;
      var iMonths = 12 * iYears ;
      var nPaymentMp = 1 / PresentValue(1, nInputInterest/1200,
                                iMonths) ;
      var bV28 = false, bV36 = false ;

      var nQPrice = nErrorNumber, nQLoan = 0, nQDown = 0, nQDownPc = 0 ;
      var nQClose = 0, nQPayment = 0, nQTin = 0, nQPmi = 0 ;

      for (iK = 0 ; iK < 4 ; ++iK)
      {
         nPmiMp = (iK % 2) ? 0 : .7/1200 ;
         bC36 = (iK < 2) ;
         nFactor = (bC36 ? 36 : 28) / 1200 ;
         nNumer = nAnnualIncome * nFactor
                         - (bC36 ? nMonthlyDebt : 0)
                                + (nPaymentMp + nPmiMp) * nCash ;
         nDenom = (nPaymentMp + nPmiMp) * (1 + nClosePc)
                        + (nPropertyTaxPc + nInsurancePc) / 1200 ;
         nQMax = (nDenom == 0) ? nCash / (1 + nClosePc)
                                        : nNumer / nDenom ;
         nLoanPc = 1 + nClosePc - nCash / nQMax ;
         if (!bSuppressChecking && nLoanPc < 0)
         {
            nNumer = nAnnualIncome * nFactor
                         - (bC36 ? nMonthlyDebt : 0) ;
            nDenom = (nPropertyTaxPc + nInsurancePc) / 1200 ;
            nQMax = (nDenom == 0) ? nCash / (1 + nClosePc)
                                        : nNumer / nDenom ;
            nLoanPc = 0 ;
         }
         if (!bSuppressChecking && nLoanPc > nMaxLoanPc && nPmiMp > 0)
         {
            nQMax = nCash / (1 - nMaxLoanPc + nClosePc) ;
            nLoanPc = nMaxLoanPc ;
         }
         bReject = (nLoanPc >= .80) != (nPmiMp > 0)
                        || (!bSuppressChecking && nLoanPc > nMaxLoanPc) ;

         if (!bReject && ((nQPrice == nErrorNumber) || nQMax < nQPrice))
         {
            nQPrice = Round(nQMax, 2) ;
            nQLoan = Round(nQPrice * nLoanPc, 2) ;
            nQDown = nQPrice - nQLoan ;
            nQDownPc = Round(nQDown / nQPrice * 100, 2) ;
            nQClose = Round(nQPrice * nClosePc, 2) ;
            nQPayment = nQLoan / PresentValue(1,
                                nInputInterest/1200, iMonths) ;
            nQTin = (nPropertyTaxPc + nInsurancePc) / 1200
                                        * nQPrice ;
            nQPmi = Round(nQLoan * nPmiMp, 2) ;
         }
         if (!bReject)
            if (bC36) bV36 = true ; else bV28 = true ;
                                
         nPrincipal = nQMax * nLoanPc ;
         nPayment = nPrincipal / PresentValue(1,
                                nInputInterest/1200, iMonths) ;
         nLenderPayment = nPayment + nQMax * (nPropertyTaxPc
                                + nInsurancePc) / 1200 ;
         nDebtPayment = nLenderPayment + nMonthlyDebt ;

         nPmiPayment = nPmiMp * nPrincipal ;

         nForwardIncome = (nLenderPayment + nPmiPayment
                        + (bC36 ? nMonthlyDebt : 0)) / nFactor ;

         bCheckError = Math.abs((nAnnualIncome - nForwardIncome)
                                / nAnnualIncome) > .01 ;

         if (bDebug)
         {
            Dump(iK + (bC36 ? ": Q36" : ": Q28")) ;
            Dump("nAnnualIncome = " + nAnnualIncome) ;
            Dump("nPmiMp = " + nPmiMp) ;
            Dump("nPaymentMp = " + nPaymentMp) ;
            Dump("nInputInterest = " + nInputInterest) ;
            Dump("nCash = " + nCash) ;
            Dump("nPropertyTaxPc = " + nPropertyTaxPc) ;
            Dump("nInsurancePc = " + nInsurancePc) ;
            Dump("nNumer = " + nNumer) ;
            Dump("nDenom = " + nDenom) ;
            Dump("nQMax = " + nQMax + " **************** "
                        + (bReject ? "Rejected" : "Accepted")) ;
            Dump("nLoanPc = " + nLoanPc) ;
            Dump("nClosePc = " + nClosePc) ;
            Dump("nPrincipal = " + nPrincipal) ;
            Dump("nPayment = " + nPayment) ;
            Dump("nMonthlyDebt = " + nMonthlyDebt) ;
            Dump("nLenderPayment = " + nLenderPayment) ;
            Dump("nDebtPayment = " + nDebtPayment) ;
            Dump("nPmiPayment = " + nPmiPayment) ;
            Dump("nForwardIncome = " + nForwardIncome) ;
            Dump("nAnnualIncome = " + nAnnualIncome) ;
            Dump(bReject ? "Value rejected" : "Value accepted") ;
            if (bCheckError)
               Dump("***************** Income Check Error "
                        + "*****************") ;
            Dump("=======================================") ;
         }
      }

      if (nQPrice == nErrorNumber || nQPrice < 0 || !bV28 || !bV36)
      {
         alert("No affordable house was found; please "
                        + "check your figures.") ;
         if (!bSuppressChecking)
            iError = 300 ;
      }
   }

   if (!iError)
   {
      Emit1(shStart, "How Much House Can You Afford Results") ;

      Emit1(shHeading1, "Here is an Estimate of What "
                                + "You Can Afford:") ;
      Emit(shBeginTable1) ;

      EmitRow2D("Maximum Home Price", nQPrice) ;
      EmitRow2D("Maximum Loan Amount", nQLoan) ;
      EmitRow2P("Interest Rate", nInputInterest) ;
      EmitRow2D("Down Payment Amount", nQDown) ;
      EmitRow2P("Down Payment (%)", nQDownPc) ;
      EmitRow2D("Estimated Closing Costs", nQClose) ;
      EmitRow2D("Monthly Principal and Interest", nQPayment) ;
      EmitRow2D("Monthly Taxes and Insurance", nQTin) ;
      EmitRow2D("Monthly PMI Payment", nQPmi) ;
      EmitRow2D("Other Monthly Debt", nMonthlyDebt) ;
      Emit(shEndTable) ;

      Emit(shBeginTable2) ;
      Emit(shLongText) ;
      Emit(shEndTable) ;

   }

   if (!iError)
   {
      if (bDebug && sDump != "")
         sOut += sNl + "<pre>" + sDump + "</pre>" ;
//    document.open() ;
      document.write(sOut) ;
      document.close() ;

   }

}

function Dump(sStr) { if (bDebug) sDump += sStr + sNl ;}

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 Round2(nVal, iD, iType)
// iType: -1 = floor, 0 = round, +1 = ceil
{
   var nPow = Power(iD) ;
   nVal = Math.round(nVal * nPow + iType * .5) / nPow ;
// alert(nVal + "/" + iD + "/" + iType + "/" + nPow + "/" + nVal) ;
   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 ;
}


//-->

</SCRIPT>

=eod

=data mortgage.howhou.html
=html

=// <BODY bgcolor=#FFFFFF onLoad="Initialize(document.appmform)">

<form name=appmform>

<TABLE border=0 cellpadding=0 cellspacing=0 width=500>

<TR><TD><font size=+2><B>How Much House Can You
Afford?</B></TD></TR></TABLE><BR>

<TABLE border=1 cellpadding=3 cellspacing=0 width=525>

  <TR>

    <TD><font size=-1><B>Gross Annual Income<BR>(salary plus bonus,
    interest, rental and dividend income)</B></font></TD>

    <TD><INPUT name=appm0001 size=10 value=40000></TD></TR>

  <TR>

    <TD><font size=-1><B>Monthly Debt Payments (car/student loan,
                        credit cards, etc.)</B></font></TD>
    <TD><INPUT name=appm0002 size=10 value=300></TD></TR>
  <TR>
    <TD><font size=-1><B>Cash Available for Down Payment and
                                Closing Costs</B></font></TD>
    <TD><INPUT name=appm0003 size=10 value=20000></TD></TR>
   <TR>
    <TD><font size=-1><B>Property Tax Rate (1.5% = $1500/yr for
                                100K house)</B></font></TD>
    <TD><INPUT name=appm0004 size=10 value=1.5></TD></TR>
  <TR>
    <TD><font size=-1><B>Home Insurance Rate (0.5% = $500/yr for
                                        100K house)</B></font></TD>
    <TD><INPUT name=appm0005 size=10 value=0.5></TD></TR>
  <TR>
    <TD><font size=-1><B>Interest Rate (%)</B></font></TD>
    <TD><INPUT name=appm0006 size=10 value=8.25></TD></TR>
  <TR>
    <TD><font size=-1><B>Length of Loan (in years)</B></font></TD>
    <TD><INPUT name=appm0007 size=10 value=30></TD></TR></TABLE><BR>

<TABLE width=525>
<TR align=center><TD><font size=-1>
<input type=button value="Calculate"
                onClick = "Execute(document.appmform)">
        </font></TD></TR></TABLE>

</FORM>


=eod

=eof
















Copyright © 1986-2014 by John J. Xenakis