<?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) == 0 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, $rate, 2);
} // 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" > <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((1 + $int), $payno)) - 1;
$value2 = $int * pow((1 + $int), $payno);
$pv = $pmt * ($value1 / $value2);
$pv = number_format($pv, 2, ".", "");
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(1 - $int * ($pv / $pmt));
$value2 = log(1 + $int);
$payno = $value1 / $value2;
$payno = abs($payno);
$payno = number_format($payno, 0, ".", "");
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, $GuessMiddle, 6);
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($GuessMiddle, 9, ".", ""); // 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((1 + $int), $payno);
$value2 = pow((1 + $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($principal, 2, ".", ",") ."</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> </td>";
echo "<td><b>" .number_format($totPayment, 2, ".", ",") ."</b></td>";
echo "<td><b>" .number_format($totInterest, 2, ".", ",") ."</b></td>";
echo "<td><b>" .number_format($totPrincipal, 2, ".", ",") ."</b></td>";
echo "<td> </td>";
echo "</tr>";
echo "</table>";
} // print_schedule ==================================================================
?>