[comp.sources.misc] v02i095: saverate.c - program to calculate interest rate from periodic savings

dennis@rlgvax.UUCP (Dennis.Bednar) (04/13/88)

comp.sources.misc: Volume 2, Issue 95
Submitted-By: "Dennis.Bednar" <dennis@rlgvax.UUCP>
Archive-Name: saverate

Below is a program that will compute the interest rate from periodic
savings.  The default is to deposit the payment at the beginning of
each period, but this can be overridden with the -e flag (stands for
"end" of period).  More details on useage in the help file included.

Misc.invest readers, this program will derive the annual percentages
reported in "29 Yr Performance of 20th Century, Select, Growth, Funds".

For example, depositing $166.66 at the beginning of every month, for
29 years (348 periods), with a future value of $874,519, you would
type:

	saverate 348 166.66 874519 12

(the last argument, 12, is the number of periods (months here) per year)

and it will yield the answer of:

14.37943891% per year, Payment deposited at beginning of each period

PS, I have verified that the numbers agree with the TI BA-II business
calculator.

-enjoy
-dennis


#--------------- CUT HERE ---------------
#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	getopt.h
#	saverate.c
#	saverate.help
# This archive created: Tue Apr 12 22:26:10 EDT 1988
#
if test -f getopt.h
then
echo shar: will not over-write existing file 'getopt.h'
else
echo x - getopt.h
# ............    F  I   L   E      B  E  G  .......... getopt.h
cat << '\SHAR_EOF' > getopt.h
extern	int	getopt();	/* get next option argument		*/
extern	char	*optarg;	/* start of option after rtn from getopt() */
extern	int	optind;		/* argv[i] to be processed by next getopt() */
extern	int	opterr;		/* set to !0 to disable ? stderr errors	*/
\SHAR_EOF
# ............    F  I   L   E      E  N  D  .......... getopt.h
fi # end of overwriting check
if test -f saverate.c
then
echo shar: will not over-write existing file 'saverate.c'
else
echo x - saverate.c
# ............    F  I   L   E      B  E  G  .......... saverate.c
cat << '\SHAR_EOF' > saverate.c
/*
 * saverate.c
 * dennis bednar 04 12 88
 * {uunet|sundc}!rlgvax!dennis
 *
 * compute rate of return on savings account with equal deposits
 * every period, either at the beginning or end of each period.
 * "annuity due" is savings at the beginning of each period.
 * "ordinary annuity" is savings at the end of each period.
 *
 * These are the unknowns:
 *	total periods
 *	payment per period (into the savings account)
 *	future value
 *	[optional] periods per year
 *
 * It will compute the interest per period, except that if the number
 * of periods per year is given, then it is multiplied by that to get
 * the percent per year.
 *
 * cmd [-de -t#] tot_periods pmt_per_period future_value [periods_per_year]
 *	-d = debug
 *	-e = deposit at end of each period
 *	-t# = max number of times "Try to guess" the rate, default DEF_TRY,
 *	      (will speed up the algorithm if you have slow machine,
	      at the expense of less accuracy)
 *
 * To try:
 *	cmd 4 100 430.913581 4
 *	should give 12% a year, deposit at begin of each period:
 *		Here you add $100 at the beginning of each quarter
 *		for 4 consecutive quarter.  The money earns 3% per
 *		quarter, so that the total you have (430.912) is
 *		computed as follows:
 *			100 * 1.03^4 = 112.55088100
 *			100 * 1.03^3 = 109.2727
 *			100 * 1.03^2 = 106.09
 *			100 * 1.03   = 103
 *		Total 		     = 430.91358100
 *
 *	cmd 4 100 418.362 4
 *	should also give 12% a year, deposit at end of each period.
 *
 * Periods_per_year defaults to 1.
 *
 * Formula for "annuity due" (deposit at begin of each period):
 *
 *	      (1+r)^(N+1) - (1+r)
 * FV = pmt * ------------------	where r != 0	(1)
 *		   (r)
 *
 * FV = pmt * N				where r == 0.	(2)
 *
 * Formula for "normal annuity" (deposit at end of each period):
 *
 *	      (1+r)^N - 1
 * FV = pmt * ------------------	where r != 0	(3)
 *		   (r)
 *
 * FV = pmt * N				where r == 0.	(4)
 *
 *
 * where
 *	fv = future value, amount in account after N periods
 *	pmt = payment at begin of each period
 *	r = interest rate per period (eg, .01 means 1% interest per period,
 *	    this would have to be multiplied by 12 to get 12%/yr if each
 *	    period were one month)
 *	N = total number of periods money is compounding interest.
 *
 * Derivation:
 *	FV = (pmt * (1+r)^N) + (pmt * (1+r)^(N-1)) + ... + (pmt * (1+r)) )
 *	we can factor out pmt for each term on the right, so
 *	FV = pmt * [ (1+r)^N + ... + (1+r)]			(5)
 *
 * usual mathematical trick is to multiply both sides of (5) by (1+r),
 * obtaining equation (6), then subtract (5) from (6), to obtain
 * equation (7):
 *
 *	FV * (1+r) = pmt * [ (1+r)^(N+1) + ... (1+r)^2]		(6)
 *
 *	FV * r = pmt * [ (1+r)^(N+1) - (1+r)]			(7)
 *
 * Divide both side of (7) by r to get equation (1).  The only problem
 * is that the division will blow up if r == 0, which is why equation
 * (2) was mentioned.
 *
 * Derivation of equations (3) & (4) are to be proved by the reader,
 * using a similar technique.
 *
 */
#include <stdio.h>
#include "getopt.h"

#define DEBUG 1
#define FLOAT double
#define STR_SAME !strcmp

extern	FLOAT	atof();

/* forward refs to non-int functions */
FLOAT	get_rate();
FLOAT	r_abs();
FLOAT	get_fv();
FLOAT	raise();
FLOAT	ask_f();

char	*cmd;
int	debug = 0;
#define T_ANNUITY_DUE	0	/* pmt made at begin of each period */
#define T_NORMAL_ANN	1	/* pmt made at end of each period */
int	annuity_type = T_ANNUITY_DUE;
#define DEF_TRY	100		/* default number of tries */
int	MAX_TRY = DEF_TRY;

main( argc, argv )
	int	argc;
	char	*argv[];
{
	int	tot_per;	/* total periods */
	FLOAT	rate,		/* rate per period, initially */
		pmt,		/* payment per period */
		fv;		/* future value */
	int	per_per_yr,	/* periods per year */
		num_opts;	/* number of options arguments */
	int	yes;

	cmd = argv[0];

	num_opts= get_args( argc, argv ) - 1;

	/* skip over the options as though they weren't even there */
	argc -= num_opts;
	argv += num_opts;

	/* argc and argv now assume as if all options have been stripped out,
	 * except that argv[0] might not be the command any more.  Hence,
	 * refer to argv[0] via "cmd" saved above.
	 */
	if (argc >= 4)
		{
		tot_per = atoi( argv[1] );
		pmt = atof( argv[2] );
		fv = atof( argv[3] );
		if (argc > 5)
			usage();
		else if (argc == 5)
			per_per_yr = atoi( argv[4] );
		else
			per_per_yr = 1;
		}
	else if (argc == 1)	/* just the command and possible args, no numbers */
		{
		tot_per = ask_i( "Total Number of Periods" );
		pmt = ask_f( "Payment Deposited Per Period" );
		fv = ask_f( "Future Value (after the last Period)" );
		per_per_yr = ask_i( "Number of Periods Per year" );
		if (annuity_type == T_ANNUITY_DUE)
			{
			yes = ask_yn( "Payments at begin of each period" );
			if (!yes)	/* switch if wrong */
				annuity_type = T_NORMAL_ANN;
			}
		else
			{
			yes = ask_yn( "Payments at end of of each period" );
			if (!yes)
				annuity_type = T_ANNUITY_DUE;
			}
		}
	else
		usage();

	/* common processing to do the computations */
	rate = get_rate( tot_per, pmt, fv, annuity_type );
	rate *= (FLOAT) per_per_yr;
	rate *= (FLOAT)100.0;
	printf("%.8f%% per %s, Payment deposited at %s of each period\n",
	rate, (per_per_yr == 1) ? "period" : "year",
	(annuity_type == T_ANNUITY_DUE) ? "beginning" : "end" );

	exit(0);
}

usage()
{
	fprintf(stderr, "Usage: %s [-de -t#] [tot_periods pmt_per_period future_value [periods_per_year]]\n", cmd);
	fprintf(stderr, "\t-d = debug\n");
	fprintf(stderr, "\t-e = each payment is at the end of each period (default = begin)\n");
	fprintf(stderr, "\t-t50 = sets the number of Tried guesses to 50, default = %d\n", DEF_TRY);
	fprintf(stderr, "\t is used to make the program run faster, but less accuracy\n");
	exit(1);
}


/*
 * compute rate per period by using a binary search
 */
FLOAT
get_rate( tot_per, pmt, fv, ann )
	int	tot_per;
	FLOAT	pmt,
		fv;
	int	ann;	/* annuity type */
{
	FLOAT	lo_rate,
		hi_rate,
		mid_rate,
		try_fv;		/* guessed future value */
	int	try;

	/* added this because "saverate 4 100 400 4" was returning
	 * .00000073% per year mysteriously.
	 */
	if (fv == get_fv( tot_per, (FLOAT)0.0, pmt, ann))
		return 0.0;

	lo_rate = .01;	/* 1% per period */
	hi_rate = .05;	/* 5% per period */

	/* adjust lo_rate down until below fv */
	/* this is because we may have "lost" money */
	while( (try_fv = get_fv( tot_per, lo_rate, pmt, ann )) > fv )
		{
		lo_rate -= .05;
#ifdef DEBUG
		if (debug)
		printf("get_rate: lowering lo_rate to %.2f\n", lo_rate);
#endif
		}

	/* adjust hi_rate up until above fv */
	while( (try_fv = get_fv( tot_per, hi_rate, pmt, ann )) < fv )
		{
		hi_rate += .05;
#ifdef DEBUG
		if (debug)
		printf("get_rate: raising hio_rate to %.2f\n", hi_rate);
#endif
		}

	/* binary search */
	for ( try = 1; mid_rate = (lo_rate+hi_rate)/2.0; ++try)
		{
		try_fv = get_fv( tot_per, mid_rate, pmt, ann );
#ifdef DEBUG
		if (debug)
		printf("get_rate: trying %lf <= %lf <= %lf\n", lo_rate, mid_rate, hi_rate);
#endif
		if (try_fv < fv )	/* mid_rate is too low ? */
			lo_rate = mid_rate;
		else
			hi_rate = mid_rate;
		if (r_abs( fv - try_fv ) <= .0000000001)
			break;
		else if (try >= MAX_TRY)
			{
#ifdef DEBUG
			if (debug)
				{
				printf("Breaking out of loop after %d times: lo=%f, hi = %f\n", try, lo_rate, hi_rate);
				printf("hi - lo = %f\n", hi_rate - lo_rate);
				}
#endif
			break;
			}
		}
	return mid_rate;
}

/*
 * real number absolute value
 */
FLOAT
r_abs( f )
	FLOAT	f;
{
	if (f >= 0)
		return f;
	else
		return -f;
}

/*
 * compute future value using the formula at the beginning of this program
 */
FLOAT
get_fv( tot_per, rate, pmt, ann )
	int	tot_per;
	FLOAT	rate,
		pmt;
	int	ann;	/* annuity type */
{
	FLOAT	one_r,	/* 1+r */
		fv,	/* future value to return */
		num,	/* numerator */
		den;	/* denominator */

	/* money is not earning any interest, so prevent "divide by zero" */
	if (rate == 0.0)
		return (pmt * tot_per);

	one_r = (FLOAT)1.0 + rate;
	if (ann == T_ANNUITY_DUE)	/* save at begin of each period */
		{
		num = raise( one_r, (tot_per+1) ) - one_r;
		den = rate;
		fv = pmt * (num / den );
		return fv;
		}
	else if (ann == T_NORMAL_ANN)	/* save at end of each period */
		{
		num = raise( one_r, tot_per) - 1.0;
		den = rate;
		fv = pmt * (num / den );
		return fv;
		}
	else
		error("Bad arg (%d) to get_fv()\n", ann);
}

/*
 * return f^n
 * where f is FLOAT and n is integer >= 0.
 */
FLOAT
raise( f, n )
	FLOAT	f;
	int	n;
{
	FLOAT	rtn;

	if (n < 0)
		error("raise: not written to handle negative exponents");
	for (rtn = 1.0; n > 0; --n)
		rtn *= f;
	return rtn;
}

#define MANYARGS msg, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9
typedef char	*CHARSTAR;

/*
 * print error message and exit
 * Error message does *not* have a newline at the end.
 */
error(MANYARGS)
	CHARSTAR	MANYARGS;
{
	extern	char	*cmd;
	char	buffer[BUFSIZ];
	sprintf(buffer, MANYARGS);
	fprintf(stderr, "%s: error: %s. Exitting.\n", cmd, buffer);
	exit(1);
}


/*
 * parses main() arguments, and returns index of first file
 */
get_args( argc, argv )
	int	argc;
	char	*argv[];
{
	char	c;
	int	errflag;

	errflag = 0;

	while ((c = getopt(argc, argv, "det:")) != EOF)
	switch(c){
	case 'd':
		debug = 1;
		break;
	case 'e':
		annuity_type = T_NORMAL_ANN;
		break;
	case 't':
		MAX_TRY = atoi( optarg );
		break;
	default:
		errflag = 1;
		break;
	}

	if (errflag)
		usage();


	return optind;	/* index of first non-option */
}

/*
 * Ask a question and return an integer
 */
ask_i( msg )
	char	*msg;
{
	int	rtn;
	printf("%s ? ", msg );
	fflush(stdout);
	scanf( "%d", &rtn);
	return rtn;
}

/*
 * Ask a question and return a floating point number
 * HARD CODED TO ASSUME THAT RETURNS A DOUBLE !!
 */
FLOAT
ask_f( msg )
	char	*msg;
{
	double	rtn;
	printf("%s ? ", msg );
	fflush(stdout);
	scanf( "%lf", &rtn);	/* %lf = double, %f = float */
	return rtn;
}

/*
 * Ask a y/n question, and return 1 iff yes.
 */
ask_yn( msg )
	char	*msg;
{
	char	rtn[100];

	while(1){
		printf("%s ? (y/n) ", msg);
		fflush(stdout);
		fflush(stdin);	/* strange interaction scanf() & gets() */
		gets( rtn );
		switch( rtn[0] ){
		case 'y':
		case 'Y':
		case '\0':
			return 1;
		case 'n':
		case 'N':
			return 0;
		default:
			continue;
		}/* switch */
	}  /* while */
}
\SHAR_EOF
# ............    F  I   L   E      E  N  D  .......... saverate.c
fi # end of overwriting check
if test -f saverate.help
then
echo shar: will not over-write existing file 'saverate.help'
else
echo x - saverate.help
# ............    F  I   L   E      B  E  G  .......... saverate.help
cat << '\SHAR_EOF' > saverate.help
saverate [-de -t#] [tot_periods pmt_per_period future_value [periods_per_year]]
	-d = debug
	-e = each payment is at the end of each period (default = begin)
	-t50 = sets the number of Tried guesses to 50, default = 100
	 is used to make the program run faster, but less accuracy

Used to determine the interest rate when you deposit the same payment
per period into a savings account.  The percent rate is the annual
percent per year (if periods_per_year is given), else it is the percent
per period.


If you supply no numbers, it asks you for all of the numbers.
\SHAR_EOF
# ............    F  I   L   E      E  N  D  .......... saverate.help
fi # end of overwriting check
# end of shell archive
exit 0
-- 
FullName:	Dennis Bednar
UUCP:		{uunet|sundc}!rlgvax!dennis
USMail:		CCI; 11490 Commerce Park Dr.; Reston VA 22091
Telephone:	+1 703 648 3300