<?php

/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */

/**
 * Math_Finance: Class of financial functions
 *
 * Assorted financial functions for interest rates, bonds, amortizations and time value of money calculations (annuities)
 * Same interface as Excel financial functions.
 *
 * PHP versions 4 and 5
 *
 * LICENSE: This source file is subject to version 3.0 of the PHP license
 * that is available through the world-wide-web at the following URI:
 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
 * the PHP License and are unable to obtain it through the web, please
 * send a note to license@php.net so we can mail you a copy immediately.
 *
 * @category   Math
 * @package    Math_Finance
 * @author     Original Author <alejandro.pedraza@dataenlace.com>
 * @copyright  2005 Alejandro Pedraza
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    CVS: $Id:$
 * @link       http://pear.php.net/Math/Finance
 * @since      File available since Release 1.2.0
 */

// to be able to throw PEAR errors
require_once 'PEAR.php';

// precision of calculations
define('FINANCE_PRECISION', 1E-6);

// payment types
define('FINANCE_PAY_END', 0);
define('FINANCE_PAY_BEGIN', 1);

// types of daycount basis
define('FINANCE_COUNT_NASD', 0);
define('FINANCE_COUNT_ACTUAL_ACTUAL', 1);
define('FINANCE_COUNT_ACTUAL_360', 2);
define('FINANCE_COUNT_ACTUAL_365', 3);
define('FINANCE_COUNT_EUROPEAN', 4);

/**
 * Math_Finance: Main class
 *
 * @category   Math
 * @package    Math_Finance
 * @author     Original Author <alejandro.pedraza@dataenlace.com>
 * @copyright  2005 Alejandro Pedraza
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
 * @version    Release: @package_version@
 * @link       http://pear.php.net/Math/Finance
 * @since      Class available since Release 1.2.0
 */
class Math_Finance
{
    /*******************************************************************
    ** Interest Rates Conversion Functions                         *****
    *******************************************************************/

    /**
    * Returns the effective interest rate given the nominal rate and the number of compounding payments per year
    * Excel equivalent: EFFECT
    *
    * @param float      Nominal interest rate
    * @param int        Number of compounding payments per year
    * @return float     
    * @static
    * @access public
    */
    function effectiveRate($nominal_rate, $npery)
    {
        $npery = (int)$npery;
        if ($npery < 0) {
            return PEAR::raiseError('Number of compounding payments per year is not positive');
        }

        $effect = pow((1 + $nominal_rate / $npery), $npery) - 1;
        return $effect;
    }

    /**
    * Returns the nominal interest rate given the effective rate and the number of compounding payments per year
    * Excel equivalent: NOMINAL
    *
    * @param float      Effective interest rate
    * @param int        Number of compounding payments per year
    * @return float     
    * @static
    * @access public
    */
    function nominalRate($effect_rate, $npery)
    {
        $npery = (int)$npery;
        if ($npery < 0) {
            return PEAR::raiseError('Number of compounding payments per year is not positive');
        }

        $nominal = $npery * (pow($effect_rate + 1, 1/$npery) - 1);
        return $nominal;
    }


    /*******************************************************************
    ** TVM (annuities) Functions                                   *****
    *******************************************************************/

    /**
    * Returns the Present Value of a cash flow with constant payments and interest rate (annuities)
    * Excel equivalent: PV
    *
    *   TVM functions solve for a term in the following formula:
    *   pv(1+r)^n + pmt(1+r.type)((1+r)^n - 1)/r) +fv = 0
    *
    *
    * @param float      Interest rate per period 
    * @param int        Number of periods
    * @param float      Periodic payment (annuity)
    * @param float      Future Value
    * @param int        Payment type:
                            FINANCE_PAY_END (default):    at the end of each period
                            FINANCE_PAY_BEGIN:            at the beginning of each period
    * @return float     
    * @static
    * @access public
    */
    function presentValue($rate, $nper, $pmt, $fv = 0, $type = 0)
    {
        if ($nper < 0) {
            return PEAR::raiseError('Number of periods must be positive');
        }
        if ($type != FINANCE_PAY_END && $type != FINANCE_PAY_BEGIN) {
            return PEAR::raiseError('Payment type must be FINANCE_PAY_END or FINANCE_PAY_BEGIN');
        }

        if ($rate) {
            $pv = (-$pmt * (1 + $rate * $type) * ((pow(1 + $rate, $nper) - 1) / $rate) - $fv) / pow(1 + $rate, $nper);
        } else {
            $pv = -$fv - $pmt * $nper;
        }
        return $pv;
    }

    /**
    * Returns the Future Value of a cash flow with constant payments and interest rate (annuities)
    * Excel equivalent: FV
    *
    * @param float      Interest rate per period 
    * @param int        Number of periods
    * @param float      Periodic payment (annuity)
    * @param float      Present Value
    * @param int        Payment type:
                            FINANCE_PAY_END (default):    at the end of each period
                            FINANCE_PAY_BEGIN:            at the beginning of each period
    * @return float     
    * @static
    * @access public
    */
    function futureValue($rate, $nper, $pmt, $pv = 0, $type = 0)
    {
        if ($nper < 0) {
            return PEAR::raiseError('Number of periods must be positive');
        }
        if ($type != FINANCE_PAY_END && $type != FINANCE_PAY_BEGIN) {
            return PEAR::raiseError('Payment type must be FINANCE_PAY_END or FINANCE_PAY_BEGIN');
        }

        if ($rate) {
            $fv = -$pv * pow(1 + $rate, $nper) - $pmt * (1 + $rate * $type) * (pow(1 + $rate, $nper) - 1) / $rate;
        } else {
            $fv = -$pv - $pmt * $nper;
        }
        return $fv;
    }

    /**
    * Returns the constant payment (annuity) for a cash flow with a constant interest rate
    * Excel equivalent: PMT
    *
    * @param float      Interest rate per period 
    * @param int        Number of periods
    * @param float      Present Value
    * @param float      Future Value
    * @param int        Payment type:
                            FINANCE_PAY_END (default):    at the end of each period
                            FINANCE_PAY_BEGIN:            at the beginning of each period
    * @return float     
    * @static
    * @access public
    */
    function payment($rate, $nper, $pv, $fv = 0, $type = 0)
    {
        if ($nper < 0) {
            return PEAR::raiseError('Number of periods must be positive');
        }
        if ($type != FINANCE_PAY_END && $type != FINANCE_PAY_BEGIN) {
            return PEAR::raiseError('Payment type must be FINANCE_PAY_END or FINANCE_PAY_BEGIN');
        }

        if ($rate) {
            $pmt = (-$fv - $pv * pow(1 + $rate, $nper)) / (1 + $rate * $type) / ((pow(1 + $rate, $nper) - 1) / $rate);
        } else {
            $pmt = (-$pv - $fv) / $nper;
        }
        return $pmt;
    }

    /**
    * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate
    * Excel equivalent: NPER
    *
    * @param float      Interest rate per period 
    * @param float      Periodic payment (annuity)
    * @param float      Present Value
    * @param float      Future Value
    * @param int        Payment type:
                            FINANCE_PAY_END (default):    at the end of each period
                            FINANCE_PAY_BEGIN:            at the beginning of each period
    * @return float
    * @static
    * @access public
    */
    function periods($rate, $pmt, $pv, $fv = 0, $type = 0)
    {
        if ($type != FINANCE_PAY_END && $type != FINANCE_PAY_BEGIN) {
            return PEAR::raiseError('Payment type must be FINANCE_PAY_END or FINANCE_PAY_BEGIN');
        }

        if ($rate) {
            if ($pmt == 0 && $pv == 0) {
                return PEAR::raiseError('Payment and Present Value can\'t be both zero when the rate is not zero');
            }
            $nper = log(($pmt * (1 + $rate * $type) / $rate - $fv) / ($pv + $pmt * (1 + $rate * $type) / $rate))
                     / log(1 + $rate);
        } else {
            if ($pmt == 0) {
                return PEAR::raiseError('Rate and Payment can\'t be both zero');
            }
            $nper = (-$pv -$fv) / $pmt;
        }
        return $nper;
    }

    /**
    * Returns the periodic interest rate for a cash flow with constant periodic payments (annuities)
    * Excel equivalent: RATE
    *
    * @param int        Number of periods
    * @param float      Periodic payment (annuity)
    * @param float      Present Value
    * @param float      Future Value
    * @param int        Payment type:
                            FINANCE_PAY_END (default):    at the end of each period
                            FINANCE_PAY_BEGIN:            at the beginning of each period
    * @param float      guess for the interest rate
    * @return float     
    * @static
    * @access public
    */
    function rate($nper, $pmt, $pv, $fv = 0, $type = 0, $guess = 0.1)
    {
        // To solve the equation
        require_once 'Math/Numerical/RootFinding/NewtonRaphson.php';
        // To preserve some variables in the Newton-Raphson callback functions
        require_once 'Math/Finance_FunctionParameters.php';

        if ($type != FINANCE_PAY_END && $type != FINANCE_PAY_BEGIN) {
            return PEAR::raiseError('Payment type must be FINANCE_PAY_END or FINANCE_PAY_BEGIN');
        }

        // Utilization of a Singleton class to preserve given values of other variables in the callback functions
        $parameters = array(
            'nper'  => $nper,
            'pmt'   => $pmt,
            'pv'    => $pv,
            'fv'    => $fv,
            'type'  => $type,
        );
		$parameters_class =& Math_Finance_FunctionParameters::getInstance($parameters, True);

        $newtonRaphson = new Math_Numerical_RootFinding_Newtonraphson(array('err_tolerance' => FINANCE_PRECISION));
        return $newtonRaphson->compute(array('Math_Finance', '_tvm'), array('Math_Finance', '_dtvm'), $guess);
    }

    /**
    * Callback function only used by Newton-Raphson algorithm. Returns value of function to be solved.
    *
    * Uses a previously instanced Singleton class to retrieve given values of other variables in the function
    *
    * @param float      Interest rate
    * @return float     
    * @static
    * @access private
    */
    function _tvm($rate)
    {
        require_once 'Math/Finance_FunctionParameters.php';

		$parameters_class =& Math_Finance_FunctionParameters::getInstance();
        $nper   = $parameters_class->parameters['nper'];
        $pmt    = $parameters_class->parameters['pmt'];
        $pv     = $parameters_class->parameters['pv'];
        $fv     = $parameters_class->parameters['fv'];
        $type   = $parameters_class->parameters['type'];

        return $pv * pow(1 + $rate, $nper) + $pmt * (1 + $rate * $type) * (pow(1 + $rate, $nper) - 1) / $rate + $fv;
    }

    /**
    * Callback function only used by Newton-Raphson algorithm. Returns value of derivative of function to be solved.
    *
    * Uses a previously instanced Singleton class to retrieve given values of other variables in the function
    *
    * @return float     
    * @static
    * @access private
    */
    function _dtvm($rate)
    {
        require_once 'Math/Finance_FunctionParameters.php';

		$parameters_class =& Math_Finance_FunctionParameters::getInstance();
        $nper   = $parameters_class->parameters['nper'];
        $pmt    = $parameters_class->parameters['pmt'];
        $pv     = $parameters_class->parameters['pv'];
        $type   = $parameters_class->parameters['type'];

        return $nper * $pv * pow(1 + $rate, $nper - 1)
                 + $pmt *
                     ($type * (pow(1 + $rate, $nper) - 1) / $rate
                     + (1 + $rate * $type) * ($nper * $rate * pow(1 + $rate, $nper - 1) - pow(1 + $rate, $nper) + 1) / pow($rate,2));
    }

    /**
    * Returns the interest payment for a given period for a cash flow with constant periodic payments (annuities)
    * and interest rate.
    * Excel equivalent: IMPT
    *
    * @param float      Interest rate per period 
    * @param int        Period for which the interest payment will be calculated
    * @param int        Number of periods
    * @param float      Present Value
    * @param float      Future Value
    * @param int        Payment type:
                            FINANCE_PAY_END (default):    at the end of each period
                            FINANCE_PAY_BEGIN:            at the beginning of each period
    * @return float     
    * @static
    * @access public
    */
    function interestPayment($rate, $per, $nper, $pv, $fv = 0, $type = 0)
    {
        if ($type != FINANCE_PAY_END && $type != FINANCE_PAY_BEGIN) {
            return PEAR::raiseError('Payment type must be FINANCE_PAY_END or FINANCE_PAY_BEGIN');
        }

        $interestAndPrincipal = Math_Finance::_interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
        return $interestAndPrincipal[0];
    }

    /**
    * Returns the principal payment for a given period for a cash flow with constant periodic payments (annuities)
    * and interest rate
    * Excel equivalent: PPMT
    *
    * @param float      Interest rate per period 
    * @param int        Period for which the principal payment will be calculated
    * @param int        Number of periods
    * @param float      Present Value
    * @param float      Future Value
    * @param int        Payment type:
                            FINANCE_PAY_END (default):    at the end of each period
                            FINANCE_PAY_BEGIN:            at the beginning of each period
    * @return float     
    * @static
    * @access public
    */
    function principalPayment($rate, $per, $nper, $pv, $fv = 0, $type = 0)
    {
        if ($type != FINANCE_PAY_END && $type != FINANCE_PAY_BEGIN) {
            return PEAR::raiseError('Payment type must be FINANCE_PAY_END or FINANCE_PAY_BEGIN');
        }

        $interestAndPrincipal = Math_Finance::_interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type);
        return $interestAndPrincipal[1];
    }

    /**
    * Returns the interest and principal payment for a given period for a cash flow with constant 
    * periodic payments (annuities) and interest rate
    *
    * @param float      Interest rate per period 
    * @param int        Number of periods
    * @param float      Present Value
    * @param float      Future Value
    * @param int        Payment type:
                            FINANCE_PAY_END (default):    at the end of each period
                            FINANCE_PAY_BEGIN:            at the beginning of each period
    * @return array
    * @static
    * @access private
    */
    function _interestAndPrincipal($rate, $per, $nper, $pv, $fv, $type)
    {
        $pmt = Math_Finance::payment($rate, $nper, $pv, $fv, $type);
        //echo "pmt: $pmt\n\n";
        $capital = $pv;
        for ($i = 1; $i<= $per; $i++) {
            // in first period of advanced payments no interests are paid
            $interest = ($type && $i == 1)? 0 : -$capital * $rate;
            $principal = $pmt - $interest;
            $capital += $principal;
            //echo "$i\t$capital\t$interest\t$principal\n";
        }
        return array($interest, $principal);
    }

    /*******************************************************************
    ** Cash Flow Functions                                        *****
    *******************************************************************/

    /**
    * Returns the Net Present Value of a cash flow series given a discount rate
    * Excel equivalent: NPV
    *
    * @param float      Discount interest rate
    * @param array      Cash flow series
    * @return float     
    * @static
    * @access public
    */

    function netPresentValue($rate, $values)
    {
        if (!is_array($values)) {
            return PEAR::raiseError('The cash flow series most be an array');
        }
    
        return MATH_Finance::_npv($rate, $values);
    }

    /**
    * Returns the internal rate of return of a cash flow series
    * Excel equivalent: IRR
    *
    * @param array      Cash flow series
    * @param float      guess for the interest rate
    * @return float     
    * @static
    * @access public
    */
    function internalRateOfReturn($values, $guess = 0.1)
    {
        // To solve the equation
        require_once 'Math/Numerical/RootFinding/NewtonRaphson.php';
        // To preserve some variables in the Newton-Raphson callback functions
        require_once 'Math/Finance_FunctionParameters.php';

        if (!is_array($values)) {
            return PEAR::raiseError('The cash flow series most be an array');
        }
        if (min($values) * max($values) >= 0) {
            return PEAR::raiseError('Cash flow must contain at least one positive value and one negative value');
        }

		$parameters_class =& Math_Finance_FunctionParameters::getInstance(array('values' => $values), True);
        $newtonRaphson = new Math_Numerical_RootFinding_Newtonraphson(array('err_tolerance' => FINANCE_PRECISION));
        return $newtonRaphson->compute(array('Math_Finance', '_npv'), array('Math_Finance', '_dnpv'), $guess);
    }

    /**
    * Function used by NPV() and as a callback by Newton-Raphson algorithm.
    * Returns value of Net Present Value of a cash flow series.
    *
    * Uses a previously instanced Singleton class to retrieve given values of other variables in the function
    *
    * @param float      Discount interest rate
    * @param array      Cash flow series
    * @return float     
    * @static
    * @access private
    */
    function _npv($rate, $values = array())
    {
        require_once 'Math/Finance_FunctionParameters.php';

        if (!$values) {
            // called from IRR
		    $parameters_class =& Math_Finance_FunctionParameters::getInstance();
            $values = $parameters_class->parameters['values'];
        }

        $npv = 0;
        $nper = count($values);
        for ($i = 1; $i <= $nper; $i++) {
            $npv += $values[$i-1]/ pow(1 + $rate, $i);
        }
        return $npv;
    }

    /**
    * Callback function used by by Newton-Raphson algorithm to calculate IRR.
    * Returns value of derivative function to be solved.
    *
    * Uses a previously instanced Singleton class to retrieve given values of other variables in the function
    *
    * @param float      Discount interest rate
    * @param array      Cash flow series
    * @return float     
    * @static
    * @access private
    */
    function _dnpv($rate, $values = array())
    {
        require_once 'Math/Finance_FunctionParameters.php';

        if (!$values) {
            // called from IRR
		    $parameters_class =& Math_Finance_FunctionParameters::getInstance();
            $values = $parameters_class->parameters['values'];
        }

        $dnpv = 0;
        $nper = count($values);
        for ($i = 1; $i <= $nper; $i++) {
            $dnpv += $values[$i-1] * (-$i) * pow(1 + $rate, $i - 1) / pow(1 + $rate, 2 * $i);
        }
        return $dnpv;
    }

    /**
    * Returns the internal rate of return of a cash flow series, considering both financial and reinvestment rates
    * Excel equivalent: MIRR
    *
    * @param array      Cash flow series
    * @param float      Interest rate on the money used in the cash flow
    * @param float      Interest rate received when reinvested
    * @return float     
    * @static
    * @access public
    */
    function modifiedInternalRateOfReturn($values, $finance_rate, $reinvest_rate)
    {
        if (!is_array($values)) {
            return PEAR::raiseError('The cash flow series most be an array');
        }
        if (min($values) * max($values) >= 0) {
            return PEAR::raiseError('Cash flow must contain at least one positive value and one negative value');
        }

        $positive_flows = $negative_flows = array();
        foreach ($values as $value) {
            if ($value >= 0) {
                $positive_flows[] = $value;
                $negative_flows[] = 0;
            } else {
                $positive_flows[] = 0;
                $negative_flows[] = $value;
            }
        }

        $nper = count($values);

        return pow(-Math_Finance::netPresentValue($reinvest_rate, $positive_flows) * pow(1 + $reinvest_rate, $nper)
                / Math_Finance::netPresentValue($finance_rate, $negative_flows) / (1 + $finance_rate), 1/($nper - 1)) - 1;
    }

    /*******************************************************************
    ** Bonds Functions                                             *****
    *******************************************************************/

    /**
    * Returns the difference of days between two dates based on a daycount basis
    *
    * @param int        First date (UNIX timestamp)
    * @param int        Second date (UNIX timestamp)
    * @param int        Type of day count basis:
                            FINANCE_COUNT_NASD(default):    US(NASD) 30/360
                            FINANCE_COUNT_ACTUAL_ACTUAL:    Actual/actual
                            FINANCE_COUNT_ACTUAL_360:       Actual/360
                            FINANCE_COUNT_ACTUAL_365:       Actual/365
                            FINANCE_COUNT_EUROPEAN:         European 30/360
    * @return int
    * @static
    * @access public
    */
    function daysDifference($date1, $date2, $basis)
    {
        $y1 = date('Y', $date1);
        $m1 = date('n', $date1);
        $d1 = date('j', $date1);
        $y2 = date('Y', $date2);
        $m2 = date('n', $date2);
        $d2 = date('j', $date2);

        switch ($basis) {
            case FINANCE_COUNT_NASD:
                if ($d2 == 31 && ($d1 == 30 || $d1 == 31)) {
                    $d2 = 30;
                }
                if ($d1 == 31) {
                    $d1 = 30;
                }
                return ($y2 - $y1) * 360 + ($m2 - $m1) * 30 + $d2 - $d1;
            case FINANCE_COUNT_ACTUAL_ACTUAL:
            case FINANCE_COUNT_ACTUAL_360:
            case FINANCE_COUNT_ACTUAL_365:
                return ($date2 - $date1) / 86400;
            case FINANCE_COUNT_EUROPEAN: // European 30/360
                return ($y2 - $y1) * 360 + ($m2 - $m1) * 30 + $d2 - $d1;
        }
    }

    /**
    * Returns the number of days in the year based on a daycount basis
    *
    * @param int        Year
    * @param int        Type of day count basis:
                            FINANCE_COUNT_NASD(default):    US(NASD) 30/360
                            FINANCE_COUNT_ACTUAL_ACTUAL:    Actual/actual
                            FINANCE_COUNT_ACTUAL_360:       Actual/360
                            FINANCE_COUNT_ACTUAL_365:       Actual/365
                            FINANCE_COUNT_EUROPEAN:         European 30/360
    * @return int
    * @static
    * @access public
    */
    function daysPerYear($year, $basis)
    {
        switch ($basis) {
            case FINANCE_COUNT_NASD:
                return 360;
            case FINANCE_COUNT_ACTUAL_ACTUAL:
                return checkdate(2, 29, $year)? 366 : 365;
            case FINANCE_COUNT_ACTUAL_360:
                return 360;
            case FINANCE_COUNT_ACTUAL_365:
                return 365;
            case FINANCE_COUNT_EUROPEAN:
                return 360;
        }
    }

    /**
    * Returns the yield for a treasury bill
    * Excel equivalent: TBILLYIELD
    *
    * @param int        Settlement date (UNIX timestamp)
    * @param int        Maturity date (UNIX timestamp)
    * @param float      TBill price per $100 face value
    * @return float     
    * @static
    * @access public
    */
    function TBillYield($settlement, $maturity, $pr)
    {
        if ($settlement >= $maturity) {
            return PEAR::raiseError('Maturity must happen before settlement!');
        }

        $dsm = ($maturity - $settlement) / 86400;   // transform to days

        if ($dsm > 360) {
            return PEAR::raiseError("maturity can't be more than one year after settlement");
        }

        return (100 - $pr) * 360 / $pr / $dsm;
    }

    /**
    * Returns the price per $100 face value for a Treasury bill
    * Excel equivalent: TBILLPRICE
    *
    * @param int        Settlement date (UNIX timestamp)
    * @param int        Maturity date (UNIX timestamp)
    * @param float      T-Bill discount rate
    * @return float     
    * @static
    * @access public
    */
    function TBillPrice($settlement, $maturity, $discount)
    {
        if ($settlement >= $maturity) {
            return PEAR::raiseError('Maturity must happen before settlement!');
        }

        $dsm = ($maturity - $settlement) / 86400;   // transform to days

        if ($dsm > 360) {
            return PEAR::raiseError("maturity can't be more than one year after settlement");
        }

        return 100 * (1 - $discount * $dsm / 360);
    }

    /**
    * Returns the bond-equivalent yield for a Treasury bill
    * Excel equivalent: TBILLEQ
    *
    * @param int        Settlement date (UNIX timestamp)
    * @param int        Maturity date (UNIX timestamp)
    * @param float      T-Bill discount rate
    * @return float     
    * @static
    * @access public
    */
    function TBillEquivalentYield($settlement, $maturity, $discount)
    {
        if ($settlement >= $maturity) {
            return PEAR::raiseError('Maturity must happen before settlement!');
        }

        $dsm = Math_Finance::daysDifference($settlement, $maturity, FINANCE_COUNT_ACTUAL_365);

        if ($dsm <= 182) {
            // for one half year or less, the bond-equivalent-yield is equivalent to an actual/365 interest rate
            return 365 * $discount / (360 - $discount * $dsm);
        } elseif ($dsm == 366 
                  && ((date('m', $settlement) <= 2 && checkdate(2, 29, date('Y', $settlement))) 
                  || (date('m', $settlement) > 2 && checkdate(2, 29, date('Y', $maturity))))) {
            return 2 * (sqrt(1 - $discount * 366 / ($discount * 366 - 360)) - 1);
        } elseif ($dsm > 365) {
            return PEAR::raiseError("maturity can't be more than one year after settlement");
        } else {
            // thanks to Zhang Qingpo (zhangqingpo@yahoo.com.cn) for solving this riddle :)
            return (-$dsm + sqrt(pow($dsm, 2) - (2 * $dsm - 365) * $discount * $dsm * 365 / ($discount * $dsm - 360))) / ($dsm - 365 / 2);
        }
    }

    /**
    * Returns the discount rate for a bond
    * Excel equivalent: DISC
    *
    * @param int        Settlement date (UNIX timestamp)
    * @param int        Maturity date (UNIX timestamp)
    * @param float      The bond's price per $100 face value
    * @param float      The bond's redemption value per $100 face value
    * @param int        Type of day count basis:
                            FINANCE_COUNT_NASD(default):    US(NASD) 30/360
                            FINANCE_COUNT_ACTUAL_ACTUAL:    Actual/actual
                            FINANCE_COUNT_ACTUAL_360:       Actual/360
                            FINANCE_COUNT_ACTUAL_365:       Actual/365
                            FINANCE_COUNT_EUROPEAN:         European 30/360
    * @return float     
    * @static
    * @access public
    */
    function discountRate($settlement, $maturity, $pr, $redemption, $basis = 0)
    {
        $days_per_year = Math_Finance::daysPerYear(date('Y', $settlement), $basis);
        $dsm = Math_Finance::daysDifference($settlement, $maturity, $basis);

        return ($redemption - $pr) * $days_per_year / $redemption / $dsm;
    }

    /**
    * Returns the price per $100 face value of a discounted bond
    * Excel equivalent: PRICEDISC
    *
    * @param int        Settlement date (UNIX timestamp)
    * @param int        Maturity date (UNIX timestamp)
    * @param float      The bond's discount rate
    * @param float      The bond's redemption value per $100 face value
    * @param int        Type of day count basis:
                            FINANCE_COUNT_NASD(default):    US(NASD) 30/360
                            FINANCE_COUNT_ACTUAL_ACTUAL:    Actual/actual
                            FINANCE_COUNT_ACTUAL_360:       Actual/360
                            FINANCE_COUNT_ACTUAL_365:       Actual/365
                            FINANCE_COUNT_EUROPEAN:         European 30/360
    * @return float     
    * @static
    * @access public
    */
    function priceDiscount($settlement, $maturity, $discount, $redemption, $basis = 0)
    {
        $days_per_year = Math_Finance::daysPerYear(date('Y', $settlement), $basis);
        $dsm = Math_Finance::daysDifference($settlement, $maturity, $basis);

        return $redemption - $discount * $redemption * $dsm / $days_per_year;
    }


    /*******************************************************************
    ** Depreciation Functions                                      *****
    *******************************************************************/

    /**
    * Returns the depreciation of an asset using the fixed-declining balance method
    * Excel equivalent: DB
    *
    * @param float      The initial cost of the asset
    * @param float      Salvage value of the asset
    * @param int        Number of depreciation periods (same unit as $life)
    * @param int        Number of months in the first year, defaults to 12
    * @return float     
    * @static
    * @access public
    */
    function depreciationFixedDeclining($cost, $salvage, $life, $period, $month = 12)
    {
        $cost       = (float) $cost;
        $salvage    = (float) $salvage;
        $life       = (int) $life;
        $period     = (int) $period;
        $month      = (int) $month;
        if ($cost < 0 || $life < 0) {
            return PEAR::raiseError('cost and life must be absolute positive numbers');
        }
        if ($period < 1) {
            return PEAR::raiseError('period must be greater or equal than one');
        }

        $rate = 1 - pow(($salvage / $cost), (1 / $life));
        $rate = round($rate, 3);

        $acc_depreciation = 0;
        for ($i = 1; $i <= $period; $i++) {
            if ($i == 1) {
                $depreciation_period = $cost * $rate * $month / 12;
            } elseif ($i == ($life + 1)) {
                $depreciation_period = ($cost - $acc_depreciation) * $rate * (12 - $month) / 12;
            } else {
                $depreciation_period = ($cost - $acc_depreciation) * $rate;
            }
            $acc_depreciation += $depreciation_period;
        }

        return $depreciation_period;
    }

    /**
    * Returns the straight-line depreciation of an asset for each period
    * Excel equivalent: SLN
    *
    * @param float      The initial cost of the asset
    * @param float      Salvage value of the asset
    * @param int        Number of depreciation periods
    * @return float     
    * @static
    * @access public
    */
    function depreciationStraightLine($cost, $salvage, $life)
    {
        $life       = (int) $life;
        if ($cost < 0 || $life < 0) {
            return PEAR::raiseError('cost and life must be absolute positive numbers');
        }

        return (($cost - $salvage) / $life);
    }

    /**
    * Returns the depreciation for an asset in a given period using the sum-of-years' digits method
    * Excel equivalent: SYD
    *
    * @param float      The initial cost of the asset
    * @param float      Salvage value of the asset
    * @param int        Number of depreciation periods
    * @param int        Period (must be in the same unit as $life)
    * @return float     
    * @static
    * @access public
    */
    function depreciationSYD($cost, $salvage, $life, $per)
    {
        return (($cost - $salvage) * ($life - $per + 1) * 2 / ($life) / ($life +1));
    }
}

?>
