Compound interest in PHP part 1

Compound interest in PHP part 1

Created:20 Sep 2017 00:25:21 , in  Maths

In this article I look at how to write a reusable piece of code for finding compound interest and total accumulated value of deposit or loan in PHP programming language. Code developed here will become a basis to both a command line interface script and website form then.

Since coding a CLI script frequently involves processing command line arguments, validating user input, handling errors and outputting formatted result, this series of two articles is a great opportunity to look closer at PHP getopt and I/O stream functions among other things.

Compound interest in a nutshell

Compound interest is interest on previously-accumulated interest. This, can be seen clearly when principal sum is set to 1.

In simple terms, the interest from the previous period is reinvested till the last period of a loan or a deposit is reached. Nothing gets withdrawn in the meantime.

Total accumulated value is the principal sum plus accumulated interest at the end of the last period of loan or deposit.

Compound interest in PHP

For finding compound interest I have written a PHP class called SWWWCompoundInterest. The class uses a not-too-complicated formula to find required value. Since the formula depends on a few, possibly user-supplied variables, the class prepares and validates user input. If configured to do that, it will format the computation result as well.

SWWWCompoundInterest has some public methods:

get - returns compound interest calculation result,
valid - returns whether user-provided configuration options are valid,
error - returns errors if one or more configuration options have been found to be incorrect,
inspect - allows to checking values of private properties of an SWWWCompoundInterest class object.

Here is the code:


/*
  Class: SWWWCompoundInterest 
  Description: Find compound interest or total accumulated value
  Author: Sylwester Wojnowski
  WWW: wojnowski.net.pl

  Methods:
    get - public - compute compound interest or total accumulated value
      return - string or array - compound interest or total accumulated value for one or more years
        
    valid - public - check whether user input is valid
      return - bool
      
    error($key) - public - get error messages for one or more pieces of user input
      parameters:
        $key - string - property name
      return - array - error messages
    
    inspect($property) - public - inspect private properties of the object of SWWWCompoundInterest
      parameters:
        [$key] - string - index in $input array
      return - mixed - SWWWCompoundInterest object property value
    
*/
class SWWWCompoundInterest{

  private $valid = true;
  private $error = null; 
  // input option labels
  private $principal = 'p';
  private $rate = 'r';
  private $frequency = 'f';
  private $years = 'y';
  private $sums = 'n';
  private $interest = 'g';
  private $format = 't';
  
  // defaults  
  protected $defs = array(
    'principal' => CompoundInterestValid::PRINCIPAL,
    'rate' => CompoundInterestValid::RATE,
    'frequency' => CompoundInterestValid::FREQUENCY,
    'years' => CompoundInterestValid::YEARS,
    'sums' => false, 
    'interest' => false,
    'format' => false
  );
  
  public function __construct($conf){
    $this -> __prep_input($conf);
    $this -> __validate_input($this -> defs);
    $this -> defs = (object)$this -> defs;
  }

  // overwrite defaults with user input
  private function __prep_input($input){

    foreach($input as $opt_name => $opt_value){
      switch($opt_name){
        case $this -> sums: // sums
          $this -> defs['sums'] = true;
          break;  
        case $this -> interest:
          $this -> defs['interest'] = true;
          break;      
        case $this -> principal:
          $this -> defs['principal'] = (float)$opt_value;
          break;  
        case $this -> rate:
          $this -> defs['rate'] = (float)$opt_value; 
          break;
        case $this -> frequency:
          $this -> defs['frequency'] = (int)$opt_value; 
          break; 
        case $this -> years:
          $this -> defs['years'] = (int)$opt_value;
          break;
          // format  
        case $this -> format:
          $this -> defs['format'] = (string)trim(strip_tags($opt_value)); 
          break;  
      }
    }
    return $this -> defs;   
  }    
  
  // validate user input
  private function __validate_input($input){  
    $validation = SWWWValidation::instance($input);
    $validation -> rule(
      'principal',
       array('CompoundInterestValid','validate_principal'),
       array(':value'),
       CompoundInterestValid::PRINCIPAL_INVALID
    );
    $validation -> rule(
      'rate',
      array('CompoundInterestValid','validate_rate'),
      array(':value'),
      CompoundInterestValid::RATE_INVALID
    );
    $validation -> rule(
      'frequency',
      array('CompoundInterestValid','validate_frequency'),
      array(':value'),
      CompoundInterestValid::FREQUENCY_INVALID
    );
    $validation -> rule(
      'years',
      array('CompoundInterestValid','validate_years'),
      array(':value'),
      CompoundInterestValid::YEARS_INVALID
    );
    $validation -> rule(
      'format',
      array('CompoundInterestValid','validate_format'),
      array(':value'),
      CompoundInterestValid::FORMAT_INVALID
    );
    
    if( !$validation -> check() ){ 
      $this -> valid = false;
      $this -> error = $validation -> errors(); 
    }
    return $this -> defs;
  }

  // check for errors
  public function valid(){ return $this -> valid; }
  
  // get error if any
  public function error($key = ''){ return  $this -> error; }
  
  // inspect property
  public function inspect($property){
    return property_exists($this -> defs,$property) ? $this -> defs -> {$property} : false;  
  }
  
  // get raw data
  public function get(){
    if( !$this -> valid ){ return false; }

    $x = null;
    if(!$this -> defs -> sums){
      $cpdInterest = CompoundInterestBase::find($this -> defs);
      if($this -> defs -> interest){
        $x = $cpdInterest - $this -> defs -> principal;   
      }else{
        $x = $cpdInterest;
      } 
    // sums  
    }else{
      $x = array();
      $years = $this -> defs -> years;
      for($i = 0; $i <= $years; $i++){
        $this -> defs -> years = $i;
        $cpdInterest = CompoundInterestBase::find($this -> defs); 
        if($this -> defs -> interest){
          $x[] = $cpdInterest - $this -> defs -> principal;
        }else{ 
          $x[] = $cpdInterest;
        }  
      }
    }  
    return $this -> defs -> format ? SWWWStringArrayFormat::format($x,$this -> defs -> format) : $x;
  }   
}     

SWWWCompoundInterest dependencies

In order to make SWWWCompoundInterest less complicated and common functionality in it available to implementations of similar problems, some code has been moved to smaller, more specialized classes (They should be included before SWWWCompoundInterest class is used).

The specialized classes are:

CompoundInterestBase, CompoundInterestValid, SWWWValidation, SWWWStringArrayFormat

Compound interest formula

CompoundInterestBase consists of just one static method. The method allows to finding compound interest or total accumulated value.


/*
  Class: SWWWCompoundInterestBase 
  Description: Provides basic formula for finding compound interest
  Author: Sylwester Wojnowski
  WWW: wojnowski.net.pl

  Methods:
    find($data) - public - find compound interest with a formula
      properties:
        $data - object - object with variables for the formula
      return - double - compound interest
*/
class CompoundInterestBase{
  public static function find($data){
    return $data -> principal * pow(
      (1 + $data -> rate / $data -> frequency),
      $data -> frequency * $data -> years
    );      
  }  
}

Validating user input

Two PHP classes are used to prepare and validate user input for SWWWCompoundInterest. They are CompoundInterestValid and SWWWValidation. The former, an extension to CompoundInterestBase, contains constants and functions used by the latter. The latter is a validation class I descrbed in detail in article on validating user input with functions.


/*
  Class: SWWWCompoundInterestValid 
  Description: provide validation functions for SWWWValidation
  Author: Sylwester Wojnowski
  WWW: wojnowski.net.pl
*/
class CompoundInterestValid extends CompoundInterestBase{

  const PRINCIPAL = 1.0;
  const PRINCIPAL_MAX = 1000000;
  const RATE = 0.0;
  const RATE_MIN = -10;
  const RATE_MAX = 10;
  const FREQUENCY = 1;
  const FREQUENCY_MAX = 12;
  const YEARS = 0;
  const YEARS_MAX = 100;
  
  const PRINCIPAL_INVALID = "Specified principal value is incorrect.";
  const RATE_INVALID = "Specified rate value is incorrect.";
  const FREQUENCY_INVALID = "Specified frequency value is incorrect.";
  const YEARS_INVALID = "Specified number of years is incorrect.";
  const FORMAT_INVALID = "Specified format string is incorrect.";

  /* validation methods */ 
  public static function validate_principal($val){
    return $val >= self::PRINCIPAL and $val <= self::PRINCIPAL_MAX;
  }
  
  public static function validate_rate($val){
    return $val >= self::RATE_MIN and $val <= self::RATE_MAX and $val !== self::RATE;
  }
  
  public static function validate_frequency($val){
    return $val >= self::FREQUENCY and $val <= self::FREQUENCY_MAX;
  }
  
  public static function validate_years($val){
    return $val >= self::YEARS and $val <= self::YEARS_MAX;
  }
 
  public static function validate_format($val){
    if($val === false){ return true; } // default value
    return (bool)preg_match('/^%\.[0-9]{0,6}f$/',$val);
  }
}

Formatting output

For formatting arrays or strings of numeric values I wrote SWWWStringArrayFormat.
Here is a listing:


/*
  Class: SWWWStringArrayFormat 
  Description: Format array or string according to a format provided
  Author: Sylwester Wojnowski
  WWW: wojnowski.net.pl

  Methods:
    format($x,$format) - public static - format variable value(s) according to a format
      properties:
        $x - float/array - value(s) to format
        $data - string - format like described for sprintf PHP function
      return - double - compound interest
*/
class SWWWStringArrayFormat{
  // format array or string according to a format
  public static function format($x,$format){
    if(is_array($x)){
      $i = count($x);
      while($i > 0){
        $x[$i - 1] = sprintf($format,$x[$i -1]); 
        $i--;
      }
      return $x;
    } else {
      return sprintf($format,$x);
    }
  }
}

Configuring SWWWCompoundInterest

SWWWCompoundInterest needs some configuring before it can be used.

The available configuration options are as follows (options in brackets are optional):

p - float - principal sum,
r - float - nominal rate of interest,
f - int - frequency of compounding,
y - int - number of years compound interest is applied
[n] - bool - if not null, total accumulated values for each of y years will be returned
[g] - bool - if not null, compound interest for each of y years will be returned (principal sum is not taken into account in this case)
[t] - string - output format string

Use examples

What follows are some examples of finding compound interest or total accumulated value(s) with SWWWCompoundInterest:

Total accumulated value over 10 years period, with principal value 100, interest rate 0.05 and compounding frequency 1:


$cp = new SWWWCompoundInterest(
  array(
    'p' => 100,  // principal
    'r' => 0.05, // rate
    'f' => 1, // frequency
    'y' => 10  // years
  )
);

$cp -> get();
=> 162.88946267774415

Total compound interest accumulated over 10 years period, with principal value 100, interest rate 0.05 and compounding frequency 1:

 
$cp = new SWWWCompoundInterest(
  array(
    'p' => 100,
    'r' => 0.05,
    'f' => 1, 
    'y' => 5,
    'g' => false
  )
);
    
$cp -> get();
=> 27.628156250000018

Total accumulated values for each of 3 years, with principal value 100, interest rate 0.05, compounding frequency 1 and result formatted according to '%.2f' format:


$cp = new SWWWCompoundInterest(
  array(
    'p' => 100,
    'r' => 0.05,
    'f' => 1,
    'y' => 3,
    'n' => true,
    't' => '%.2f'
  )
);
        
$cp -> get()
=> array(100.00,105.00,110.25,115.76)

I guess, these examples give enough insight on how SWWWCompoundInterest can be used.

Final thoughts

Compound interest can be found in a couple of lines of code. However, the trouble with the "quick option" is that it is neither reusable nor, since the formula depends on a couple of user-supplied variables, secure. My approach is longer and more complicated but it yields more secure and reusable body of code.

In the second part of this short series on compound interest I'm going to build both a CLI script and a form based on the PHP code introduced in this article.

This post was updated on 21 Sep 2017 22:46:02

Tags:  php 


Author, Copyright and citation

Author

Sylwester Wojnowski

Author of the above article, Sylwester Wojnowski, is sWWW admin and owner.He enjoys doing Maths and studying algorithms, writing code in scripting and command languages, Thrash Metal music and playing electric guitar.

Copyrights

©Copyright, 2017 Sylwester Wojnowski. This article may not be reproduced or published as a whole or in parts without permission from the author. If you share it, please give author credit and do not remove embedded links.

Computer code, if present in the article, is excluded from the above and licensed under GPLv3.

Citation

Cite this article as:

Wojnowski, Sylwester. "Compound interest in PHP part 1." From sWWW - Code For The Web . https://wojnowski.net.pl//main/index/compound-interest-in-php-part-1