//Copyright 2000. Compendium Research Corporation. All Rights Reserved.

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
   + "<link rel='stylesheet' href='normal.css'>" + 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></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></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 class='tdHead'>%s</TD>";
var shCell2 = "<TD class='tdData'>%s</TD>";
var shCell3 = "<TD class='tdDataRight'>%s</TD>";
var shEndPage = "<p><a href='javascript: history.go(-1)'>Back</a>";

function EmitRow1(s1, s2)
{
   Emit(shBeginRow1);
   Emit1(shCell1, "&nbsp;");
   Emit1(shCell1, s1);
   Emit1(shCell1, s2);
   Emit(shEndRow);
}

function EmitRow2D(s1, bNa, s2, s3)
{
   Emit(shBeginRow1);
   Emit1(shCell2, s1);
   Emit1(shCell3, bNa ? "N/A" : DollarFormat(s2, 0, 2));
   Emit1(shCell3, DollarFormat(s3, 0, 2));
   Emit(shEndRow);
}

function EmitRow2P(s1, bNa, s2, s3)
{
   Emit(shBeginRow1);
   Emit1(shCell2, s1);
   Emit1(shCell3, bNa ? "N/A" : (s2 + " %"));
   Emit1(shCell3, s3 + " %");
   Emit(shEndRow);
}

var shLongText = ""

   + "<TR><TD><B>If you are serious about purchasing a home," + sNl
   + "we recommend you become formally approved</B>." + sNl
   + "Click <a href='https://virtualtechsecure.com/gsfloanapp/application.asp?uid=12'> here</a>" + sNl
   + "to complete our quick online application.  Once we obtain further information" + sNl
   + "from you, we can pinpoint exactly where you fit into the above range.<BR><BR></TD></TR>"

   + "<TR><TD>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></TD></TR>" + sNl + sNl

   + "<TR><TD><BR>*The high estimate is calculated using a" + sNl
   + "housing expense ratio of 36% and a long term debt ratio of 40%.  It provides" + sNl
   + "you with an aggressive estimate of how much you can qualify for with some," +sNl
   + "but not all, lenders.  If the amounts of your high and low estimates don't" +sNl
   + "vary by much, it's likely that the cause is your cash-available amount. To" +sNl
   + "get a larger difference between high and low estimates, try increasing the" +sNl
   + "amount in the cash-available field.</TD></TR>" + sNl + sNl

   + "<TR><TD><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.</TD></TR>" + sNl + sNl

   + "<TR><TD><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.</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, bCol1, bReject;
      var nClosePc = .02, nMaxLoanPc = .95;
      var nNumer, nDenom, nTemp;
      var nPayment, nLenderPayment, nDebtPayment, nPmiPayment;
      var iMonths = 12 * iYears;
      var nPaymentMp = 1 / PresentValue(1, nInputInterest/1200,iMonths);
      var bV28 = false, bV36 = false;
      var bV281 = false, bV361 = false;

      var nQPrice = nErrorNumber, nQLoan = 0, nQDown = 0, nQDownPc = 0;
      var nQClose = 0, nQPayment = 0, nQTin = 0, nQPmi = 0;
      var nQPrice1 = nErrorNumber, nQLoan1 = 0, nQDown1 = 0, nQDownPc1 = 0;
      var nQClose1 = 0, nQPayment1 = 0, nQTin1 = 0, nQPmi1 = 0;
      var nQIncome;
      var bNa = false;

      var nFactors = [36, 28, 40, 36];

      for (iK = 0; iK < 8; ++iK)
      {
         nPmiMp = (iK % 2) ? .7/1200 : 0;
         bC36 = (iK % 4 < 2);
         bCol1 = (iK < 4);
         nFactor = nFactors[Math.floor(iK/2)] / 1200;

         nNumer = (nAnnualIncome * nFactor
               - (bC36 ? nMonthlyDebt : 0))
                        * (1 - nClosePc)
               + (nPaymentMp + nPmiMp) * nCash;
         nDenom = (nPaymentMp + nPmiMp)
               + (nPropertyTaxPc + nInsurancePc) / 1200
                           * (1 - nClosePc);
         nQMax = nNumer / nDenom;
         nLoanPc = (1 - nCash / nQMax) / (1 - nClosePc);

         if (!bSuppressChecking && nLoanPc < 0)
         {
            Dump("**** #1 Adjusting result because nLoanPc = "
                        + nLoanPc);
            nNumer = nAnnualIncome * nFactor
                        - (bC36 ? nMonthlyDebt : 0);
            nDenom = (nPropertyTaxPc + nInsurancePc) / 1200;
            nQMax = (nDenom == 0) ? nCash : nNumer / nDenom;
            nLoanPc = 0;
         }
         else if (!bSuppressChecking && nLoanPc > nMaxLoanPc
                  && nPmiMp > 0 && nQMax > 0)
         {
//          nTemp = nCash / (1 - nMaxLoanPc * (1 - nClosePc));
            Dump("**** # 2 Adjusting result because nLoanPc = "
                     + nLoanPc);
            nQMax = nCash / (1 - nMaxLoanPc * (1 - nClosePc));
            nLoanPc = nMaxLoanPc;
         }
         else if (!bSuppressChecking && nLoanPc < .80 && nPmiMp > 0
                     && !(bC36 ? bV36 : bV28))
         {
            Dump("**** # 3 Adjusting result because PMI nLoanPc = "
                     + nLoanPc);
            nLoanPc = .80;
            nPmiMp = 0;
            nQMax = nCash / (1 - nLoanPc * (1 - nClosePc));
         }

         nQIncome = ((bC36 ? nMonthlyDebt : 0)
               + (nPropertyTaxPc + nInsurancePc) / 1200 * nQMax
               + nQMax * nLoanPc / PresentValue(1,
                           nInputInterest/1200, iMonths)
               + nPmiMp * nQMax * nLoanPc) / nFactor;

         bReject = nQMax < 0 || (nLoanPc > .80) != (nPmiMp > 0)
                  || nQIncome >= 1.000001*nAnnualIncome
                  || (!bSuppressChecking && nLoanPc > nMaxLoanPc);

         if (!bReject && ((nQPrice == nErrorNumber) || nQMax < nQPrice))
         {
            nQPrice = Round(nQMax, 2);
            nQLoan = Round(nQPrice * nLoanPc, 2);
                Dump("***************** Setting QPrice,QLoan = "
                        + nQPrice + ", " + nQLoan);
            nQDown = nQPrice - nQLoan;
            nQDownPc = nQMax ? Round(nQDown / nQPrice * 100, 2) : 0;
            nQClose = Round(nQLoan * 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(iK + ": Q" + nFactors[Math.floor(iK/2)]);
            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("nQIncome = " + nQIncome);
            Dump("bV361,bV281,bV36,bV28="
               + bV361+ ", " + bV281+ ", " + bV36+ ", " +  bV28);
            Dump(bReject ? "Value rejected" : "Value accepted");
            if (bCheckError)
               Dump("***************** Income Check Error "
                        + "*****************");
            Dump("=======================================");
         }
         if (iK == 3)
         {
            if (!bV36 || !bV28 || nQLoan == 0)
            {
               nQLoan = nQPrice = nQPayment = nQDown
                        = nQClose = nQDownPc = nLenderPayment
                        = nQTin = nQPmi = 0;
               bV36 = bV28 = true;
               nDebtPayment = nMonthlyDebt;
               bNa = true;
            }
            nQPrice1 = nQPrice;
            nQLoan1 = nQLoan;
            nQDown1 = nQDown;
            nQDownPc1 = nQDownPc;
            nQClose1 = nQClose;
            nQPayment1 = nQPayment;
            nQTin1 = nQTin;
            nQPmi1 = nQPmi;
            bV281 = bV28;
            bV361 = bV36;
            bV28 = bV36 = false;
            nQPrice = nErrorNumber;
         }
      }

      if (nQPrice == nErrorNumber || nQPrice < 0 || nQLoan == 0
            || !bV28 || !bV36
            || !bV281 || !bV361)
      {
//       alert("No affordable house was found; please "
//                   + "check your figures.");

         alert("Although you don't qualify for a loan "
               + "according to this calculator, please contact "
               + "our office to learn about our other loan "
               + "programs which may be available to you");
         if (!bSuppressChecking)
            iError = 300;
      }
   }

   if (!iError)
   {
      Emit1(shStart, "<span class='tableHeadLeft'>Pre-Qualifier Results<\/span><br>");

      Emit1(shHeading1, "<span class='tableHeadLeft'>Pre-Qualifier Results<\/span><br>");

      Emit(shBeginTable1);
      EmitRow1("Low Estimate", "High Estimate");

      EmitRow2D("Home Price", bNa, nQPrice1, nQPrice);
      EmitRow2D("Loan Amount", false, nQLoan1, nQLoan);
      EmitRow2P("Interest Rate", bNa, nInputInterest, nInputInterest);
      EmitRow2D("Down Payment Amount", bNa, nQDown1, nQDown);
      EmitRow2P("Down Payment (%)", bNa, nQDownPc1, nQDownPc);
      EmitRow2D("Estimated Closing Costs", bNa, nQClose1, nQClose);
      EmitRow2D("Monthly Principal and Interest", bNa,
                                        nQPayment1, nQPayment);
      EmitRow2D("Monthly Taxes and Insurance", bNa, nQTin1, nQTin);
      EmitRow2D("Monthly PMI Payment", bNa, nQPmi1, nQPmi);
      EmitRow2D("Other Monthly Debt", bNa, nMonthlyDebt, nMonthlyDebt);
      Emit(shEndTable);

      Emit(shBeginTable2);
      Emit(shLongText);
      Emit(shEndTable);
      Emit(shEndPage);

   }

   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;
}