#!/bin/sh
# -*- perl -*-
# This code allows us to start perl from our path or an environment variable
# rather than hardcoding a path into the #! line.  It works from sh or csh.
(exit $?0) && eval 'exec ${QPERLQ-perl} -x $0 ${1+"$@"}'
if (! $?QPERLQ) setenv QPERLQ perl
exec $QPERLQ -x $0 $argv:q

#!/usr/local/bin/perl -w
#
# $Id: loan,v 1.14 1998/05/10 14:29:51 ejb Exp $
# $Source: /home/ejb/scripts/RCS/loan,v $
# $Author: ejb $
#
# Author: E. Jay Berkenbilt
# Loan calculation program
# 
# See usage subroutine for usage.
#


$whoami = ($0 =~ m,([^/]*)$,) ? $1 : $0;

#
# NOTE: This algorithm depends upon the value of $pay_sym being
# alphabetically before $comp_sym.
#
$pay_sym = 'a';
$comp_sym = 'b';

$n = 1;
$tint = 0;
$tprinc = 0;
$oldyrcount = 0;
$yrcount = 1;
$header = "";
$startmo = 0;
$startyr = 0;
$always_monthly = 1;

&usage if (scalar(@ARGV) != 1);
$file = shift(@ARGV);
if (open(FILE, "<$file") == 0) {
    print STDERR "$whoami: can't open $file: $!\n";
    &usage;
}

$first_line = 1;
while (<FILE>)
{
    chop;
    s/\#.*//;
    m/^\s*$/ && next;
    $param = $_;

    if ($first_line && ($param =~ s/^header:\s*//))
    {
	&get_header($param);
    }
    elsif ($param =~ s/^comment:\s*//)
    {
	print "\n$param\n";
    }
    else
    {
	&get_params($param);

	if ($n == 1)
	{
	    print $header, "\n\n" if ($header ne "");
	    printf("Initial balance: \$%.2f\n", $b/100.0);
	}
	
	&run_sched;
    }

    $first_line = 0;
}

sub get_header {
    local($startdate);
    ($header) = @_;
    ($header =~ s,^([0-9]+/[0-9]+)\s*,,) || &usage;
    $startdate = $1;
    ($startmo, $startyr) = split('/', $startdate);
}

sub get_params {
    local(@tmp) = split(' ', $_[0]);
    
    # $b  is initial balance
    # $i  is anuual interest rate as a percentage
    # $y  is number of years
    # $nc is times per year compounded
    # $np is payments per year
    
    defined($tmp = shift(@tmp)) || &usage;
    $b = int($tmp * 100) unless ($tmp eq "-");
    defined($i = shift(@tmp)) || &usage;
    defined($y = shift(@tmp)) || &usage;
    defined($ap = shift(@tmp)) || ($ap = "-");
    defined($nc = shift(@tmp)) || ($nc = 12);
    defined($np = shift(@tmp)) || ($np = 12);

    $always_monthly = 0 if ($np != 12);

    defined($trash = shift(@tmp)) && &usage;

    ($y = $1/$2) if ($y =~ m,(.*)/(.*),);
    
    if ($y < 0) {
	warn "\nterm must be >= 0.\n";
	&usage;
    }
    
    if ($i < 0) {
	warn "\ninterest must be >= 0.\n";
	&usage;
    }
    
    # Convert $i to a factor rather than a percentage
    $i /= 100;
    
    #
    # Calculate the payment.  If the user supplied a payment value, use
    # that.	 Note that the calculated payment value may be slightly
    # inaccurate if the number of times the loan is compounded is not a
    # multiple of the number of payments made.
    #
    if ($ap eq "-") {
	if ($i == 0)
	{
	    $p = $b / ($nc * $y);
	}
	else
	{
	    # $c is the effective interest factor for continuous compounding
	    # or for when $nc is a multiple of $np.  It results in an
	    # approximate payment for all other cases.
	    $c = (($nc) ? ((1 + $i/$nc) ** ($nc / $np)) : exp($i / $np));
	    $p1 = ($b * ($c ** ($np * $y)) * ($c - 1)) /
		(($c ** ($np * $y)) - 1);
	    $p = int($p1);
	    $p += 1 if $p < $p1;
	}
    }
    else {
	$p = $ap * 100;
    }
    
    #
    # Compute factor for interest compounding.  If we are using continuous
    # compounding of interest, make $c e^($i/$np) and set the number of
    # compoundings to be equal to the number of payments.  Otherwise, 
    # just use 1 + $i/$nc
    #
    if ($nc == 0) {
	$nc = $np;
	$c = exp($i / $nc);
    }
    else {
	$c = 1 + ($i / $nc);
    };
    
    #
    # Calculate schedule.  The first thing that happens is always a
    # compounding of interest.  Then, payments and compoundings occur
    # according to the parameters given by the user.
    #
    # An event is defined to be either a compounding or a payment.
    # The smallest time unit is one year divided by the product of the
    # number of compoundings and the number of payments.  Every $np
    # events, a compounding occurs; every $nc events, a payment
    # occurs.  This way, there are $np payments and $nc compoundings
    # distributed properly throughout the year.	 This takes into
    # consideration the possibility of $nc not being a multiple of
    # $nc.
    #

    # Generate schedule of events
    @sched = ();
    push(@sched, ("0 $comp_sym"));
    for ($counter = 1; $counter < ($nc * $y); $counter++) {
	push(@sched, sprintf("%d $comp_sym", $np * $counter));
    };
    for ($counter = 1; $counter <= ($np * $y); $counter++) {
	push(@sched, sprintf("%d $pay_sym", $nc * $counter));
    };
    
    @sched = sort byevent @sched;
}


sub run_sched {
    printf("---> interest: %.2f%%/year, payment: \$%.2f %d times/year\n",
	   100 * $i, $p / 100.0, $np);
    
    $error = 0;
    for (@sched) {
	@trash = split;
	$event = pop(@trash);
	if ($event eq $comp_sym) {
	    # Based on values from our mortgage, it is not correct to round
	    # the principal and interest values separately. 
	    $bint = $b * ($c - 1);
	    $newbint = int($bint + 0.5);
	    $error = $newbint - $bint;
	    $bint = $newbint;
	    $b += $bint;
	    while ($error > 1)
	    {
		$bint--;
		$error--;
	    }
	}
	else {
	    $yrcount = &fixround($yrcount);
	    if (int($yrcount) > int($oldyrcount)) {
		&print_year(int($yrcount));
	    };
	    $oldyrcount = $yrcount;
	    $yrcount += (1 / $np);
	    
	    # Decrease balance by payment;
	    if ($b > $p) {
		$b -= $p;
	    }
	    else {
		$p = $b;
		$b = 0;
	    };
	    # Payment goes toward interest first, then principal
	    if ($bint >= $p) {
		$bint -= $p;
		$int = $p;
	    }
	    else {
		$int = $bint;
		$bint = 0;
	    };
	    $princ = $p - $int;
	    $tint += $int;
	    $tprinc += $princ;
	    &print_vals;
	    if ($b == 0) {
		printf("Final payment: \$%.2f", $p / 100.0);
		if (($startyr != 0) && ($always_monthly))
		{
		    local($endmo, $endyr) = ($startmo + $n - 1, $startyr);
		    $endmo = $startmo + $n - 1;
		    while ($endmo > 12)
		    {
			$endmo -= 12;
			$endyr++;
		    }
		    printf(" made on $endmo/$endyr\n");
		}
		else
		{
		    print "\n";
		}
		last;
	    }
	    $n++;
	};
    }
}

sub usage {
    printf STDERR <<EOF;

Usage: $whoami { file | - }
   file contains a list of parameter lines.  # is the comment character.
   Blank lines are ignored.  Each line is of the form

   bal int term [ act_payment [ comp [ payments ]]]

   "bal" is the initial balance in dollars
   "int" is the annual interest rate as a percentage (i.e., 6 not 0.06)
   "term" is the term of the loan in years.  This field may be specified as
       a fraction.
   "act_payment" is the actual payment made each period toward the loan.
       If zero or unspecified, this value is computed so that the balance
       will be zero at the end of the term of the loan.
   "comp" is the number of times per year the interest is
       compounded.  A value of 0 means continuous compounding.  The default
       value for this parameter is 12.
   "payments" is the number of payments made per year.  The default value
       for this parameter is 12.

   Optionally, the first (non-blank, non-comment) line may be a header line.
   The format of the header line is

   header: m/y comment comment comment

   where m/y is month/year and the comments are arbitrary text.  If this 
   header line is present, the starting month and year of each loan
   year will be printed, and the month and year in which the final
   payment is made will be indicated as long as the number of payments
   is always twelve throughout the life of the loan.

   Any line in the input file that starts with comment: will be echoed
   into the output (after the word "comment:" is removed) preceded by
   a newline.

EOF
    exit 1;
}

sub print_vals {
    printf("%3d: balance: %9.2f, p/i: %9.2f/%6.2f; " .
	   "total p/i: %9.2f/%9.2f\n", 
	   $n, $b / 100.0, $princ / 100.0, $int / 100.0,
	   $tprinc / 100.0, $tint / 100.0);
}

sub print_year {
    local($yc) = @_;

    $year = sprintf("=== Year %d ", $yc);
    $year .= sprintf("(starting %d/%d) ", $startmo, $startyr - 1 + $yc)
	if ($startyr != 0);
    $year .= ("=" x (79 - length($year)));
    printf("%s\n", $year);
}

sub min {
    local($a, $b) = @_;
    ($a < $b) ? $a : $b;
}


sub byevent {
    local($an, $at) = split(' ', $a);
    local($bn, $bt) = split(' ', $b);
    ($an <=> $bn) || ($at cmp $bt);
}


sub fixround {
    local($x) = @_;
    local($frac);

    $frac = $x - int($x);
    if ($frac <= 0.0001)
    {
	$x = int($x)
    }
    elsif ($frac >= 0.9999)
    {
	$x = int($x) + 1;
    }

    $x;
}
