home *** CD-ROM | disk | FTP | other *** search
- /*
- * 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 */
- }
-