| Category: | Financial |
| Author/Contact Info | tye |
| Description: | Calculates loan payment amounts. It includes a complete derivation of the formulas used and is more accurate than the calculations many loan officers will do when giving you an estimate. It also shows how much the final payment will be and how much interest you will have paid in total (if things go exactly as planned). The final payment amount is useful for proving how accurate the calculation were (if it is within $0.01*N/2 of the regular payment, than the payment amount was accurate to the penny). It can also (if you specify -v) show how much in interest you pay per year and per payment (for payments near year boundaries) which is useful for tax planning. Give it as many arguments as you feel like on the command line and it will prompt for those you leave off. If you don't want to use dollars and cents, then adjust the four variables near the top of the script. (I cleaned up this code that I wrote years ago.) Sample use:
Updates: Removed incorrect usage of "APR". Changed one '$' to $Cur. Added display of (correct) APR (though it doesn't include any "fees" in the APR calculation). |
#!/usr/bin/perl -w
use strict;
my $Cur= '$'; # Currency symbol to use.
my $Fmt= '%.2f'; # Format to output currency with.
my $Cents= 100; # Fractions of $Cur to round to.
my $Comma= ','; # Thousands separator.
Main( @ARGV );
exit( 0 );
sub Usage
{
die "Usage: $0 [-v] 10000 8% /12 5.5\n",
"Computes payment amount for a ${Cur}10,000 loan at 8%\n",
"with 12 payments per year over 5.5 years.\n";
}
sub PmtRatio
{
my( $periodic, $count )= @_;
my $mult= $periodic ** $count;
my $ratio= ($mult-1)/($periodic-1);
return wantarray
? ( $ratio, $mult ) # "large payment ratio", "principle mu
+ltiplier"
: $ratio/$mult; # "small payment ratio"
}
=head1 Derivation
P= principle (starting amount of loan)
i= annual interest rate (0.10 == 10%)
p= amount of periodic payment (to be calculated)
f= payment frequency (12 for monthly payments and monthly compound
+ing)
N= total number of payments (N/f equals number of years)
If we owe R, then after one period, R*i/f in interest
will accrue so we will owe R+R*i/f or R*(1+i/f)
r= periodic rate of growth of principle = (1+i/f)
Payments Owe
0 P
|
1 P*r - p
|||||||
2 [ P*r - p ]*r - p
2 P*r^2 - p*r - p
2 P*r^2 - p*( r + 1 )
|||||||||||||||||||
3 [ P*r^2 - p*( r + 1 ) ]*r - p
3 P*r^3 - p*[ r^2 + r + 1 ]
N P*r^N - p*sum<j=0..N-1>( r^j )
L= sum<j=0..N-1>( r^j )
L= 1+r+..+r^(N-1)
r*L - L = r*( 1+r+..+r^(N-2)+r^(N-1) ) - ( 1+r+..+r^(N-1) )
r*L - L = ( r+r^2+..+r^(N-1)+r^N ) - ( 1+r+..+r^(N-1) )
r*L - L = r+r^2+..+r^(N-1) + r^N - ( 1 + r+..+r^(N-1) )
r*L - L = r+r^2+..+r^(N-1) + r^N - 1 - ( r+..+r^(N-1) )
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
r*L - L = r^N - 1
(r - 1)*L = r^N - 1
L = ( r^N - 1 ) / ( r - 1 )
After payment N, we owe nothing so:
0 = P*r^N - p*L
0 = P*r^N - p*(r^N-1)/(r-1)
p*(r^N-1)/(r-1) = P*r^N
p = P * r^N / [ (r^N-1) / (r-1) ]
^^^ ^^^^^^^^^^^^^^^
M L
We'll call M the "multiplier". It is the ratio of ending principle ov
+er
starting principle if you never make any payments. It is useful for
computing how your savings will grow based on different interest
compounding schemes.
We'll call L the "large payment ratio". It is the ratio of P*M (endin
+g
priciple if no payments) over payment size. It will be somewhat large
+r
than N (your payments will be somewhat smaller than P*M/N).
We'll call S=L/M the "small payment ratio". It is the ratio of P
(starting principle) over payment size. It will be somewhat smaller
than N (your payments will be somewhat larger than P/N).
=cut
sub Round
{
my( $amt )= @_;
return int( $amt*$Cents + 0.5 ) / $Cents;
}
sub readLine
{
my $line= <STDIN>;
die "Can't read STDIN: $!\n"
unless defined $line;
chomp $line;
return $line;
}
sub getParams
{
my $params= @_ ? "@_" : "";
my $reAmt= '\d+[.]?\d*';
my( $P, $i, $f, $y );
if( $params =~ /\S/ ) {
Usage()
unless $params =~ s[ ^\s* ($reAmt) (\s|$) ][]x;
$P= $1;
}
if( ! defined $P || "" eq $P ) {
while( 1 ) {
print "Enter current value [amount of loan]: ";
$params= readLine();
if( $params =~ s[ ^\s* ($reAmt) (\s|$) ][]x ) {
$P= $1;
last;
}
warn "Invalid amount. Please try again.\n";
}
}
if( $params =~ /\S/ ) {
warn "Ignoring garbage after principle ($params).\n"
unless $params =~ s[ ^\s* ($reAmt)\s*% ([\s/]|$) ][$2]x;
$i= $1;
}
if( ! defined $i || "" eq $i ) {
while( 1 ) {
print "Enter interest annual percentage rate: ";
$params= readLine();
if( $params =~ s[ ^\s* ($reAmt)\s*%? ([\s/]|$) ][$2]x )
+{
$i= $1;
last;
}
warn "Invalid amount. Please try again.\n";
}
}
if( $params =~ /\S/ ) {
warn "Ignoring garbage after interest rate ($params).\n"
unless $params =~ s[ ^\s* /\s*(\d+) (\s|$) ][]x;
$f= $1;
}
if( ! defined $f || "" eq $f ) {
while( 1 ) {
print qq{Enter payment frequency [periods per "year"]: };
$params= readLine();
if( $params =~ s[ ^\s* (?:/\s*)?(\d+) (\s|$) ][]x ) {
$f= $1;
last;
}
warn "Invalid amount. Please try again.\n";
}
}
if( $params =~ /\S/ ) {
warn "Ignoring garbage after frequency ($params).\n"
unless $params =~ s[ ^\s* ($reAmt) (\s|$) ][]x;
$y= $1;
}
if( ! defined $y || "" eq $y ) {
while( 1 ) {
print qq{Enter loan duration [years]: };
$params= readLine();
if( $params =~ s[ ^\s* ($reAmt) (\s|$) ][]x ) {
$y= $1;
last;
}
warn "Invalid amount. Please try again.\n";
}
}
my $N= int( $y * $f + 0.5);
$y= $N/$f;
$P= Round( $P );
return( $P, $i, $f, $y, $N );
}
sub Main
{
my $verbose= 0;
if( @_ && $_[0] eq "-v" ) {
shift(@_);
$verbose= 1;
}
my( $P, $i, $f, $y, $N )= getParams( @_ );
$i /= 100;
my( $rat, $mult )= PmtRatio( 1+$i/$f, $N );
my $p= $P*$mult/$rat;
my $amt= $Cur . $P;
0 while $amt =~ s/(\d)(\d\d\d(\D|$))/$1$Comma$2/;
$p= Round( $p );
print "\n$amt at ", $i*100,
qq{%/$f per period, requires $N payments of $Cur$p ($y "years"
+)\n};
if( $verbose ) {
print "\npayment_number +interest_accrued -payment_made =still
+_owe\n";
}
my $pi;
my $ti= 0;
my $yi= 0;
for( 1..$N ) {
$pi= Round( $P * $i / $f );
$yi= Round( $yi + $pi );
$ti= Round( $ti + $pi );
$P += $pi - $p;
if( $verbose
and 1 == $_ % $f
|| 0 == $_ % $f
) {
printf "%3d +$Fmt -$Fmt =$Fmt", $_, $pi, $p, $P;
if( 0 == $_ % $f ) {
printf " ($Cur$Fmt int)", $yi;
$yi= 0;
}
print "\n";
}
}
print "\n" if $verbose;
$ti= sprintf $Fmt, $ti;
0 while $ti =~ s/(\d)(\d\d\d(\D|$))/$1$Comma$2/;
printf "Final payment=$Cur$Fmt, Total interest=$Cur%s, APR=%f%%\n"
+,
$p+$P, $ti, 100*( (1+$i/$f)**$f - 1 );
}
|
|
|
|---|