<?php
//*****************************************************************************
// Copyright 2002 by A J Marston <http://www.tonymarston.net>
// Distributed under the GNU General Public Licence
//*****************************************************************************

//
// Calculate interest rate and produce loan amortisation schedule.
//
// This will accept any 3 of the 4 components of the calculation,
// calculate the missing component, then produce a repayment schedule
// showing the split between interest and principle for each payment.

?>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Loan Amortisation Schedule</title>
    <meta http-equiv='Content-type' content='text/html; charset=UTF-8' >
    <style type="text/css">
    <!--
        P.error { margin-top: 0pt; color: red; font-weight: bold; }
    -->
    </style>
</head>
<body>
<div align="center">
<h1>Loan Amortisation Schedule</h1>

<?php

if ($_SERVER['REQUEST_METHOD'] == 'GET') {
    
$url parse_url($_SERVER['REQUEST_URI']);
    if (
$url['path'] != $_SERVER['PHP_SELF']) {
        
// somebody is screwing with the URL! Posasible XSS attack!
        
header('Location: ' .$_SERVER['SCRIPT_NAME']);
        exit;
    } 
// if
// if

// look for no POST entries, or the RESET button
if (count($_POST) == or @$_POST['reset']) {
    
// POST array is empty - set initial values
    
$principal 484411.01;
    
$number    19;
    
$rate      1.044813912;
    
$payment   28242.11;
} else {
    
// retrieve values from POST array
    
$principal $_POST['principal'];
    
$number    $_POST['number'];
    
$rate      $_POST['rate'];
    
$payment   $_POST['payment'];
// if
// validate all fields
$error = array();
if (!empty(
$principal)) {
   if (!
is_numeric($principal)) {
      
$error['principal'] = "must be numeric";
   } elseif (
$principal 0) {
      
$error['principal'] = "must be > zero";
   } else {
      
$principal = (float)$principal;    // convert to floating point
   
// if
// if

if (!empty($number)) {
   if (!
preg_match('/^[0-9]+$/'$number)) {
      
$error['number'] = "must be an integer";
   } else {
      
$number = (int)$number;    // convert to integer
   
// if
// if

if (!empty($rate)) {
   if (!
is_numeric($rate)) {
      
$error['rate'] = "must be numeric";
   } elseif (
$rate 0) {
      
$error['rate'] = "must be > zero";
   } else {
      
$rate = (float)$rate;    // convert to floating point
   
// if
// if

if (!empty($payment)) {
   if (!
is_numeric($payment)) {
      
$error['payment'] = "must be numeric";
   } elseif (
$payment 0) {
      
$error['payment'] = "must be > zero";
   } else {
      
$payment = (float)$payment;    // convert to floating point
   
// if
// if

if (count($error) == 0) {
   
// no errors - perform requested action
   
if (isset($_POST['button1'])) {
      
$principal calc_principal($number$rate$payment);
   } 
// if
   
if (isset($_POST['button2'])) {
      
$number calc_number($principal $rate$payment);
   } 
// if
   
if (isset($_POST['button3'])) {
      
$rate calc_rate($principal$number$payment);
   } 
// if
   
if (isset($_POST['button4'])) {
      
$payment calc_payment($principal$number$rate2);
   } 
// if
// if
?>
<form action="<?php echo $_SERVER['PHP_SELF'?>" method="POST">
<table border="1">
<colgroup align="right">
<colgroup align="left">
<colgroup align="center">
<tr>
    <td>Principal</td><td><input type="text" name="principal" value="<?php echo $principal ?>" >
<?php
   
if (isset($error['principal'])) {
      echo 
'<p class="error">' .$error['principal'] .'</p>';
   } 
// if
?>
    </td><td><input type="submit" name="button1" value="calculate Principal" ></td>
</tr>
<tr>
    <td>Number of Payments</td><td><input type="text" name="number" value="<?php echo $number ?>" >
<?php
   
if (isset($error['number'])) {
      echo 
'<p class="error">' .$error['number'] .'</p>';
   } 
// if
?>
    </td><td><input type="submit" name="button2" value="calculate Number " ></td>
</tr>
<tr>
    <td>Interest Rate (%) per Payment</td><td><input type="text" name="rate" value="<?php echo $rate ?>" >
<?php
   
if (isset($error['rate'])) {
      echo 
'<p class="error">' .$error['rate'] .'</p>';
   } 
// if
?>
    </td><td><input type="submit" name="button3" value="calculate Interest   " ></td>
</tr>
<tr>
    <td>Payment</td><td><input type="text" name="payment" value="<?php echo $payment ?>" >
<?php
   
if (isset($error['payment'])) {
      echo 
'<p class="error">' .$error['payment'] .'</p>';
   } 
// if
?>
    </td><td><input type="submit" name="button4" value="calculate Payment" ></td>
</tr>
</table>
<p><input type="submit" name="reset" value="reset" >&nbsp;&nbsp;&nbsp;<input type="submit" name="button5" value ="Payment Schedule" ></p>
</form>

<?php
if (empty($error) AND isset($_POST['button5'])) {
   
print_schedule($principal$rate$payment);
// if
?>
</div>
</body>
</html>
<?php

function calc_principal($payno$int$pmt)
{
// check that required values have been supplied
if (empty($payno)) {
   echo 
"<p class='error'>a value for NUMBER of PAYMENTS is required</p>";
   exit;
// if
if (empty($int)) {
   echo 
"<p class='error'>a value for INTEREST RATE is required</p>";
   exit;
// if
if (empty($pmt)) {
   echo 
"<p class='error'>a value for PAYMENT is required</p>";
   exit;
// if

// now do the calculation using this formula:

//******************************************
//             ((1 + INT) ** PAYNO) - 1
// PV = PMT * --------------------------
//            INT * ((1 + INT) ** PAYNO)
//******************************************

$int    $int 100;        //convert to percentage
$value1 = (pow(($int), $payno)) - 1;
$value2 $int pow(($int), $payno);
$pv     $pmt * ($value1 $value2);
$pv     number_format($pv2".""");

return 
$pv;

// calc_principal ==================================================================

function calc_number($pv$int$pmt)
{
// check that required values have been supplied
if (empty($pv)) {
   echo 
"<p class='error'>a value for PRINCIPAL is required</p>";
   exit;
// if
if (empty($int)) {
   echo 
"<p class='error'>a value for INTEREST RATE is required</p>";
   exit;
// if
if (empty($pmt)) {
   echo 
"<p class='error'>a value for PAYMENT is required</p>";
   exit;
// if

// now do the calculation using this formula:

//******************************************
//         log(1 - INT * PV/PMT)
// PAYNO = ---------------------
//             log(1 + INT)
//******************************************

$int    $int 100;
$value1 log($int * ($pv $pmt));
$value2 log($int);
$payno  $value1 $value2;
$payno  abs($payno);
$payno  number_format($payno0".""");

return 
$payno;

// calc_number =====================================================================

function calc_rate($pv$payno$pmt)
{
// check that required values have been supplied
if (empty($pv)) {
   echo 
"<p class='error'>a value for PRINCIPAL is required</p>";
   exit;
// if
if (empty($payno)) {
   echo 
"<p class='error'>a value for NUMBER of PAYMENTS is required</p>";
   exit;
// if
if (empty($pmt)) {
   echo 
"<p class='error'>a value for PAYMENT is required</p>";
   exit;
// if

// now try and guess the value using the binary chop technique
$GuessHigh   = (float)100;    // maximum value
$GuessMiddle = (float)2.5;    // first guess
$GuessLow    = (float)0;      // minimum value
$GuessPMT    = (float)0;      // result of test calculation

do {
   
// use current value for GuessMiddle as the interest rate,
   // and set level of accurracy to 6 decimal places
   
$GuessPMT = (float)calc_payment($pv$payno$GuessMiddle6);

   if (
$GuessPMT $pmt) {    // guess is too high
      
$GuessHigh   $GuessMiddle;
      
$GuessMiddle $GuessMiddle $GuessLow;
      
$GuessMiddle $GuessMiddle 2;
   } 
// if

   
if ($GuessPMT $pmt) {    // guess is too low
      
$GuessLow    $GuessMiddle;
      
$GuessMiddle $GuessMiddle $GuessHigh;
      
$GuessMiddle $GuessMiddle 2;
   } 
// if

   
if ($GuessMiddle == $GuessHigh) break;
   if (
$GuessMiddle == $GuessLow) break;

   
$int number_format($GuessMiddle9".""");    // round it to 9 decimal places
   
if ($int == 0) {
      echo 
"<p class='error'>Interest rate has reached zero - calculation error</p>";
      exit;
   } 
// if

} while ($GuessPMT !== $pmt);

return 
$int;

// calc_rate =======================================================================

function calc_payment($pv$payno$int$accuracy)
{
// check that required values have been supplied
if (empty($pv)) {
   echo 
"<p class='error'>a value for PRINCIPAL is required</p>";
   exit;
// if
if (empty($payno)) {
   echo 
"<p class='error'>a value for NUMBER of PAYMENTS is required</p>";
   exit;
// if
if (empty($int)) {
   echo 
"<p class='error'>a value for INTEREST RATE is required</p>";
   exit;
// if

// now do the calculation using this formula:

//******************************************
//            INT * ((1 + INT) ** PAYNO)
// PMT = PV * --------------------------
//             ((1 + INT) ** PAYNO) - 1
//******************************************

$int    $int 100;    // convert to a percentage
$value1 $int pow(($int), $payno);
$value2 pow(($int), $payno) - 1;
$pmt    $pv * ($value1 $value2);
// $accuracy specifies the number of decimal places required in the result
$pmt    number_format($pmt$accuracy".""");

return 
$pmt;

// calc_payment ====================================================================

function print_schedule($balance$rate$payment)
{
// check that required values have been supplied
if (empty($balance)) {
   echo 
"<p class='error'>a value for PRINCIPAL is required</p>";
   exit;
// if
if (empty($rate)) {
   echo 
"<p class='error'>a value for INTEREST RATE is required</p>";
   exit;
// if
if (empty($payment)) {
   echo 
"<p class='error'>a value for PAYMENT is required</p>";
   exit;
// if

echo '<table border="1">';
echo 
'<colgroup align="right" width="20">';
echo 
'<colgroup align="right" width="115">';
echo 
'<colgroup align="right" width="115">';
echo 
'<colgroup align="right" width="115">';
echo 
'<colgroup align="right" width="115">';
echo 
'<tr><th>#</th><th>PAYMENT</th><th>INTEREST</th><th>PRINCIPAL</th><th>BALANCE</th></tr>';

$count 0;
do {
   
$count++;
   if (!
is_numeric($balance)) {
       
trigger_error("Balance [$balance] is not numeric"E_USER_ERROR);
   } 
// if
   
if (!is_numeric($rate)) {
       
trigger_error("Rate [$rate] is not numeric"E_USER_ERROR);
   } 
// if
   
if (!is_numeric($payment)) {
       
trigger_error("Payment [$payment] is not numeric"E_USER_ERROR);
   } 
// if

   // calculate interest on outstanding balance
   
$interest $balance $rate/100;

   
// what portion of payment applies to principal?
   
$principal $payment $interest;

   
// watch out for balance < payment
   
if ($balance $payment) {
      
$principal $balance;
      
$payment   $interest $principal;
   } 
// if

   // reduce balance by principal paid
   
$balance $balance $principal;

   
// watch for rounding error that leaves a tiny balance
   
if ($balance 0) {
      
$principal $principal $balance;
      
$interest  $interest $balance;
      
$balance   0;
   } 
// if

   
echo "<tr>";
   echo 
"<td>$count</td>";
   echo 
"<td>" .number_format($payment,   2"."",") ."</td>";
   echo 
"<td>" .number_format($interest,  2"."",") ."</td>";
   echo 
"<td>" .number_format($principal2"."",") ."</td>";
   echo 
"<td>" .number_format($balance,   2"."",") ."</td>";
   echo 
"</tr>";

   @
$totPayment   $totPayment $payment;
   @
$totInterest  $totInterest $interest;
   @
$totPrincipal $totPrincipal $principal;

   if (
$payment $interest) {
      echo 
"</table>";
      echo 
"<p>Payment < Interest amount - rate is too high, or payment is too low</p>";
      exit;
   } 
// if

} while ($balance 0);

echo 
"<tr>";
echo 
"<td>&nbsp;</td>";
echo 
"<td><b>" .number_format($totPayment,   2"."",") ."</b></td>";
echo 
"<td><b>" .number_format($totInterest,  2"."",") ."</b></td>";
echo 
"<td><b>" .number_format($totPrincipal2"."",") ."</b></td>";
echo 
"<td>&nbsp;</td>";
echo 
"</tr>";
echo 
"</table>";

// print_schedule ==================================================================
?>