金融金额计算

public class FinancialCalculator {
public static boolean DEBUG = false;
/*
* Basis
*
* 0, default= US (NASD) 30/360 - As with the European 30/360, with the
* additional provision that if the end date occurs on the 31st of a month
* it is moved to the 1st of the next month if the start date is earlier
* than the 30th. 1= Uses the exact number of elapsed days between the two
* dates, as well as the exact length of the year. 2= Uses the exact number
* of elapsed days between two dates but assumes the year only have 360 days
* 3- Uses the exact number of elapsed days between two dates but assumes
* the year always has 365 days 4= European 30/360 - Each month is assumed
* to have 30 days, such that the year has only 360 days. Start and end
* dates that occur on the 31st of a month become equal to the 30th of the
* same month.
*
* 30/360: If the accrual period ends on a 31st, do not change the date
* unless the period started on a 30th or 31st, in which case change the end
* date to 30th. In addition, if the accrual period ends on the last day of
* February, the month of February should not be extended to a 30 day month.
* 30/Actual: Method whereby interest is calculated based on a 30-day month
* and the assumed number of days in a year, i.e. the actual number of days
* in the accrual period multiplied by the number of interest payments in
* the year. Eg, a semi-annual bond (one paying two coupons per year) can
* display a period between coupons of 181 to 184 days. In this case, the
* number of days in a year will be 362 to 368 days.
*
* Euro: Method whereby interest is calculated based on a 30-day month (no
* exceptions, ie, February should always be extended to a 30 day month) and
* a 360-day year.
*
* Actual: Method whereby interest is calculated based on the actual number
* of accrued days (falling on a normal year) divided by 365, added to the
* actual number of accrued days (falling on a leap year) divided by 366.
*
* Actual/Actual: Is used for Treasury bonds and notes. This convention it
* refers to an interest accrual method that utilizes the actual number of
* days in a month and the actual number of days in a year.
*
* Actual/360: A day count fraction equal to actual days divided by 360
* except in the
* United Kingdom and several countries where the denominator is 365 or
* actual days. It is used for bank deposits and in calculating rates pegged
* to some indices, such as LIBOR. 30/360 Rules: It is used for corporate
* bonds, U.S. Agency bonds and all mortgage backed securities. It assumes
* that all months have 30 days, and all years have 360 days. The number of
* days from M1/D1/Y1 to M2/D2/Y2 is computed according to the following
* procedure: If D1 is 31, change D1 to 30. If D2 is 31 and D1 is 30 or 31,
* then change D2 to 30. If M1 is 2, and D1 is 28 (in a non-leap year) or
* 29, then change D1 to 30. Then the number of days, N is: N = 360(Y2-Y1) +
* 30(M2-M1) + (D2-D1).
*/
static double yearFrac(int basis, long date0, long date1) {
double result;
// deep breath …
GregorianCalendar fromDate = (GregorianCalendar) DateConverter
.getCalendarFromNumber(new Long(date0));
GregorianCalendar toDate = (GregorianCalendar) DateConverter
.getCalendarFromNumber(new Long(date1));
int y0 = fromDate.get(Calendar.YEAR);
int y1 = toDate.get(Calendar.YEAR);
int d0 = fromDate.get(Calendar.DAY_OF_MONTH);
int d1 = toDate.get(Calendar.DAY_OF_MONTH);
int m0 = fromDate.get(Calendar.MONTH);
int m1 = toDate.get(Calendar.MONTH);
double yearFrac = 1.0;
if (basis == 0) { // 30/360 US/NASD
if (d0 == 31)
d0 = 30;
if (d1 == 31 && d0 >= 30)
d1 = 30;
if (m0 == 1 && d0 >= 28)
d0 = 30;
yearFrac = (double) (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)) / 360.0;
} else if (basis == 1) { // Actual/Actual
int ndays = 0;
int i; // average # days between dates
for (i = y0; i <= y1; i++) {
ndays += isLeapYear(i) ? 366 : 365;
}
if (i!=y0)
yearFrac = (double) (date1 - date0) / ((double) ndays / (i - y0)); // yes I know it’s redundant …
else
yearFrac= date1-date0;
} else if (basis == 2) { // Actual/360
yearFrac = (double) (date1 - date0) / 360.0;
} else if (basis == 3) { // Actual/365
yearFrac = (double) (date1 - date0) / 365.0;
} else if (basis == 4) { // 30/360 EURO
// if (m01 && d0>=28) d0= 30; //???
// if (m1
1 && d1>=28) d1= 30; //???
yearFrac = (double) (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0)) / 360.0;
}
return yearFrac;
}

static double getDaysInYearFromBasis(int basis, long date0, long date1) {
	double r= 0;
	switch (basis) {
		case 0:	// 30/360
		case 2:	// actual/360
		case 4:	// 30/360 (EURO)
			r= 360;		
			break;
		case 1:	// actual/actual
			GregorianCalendar fromDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date0));
			int y0 = fromDate.get(Calendar.YEAR);
			if (isLeapYear(y0))
				r= 365.4;
			else
				r= 365.25;

// r= (date1-date0)/yearFrac(basis, date0, date1);
break;
case 3:
r= 365; // actual/365
break;
}
return r;
}

static long getDaysFromBasis(int basis, long date0, long date1) {
	if (basis == 0 || basis == 4) { // # months * 30 + extra days
		GregorianCalendar fromDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date0));
		GregorianCalendar toDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(new Long(date1));
		int y0 = fromDate.get(Calendar.YEAR);
		int y1 = toDate.get(Calendar.YEAR);
		int d0 = fromDate.get(Calendar.DAY_OF_MONTH);
		int d1 = toDate.get(Calendar.DAY_OF_MONTH);
		int m0 = fromDate.get(Calendar.MONTH);
		int m1 = toDate.get(Calendar.MONTH);
		if (basis == 0) { // 30/360 US/NASD
			// TODO: PROBLEM: When date should be 2-28-02, get
			// 2-2-02!!!!!!!!!!!!!!!
			if (d0 == 31)
				d0 = 30;
			if (d1 == 31 && d0 >= 30)
				d1 = 30;
			//				if (m0==1 && d0 >=28) d0= 30; //
			// ??????????????????????????????????????????????????
			//				if (m1 == 1 && d1 >= 28)
			//					d1 = 30; // ?????????????????
		} else { // 30/360 EURO -- CORRECT FOR ALL DATES EXCEPT MONTH OF
			// FEBRUARY (M0)!!
			//				if (m0==1 && d0>=28) d0= 30; // ?????????????????????
			//				if (m1==1 && d1>=28) d1= 30; // ?????????????????????
		}
		int result = (360 * (y1 - y0) + 30 * (m1 - m0) + (d1 - d0));
		return result;
	}
	return date1 - date0; // actual (1, 2, 3)
}

static int validateDay(int y, int m, int d) {
	if (d > 28) { // m is 0-based
		if (m == 1) // TODO: Get maximum for year
			d = 28;
		else if ((m == 3 || m == 5 || m == 8 || m == 10) && d == 31)
			d = 30;
	}
	return d;
}

/**
 * ACCRINT - Returns the accrued interest for a security that pays periodic
 * interest Analysis Pak function Issue is the security's issue date.
 * First_interest is the security's first interest date. Settlement is the
 * security's settlement date. The security settlement date is the date
 * after the issue date when the security is traded to the buyer. Rate is
 * the security's annual coupon rate. Par is the security's par value. If
 * you omit par, ACCRINT uses $1,000. Frequency is the number of coupon
 * payments per year. For annual payments, frequency = 1; for semiannual,
 * frequency = 2; for quarterly, frequency = 4. Basis is the type of day
 * count basis to use.
 * 
 * Basis Day count basis 0 or omitted US (NASD) 30/360 1 Actual/actual 2
 * Actual/360 3 Actual/365 4 European 30/360
 * 
 * ACCRINT is calculated as follows:
 * 
 * par X rate/frequency X Sum Ai/NLi over NC
 * 
 * where:
 * 
 * Ai = number of accrued days for the ith quasi-coupon period within odd
 * period. NC = number of quasi-coupon periods that fit in odd period. If
 * this number contains a fraction, raise it to the next whole number. NLi =
 * normal length in days of the ith quasi-coupon period within odd period.
 * 
 */
// TODO: Works only for basis 0 & 4
protected static Ptg calcAccrint(Ptg[] operands) throws
	FunctionNotSupportedException {
	if (true) {
        String wn = "WARNING: this version of OpenXLS does not support the formula ACCRINT.";
        Logger.logWarn(wn);
        throw new FunctionNotSupportedException(wn);
	}
	if (operands.length < 6) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG) debugOperands(operands, "ACCRINT");
	try {
	GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar fiDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue());
	GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue());
	double rate = operands[3].getDoubleVal();
	double par = 1000;
	if (!(operands[4] instanceof PtgMissArg))
		par = operands[4].getDoubleVal();
	int frequency = operands[5].getIntVal();
	int basis = 0;
	if (operands.length > 6)
		basis = operands[6].getIntVal();

	long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue();
	long firstInterestDate= (new Double(DateConverter.getXLSDateVal(fiDate))).longValue();
	long settlementDate=  (new Double(DateConverter.getXLSDateVal(sDate))).longValue();
	
	// TODO: if dates are not valid, return #VALUE! error
	if (!(frequency == 1 || frequency == 2 || frequency == 4))			return new PtgErr(PtgErr.ERROR_NUM);
	if (rate <= 0 || par <= 0)		 return new PtgErr(PtgErr.ERROR_NUM);
	if (basis < 0 || basis > 4) 	 return new PtgErr(PtgErr.ERROR_NUM);
	if (issueDate >= settlementDate) return new PtgErr(PtgErr.ERROR_NUM);

	// quasicoupon period: extend the series of equal payment periods to
	// before or after the actual payment periods
	// odd period= period between payments that differs from the usual
	// equally spaced periods at which payments are made
	// Ai= number of days which have accrued for the ith quasi-coupon period
	// within the odd period
	// equation:  ACCRINT= ((par*rate)/frequency)* Sum over NC (Ai/NLi)
	// where NC= # of quasi-coupon periods, rounded up
	//        Ai= # days in odd period i
	//        NLi=normal # days in odd period i
	// For cases where Previous Coupon Date < Issue Date, NC=1, A= getDaysFromBasis and NLi= coupDays
	
	double result = (par * rate / frequency);//* Sum #Accrued days for
	double sum = 0;
	long E, PCD;
	
	// ACCRINT in EXCEL handles this case, so we should, too!
	if (firstInterestDate<settlementDate) {
		if (DEBUG) Logger.logInfo(">>> S > FI!");

/* Ptg[] ops = new Ptg[4];
ops[0] = new PtgNumber(settlementDate);
ops[1] = new PtgNumber(firstInterestDate);
ops[2] = new PtgInt(frequency);
ops[3] = new PtgInt(basis);

		long x= PtgCalculator.getLongValue(calcCoupNCD(ops));

*/
// ???
E= 181;
PCD= issueDate;
}
else {
Ptg[] ops = new Ptg[4];
ops[0] = new PtgNumber(settlementDate);
ops[1] = new PtgNumber(firstInterestDate);
ops[2] = new PtgInt(frequency);
ops[3] = new PtgInt(basis);

		E= PtgCalculator.getLongValue(calcCoupDays(ops));
		PCD= PtgCalculator.getLongValue(calcCoupPCD(ops));
	}

// testing
if (PCD0) {
PCD= issueDate + 1;
//Logger.logInfo(">>PCD
0");
}
long A= 0;
if (basis0 || basis4)
// correct for basis 0, 4
// INCORRECT for basis 1,2 and 3
A= getDaysFromBasis(basis, issueDate, settlementDate);
else if (issueDate >= PCD)
// correct when issue >= PCD
A= getDaysFromBasis(basis, Math.max(issueDate, PCD), settlementDate);
else {
// ???
A= getDaysFromBasis(basis, Math.max(issueDate, PCD), settlementDate);
if (DEBUG) Logger.logInfo(">>I < PCD");
}

	result *= A/(double)E;

	PtgNumber pnum = new PtgNumber(result);
	// TODO: Complete Accrint Alogorithm
	if (DEBUG) Logger.logInfo("Result from Accrint= " + result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * ACCRINTM Returns the accrued interest for a security that pays interest
 * at maturity Analysis Pak function
 * 
 * Issue is the security's issue date. Maturity is the security's maturity
 * date. Rate is the security's annual coupon rate. Par is the security's
 * par value. If you omit par, ACCRINTM uses $1,000. Basis is the type of
 * day count basis to use. 0 or omitted US (NASD) 30/360 1 Actual/actual 2
 * Actual/360 3 Actual/365 4 European 30/360 ACCRINTM = par X rate x A/D
 * where: A = Number of accrued days counted according to a monthly basis.
 * For interest at maturity items, the number of days from the issue date to
 * the maturity date is used. D = Annual Year Basis.
 * 
 */
protected static Ptg calcAccrintm(Ptg[] operands) {
	if (operands.length < 3) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "ACCRINTM");
	try {
	long issueDate, maturityDate; // dates are truncated to integers
	double rate, par = 1000;
	int basis = 0;

	// Issue Date
	GregorianCalendar dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue());
	issueDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue();
	// Maturity Date
	dt = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	maturityDate = (new Double(DateConverter.getXLSDateVal(dt)))
			.longValue();
	// Annual Coupon Rate
	rate = operands[2].getDoubleVal();
	// Par value. If omitted, = 1000.
	if (operands.length > 3 && (!(operands[3] instanceof PtgMissArg)))
		par = operands[3].getDoubleVal();
	// Basis. If omitted, = 0
	if (operands.length > 4)
		basis = operands[4].getIntVal();

	// TODO: if dates are not valid, return #VALUE! error
	if (rate <= 0 || par <= 0)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (basis < 0 || basis > 4)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (issueDate > maturityDate)
		return new PtgErr(PtgErr.ERROR_NUM);

	double result = par * rate * yearFrac(basis, issueDate, maturityDate);
	if (DEBUG) Logger.logInfo("Result from Accrintm= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * AMORDEGRC Returns the depreciation for each accounting period
 */
protected static Ptg calcAmordegrc(Ptg[] operands) {
	if (operands.length < 6) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "AMORDEGRC");
	try {
	double cost = operands[0].getDoubleVal();
	GregorianCalendar dP = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue());
	GregorianCalendar fP = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue());
	double salvage = operands[3].getDoubleVal();
	int period = operands[4].getIntVal();
	double rate = operands[5].getDoubleVal();
	int basis = operands[6].getIntVal();

	long datePurchased = (new Double(DateConverter.getXLSDateVal(dP))).longValue();
	long firstPeriod = (new Double(DateConverter.getXLSDateVal(fP))).longValue();

	// TODO: if dates are not valid, return #VALUE! error
	if (rate <= 0)			return new PtgErr(PtgErr.ERROR_NUM);
	if (basis < 0 || basis > 4 || basis==2) return new PtgErr(PtgErr.ERROR_NUM);
	if (datePurchased > firstPeriod) 		return new PtgErr(PtgErr.ERROR_NUM);

	double coefficient;
	double life = 1.0 / rate;
	if (life < 3)
		coefficient = 1;
	else if (life < 5.0) // between 3 and 4 years
		coefficient = 1.5;
	else if (life <= 6.0) // between 5 and 6 years
		coefficient = 2.0;
	else
		// more than 6 years
		coefficient = 2.5;

	rate *= coefficient;
	//cost-= Math.round(yearFrac(basis, datePurchased,
	// firstPeriod)*rate*cost);
	cost -= yearFrac(basis, datePurchased, firstPeriod) * rate * cost;
	double Remainder = cost - salvage;
	double A = 0;
	if (Remainder > 0) {
		for (int i = 0; i < period; i++) {
			//A= Math.round(rate*cost);
			A = rate * cost;
			Remainder -= A;
			if (Remainder < 0) {
				/*
				 * if (period==i+1) { // A= Math.round(0.5*cost);
				 * Remainder=A; }
				 */
				if (i + 1 < period)
					A = 0;
				i = period; // exit loop
			}

			cost -= A;
		}
	} else
		A = Math.round(rate * salvage);
	double result = Math.max(Math.round(A), 0);
	if (DEBUG)
		Logger.logInfo("Result from AMORDEGRC= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * AMORLINC Returns the depreciation for each accounting period
 * 
 *  
 */
protected static Ptg calcAmorlinc(Ptg[] operands) {
	if (operands.length < 6) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "AMORLINC");
	try {
	double cost = operands[0].getDoubleVal();
	GregorianCalendar dP = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	GregorianCalendar fP = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[2].getValue());
	double salvage = operands[3].getDoubleVal();
	int period = operands[4].getIntVal();
	double rate = operands[5].getDoubleVal();
	int basis = operands[6].getIntVal();

	long datePurchased = (new Double(DateConverter.getXLSDateVal(dP)))
			.longValue();
	long firstPeriod = (new Double(DateConverter.getXLSDateVal(fP)))
			.longValue();

	// TODO: if dates are not valid, return #VALUE! error
	if (rate <= 0)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (basis < 0 || basis > 4 || basis==2)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (datePurchased > firstPeriod)
		return new PtgErr(PtgErr.ERROR_NUM);

	double A = 0;
	double B = cost - salvage;
	double C = yearFrac(basis, datePurchased, firstPeriod) * rate * cost;
	double D = cost*rate;
	long n = Math.round((cost - salvage - C) / D);
	if (period == 0 || C == 0)
		A= C;
	else if (period < n)
		A= D;
	else if (period == n)
		A= Math.min(D+(B-D*n-C), D);
	else if (period == n + 1)
		A= B-D*n-C; 
	else
		A= 0;

/*
cost -= yearFrac(basis, datePurchased, firstPeriod) * rate * cost;
if (Remainder > 0) {
for (int i = 0; i < period; i++) {
//A= Math.round(ratecost);
A = rate * cost;
Remainder -= A;
if (Remainder < 0) {
/

* if (period==i+1) { // A= Math.round(0.5*cost);
}
* /
if (i + 1 < period)
A = 0;
i = period; // exit loop
}
cost -= A;
}
} else
A = Math.round(rate * salvage);
/
double result = Math.max(Math.round(A), 0);
/

double A = cost * rate;
double B = cost - salvage;
//double yf= yearFrac(basis, datePurchased, firstPeriod);
double C = yearFrac(basis, datePurchased, firstPeriod) * rate * cost;
long n = Math.round((cost - salvage - C) / A);
double result;
if (period == 0 || C == 0)
result = C;
else if (period < n)
result = A;
else if (period == n)
result = A - C; //B - A * n - C;
else if (period == n + 1)
result = A - C; //B - A * n - C;
else
result = 0;
*/ if (DEBUG)
Logger.logInfo("Result from AMORLINC= " + result);
PtgNumber pnum = new PtgNumber(result);
return pnum;
}catch (Exception e) { }
return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * COUPDAYBS --
 * 
 * Returns the number of days from the beginning of the coupon period to the
 * settlement date Settlement is the security's settlement date. The
 * security settlement date is the date after the issue date when the
 * security is traded to the buyer. Maturity is the security's maturity
 * date. The maturity date is the date when the security expires. Frequency
 * is the number of coupon payments per year. For annual payments, frequency =
 * 1; for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is
 * the type of day count basis to use. (optional)
 * 
 *  
 */
protected static Ptg calcCoupDayBS(Ptg[] operands) {
	if (operands.length < 2) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG) debugOperands(operands, "COUPDAYSBS");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue());
	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue();
	int frequency = operands[2].getIntVal();
	int basis = 0;
	if (operands.length > 3)
		basis = operands[3].getIntVal();
	//			 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)  return new PtgErr(PtgErr.ERROR_NUM);
	if (!(frequency == 1 || frequency == 2 || frequency == 4)) return new PtgErr(PtgErr.ERROR_NUM);
	if (settlementDate > maturityDate)  return new PtgErr(PtgErr.ERROR_NUM);

	long pcd = PtgCalculator.getLongValue(calcCoupPCD(operands));

	double result = getDaysFromBasis(basis, pcd, settlementDate);
	
	// 
	
	
	if (DEBUG)
		Logger.logInfo("Result from calcCoupDaysBS= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * COUPDAYS Returns the number of days in the coupon period that contains
 * the settlement date Settlement is the security's settlement date. The
 * security settlement date is the date after the issue date when the
 * security is traded to the buyer Maturity is the security's maturity date.
 * The maturity date is the date when the security expires. Frequency is the
 * number of coupon payments per year. For annual payments, frequency = 1;
 * for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is the
 * type of day count basis to use
 * 
 */
protected static Ptg calcCoupDays(Ptg[] operands) {
	if (operands.length < 3) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "COUPDAYS");
	try {
	GregorianCalendar dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue());
	long settlementDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue();
	dt = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue());
	long maturityDate = (new Double(DateConverter.getXLSDateVal(dt))).longValue();
	int frequency = operands[2].getIntVal();
	int basis = 0;
	if (operands.length > 3)
		basis = operands[3].getIntVal();
	// TODO: if dates are not valid, return #VALUE! error
	if (!(frequency == 1 || frequency == 2 || frequency == 4)) return new PtgErr(PtgErr.ERROR_NUM);
	if (basis < 0 || basis > 4)  return new PtgErr(PtgErr.ERROR_NUM);
	if (settlementDate > maturityDate)  return new PtgErr(PtgErr.ERROR_NUM);
	
	// VERY strange, but seems to be correct
	double result;
	if (basis==1) {	// actual/actual
		long pcd = PtgCalculator.getLongValue(calcCoupPCD(operands));
		long ncd= PtgCalculator.getLongValue(calcCoupNCD(operands));
		result = getDaysFromBasis(basis, pcd, ncd);
	}
	else if (basis==0 || basis==2 || basis==4)
		result= 360.0/frequency;
	else
		result= 365.0/frequency;
	if (DEBUG)
		Logger.logInfo("Result from calcCoupDays=" + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * COUPDAYSNC - Returns the number of days from the settlement date to the
 * next coupon date
 * 
 * Settlement is the security's settlement date. The security settlement
 * date is the date after the issue date when the security is traded to the
 * buyer Maturity is the security's maturity date. The maturity date is the
 * date when the security expires. Frequency is the number of coupon
 * payments per year. For annual payments, frequency = 1; for semiannual,
 * frequency = 2; for quarterly, frequency = 4. Basis is the type of day
 * count basis to use
 * 
 */
protected static Ptg calcCoupDaysNC(Ptg[] operands) {
	if (operands.length < 2) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "COUPDAYSNC");
	long settlementDate = new Long(operands[0].getValue().toString()).longValue();
	long maturityDate = new Long(operands[1].getValue().toString()).longValue();
	int frequency = operands[2].getIntVal();
	int basis = 0;
	if (operands.length > 3)
		basis = operands[3].getIntVal();
	//		 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (!(frequency == 1 || frequency == 2 || frequency == 4))
		return new PtgErr(PtgErr.ERROR_NUM);
	if (settlementDate > maturityDate)
		return new PtgErr(PtgErr.ERROR_NUM);

	long ncd = PtgCalculator.getLongValue(calcCoupNCD(operands));

	double result = getDaysFromBasis(basis, settlementDate, ncd);
	if (DEBUG)
		Logger.logInfo("Result from calcCoupDaysNC= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * COUPNCD Returns the next coupon date after the settlement date Returns a
 * number that represents the next coupon date after the settlement date.
 * COUPNCD(settlement,maturity,frequency,basis) Important Dates should be
 * entered by using the DATE function, or as results of other formulas or
 * functions. For example, use DATE(2008,5,23) for the 23rd day of May,
 * 2008. Problems can occur if dates are entered as text. Settlement is the
 * security's settlement date. The security settlement date is the date
 * after the issue date when the security is traded to the buyer. Maturity
 * is the security's maturity date. The maturity date is the date when the
 * security expires. Frequency is the number of coupon payments per year.
 * For annual payments, frequency = 1; for semiannual, frequency = 2; for
 * quarterly, frequency = 4. Basis is the type of day count basis to use.
 *  
 */
protected static Ptg calcCoupNCD(Ptg[] operands) {
	if (operands.length < 2) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "COUPNCD");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	int frequency = operands[2].getIntVal();
	int basis = 0;
	if (operands.length > 3)
		basis = operands[3].getIntVal();
	//		 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (!(frequency == 1 || frequency == 2 || frequency == 4))
		return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))
		return new PtgErr(PtgErr.ERROR_NUM);

	GregorianCalendar resultDate;
	int mm = mDate.get(Calendar.MONTH) + 1; // months are 0-based but calc
	// needs 1-based - for now!
	int sm = sDate.get(Calendar.MONTH) + 1;
	int y = mDate.get(Calendar.YEAR);
	int d = mDate.get(Calendar.DAY_OF_MONTH);

	if (frequency == 1) { // annual
		while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) {
			y--;
		}
        y++;
	}
	if (frequency == 2) { // semi-annual
		while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) {
            mm-=6;
            if (mm<1) {
                mm += 12;
                y--;
            }
           }
        mm+=6;
	} else if (frequency == 4) { // quarterly
		while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) {
            mm-=3;
            if (mm<1) {
                mm += 12;
                y--;
            }
		}
        mm+=3;
	}
	resultDate = new GregorianCalendar(y, mm - 1, d);
	double date = DateConverter.getXLSDateVal(resultDate);
	if (DEBUG)
		Logger.logInfo("Result from calcCoupNCD= "
				+ date
				+ " "
				+ java.text.DateFormat.getDateInstance().format(
						resultDate.getTime()));
	int i = (int) date;
	PtgInt pi = new PtgInt(i);
	return pi;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * COUPNUM Returns the number of coupons payable between the settlement date
 * and maturity date Settlement is the security's settlement date. The
 * security settlement date is the date after the issue date when the
 * security is traded to the buyer Maturity is the security's maturity date.
 * The maturity date is the date when the security expires. Frequency is the
 * number of coupon payments per year. For annual payments, frequency = 1;
 * for semiannual, frequency = 2; for quarterly, frequency = 4. Basis is the
 * type of day count basis to use
 * 
 */
protected static Ptg calcCoupNum(Ptg[] operands) {
	if (operands.length < 2) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "COUPNUM");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate)))
			.longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate)))
			.longValue();
	int frequency = operands[2].getIntVal();
	int basis = 0;
	if (operands.length > 3)
		basis = operands[3].getIntVal();
	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (!(frequency == 1 || frequency == 2 || frequency == 4))
		return new PtgErr(PtgErr.ERROR_NUM);
	if (settlementDate > maturityDate)
		return new PtgErr(PtgErr.ERROR_NUM);

/*
double result = getDaysFromBasis(basis, settlementDate, maturityDate)
/ (getDaysInYearFromBasis(basis, settlementDate, maturityDate) / frequency);
result = Math.ceil(result);
double delta= maturityDate-settlementDate;
double result= delta/calcCoupDays(operands));
*/
double result= Math.ceil(yearFrac(basis, settlementDate, maturityDate)*frequency);
if (DEBUG)
Logger.logInfo("Result from calcCoupNUM= " + result);
PtgNumber pnum = new PtgNumber(result);
return pnum;
}catch (Exception e) { }
return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * COUPPCD Returns the previous coupon date before the settlement date
 */
protected static Ptg calcCoupPCD(Ptg[] operands) {
	if (operands.length < 2) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)			debugOperands(operands, "COUPPCD");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue());
	int frequency = operands[2].getIntVal();
	int basis = 0;
	if (operands.length > 3)
		basis = operands[3].getIntVal();
	//		 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)			return new PtgErr(PtgErr.ERROR_NUM);
	if (!(frequency == 1 || frequency == 2 || frequency == 4))
		return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))			return new PtgErr(PtgErr.ERROR_NUM);

    GregorianCalendar resultDate;
    int mm = mDate.get(Calendar.MONTH) + 1; // months are 0-based but calc
    // needs 1-based - for now!
    int sm = sDate.get(Calendar.MONTH) + 1;
    int y = mDate.get(Calendar.YEAR);
    int d = mDate.get(Calendar.DAY_OF_MONTH);

    if (frequency == 1) { // annual
        while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) {
            y--;
        }
    }
    if (frequency == 2) { // semi-annual
        while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) {
            mm-=6;
            if (mm<1) {
                mm += 12;
                y--;
            }
           }
    } else if (frequency == 4) { // quarterly
        while (sDate.before((new GregorianCalendar(y, mm - 1, d)))) {
            mm-=3;
            if (mm<1) {
                mm += 12;
                y--;
            }
        }
    }
    resultDate = new GregorianCalendar(y, mm - 1, d);
    double date = DateConverter.getXLSDateVal(resultDate);
    if (DEBUG)
        Logger.logInfo("Result from calcCoupPCD= "
                + date
                + " "
                + java.text.DateFormat.getDateInstance().format(
                        resultDate.getTime()));
    int i = (int) date;
    PtgInt pi = new PtgInt(i);
    return pi;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * CUMIPMT Returns the cumulative interest paid between two periods
 * CUMIPMT(rate,nper,pv,start_period,end_period,type) All parameters are
 * require Rate is the interest rate. Nper is the total number of payment
 * periods. Pv is the present value. Start_period is the first period in the
 * calculation. Payment periods are numbered beginning with 1. End_period is
 * the last period in the calculation. Type is the timing of the payment.
 * 
 * Type Timing 0 (zero) Payment at the end of the period 1 Payment at the
 * beginning of the period
 *  
 */
protected static Ptg calcCumIPmt(Ptg[] operands) {
	if (operands.length < 6) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	double rate = operands[0].getDoubleVal();
	double nper = operands[1].getDoubleVal();
	double pv = operands[2].getDoubleVal();
	int startperiod = operands[3].getIntVal();
	int endperiod = operands[4].getIntVal();
	int type = operands[5].getIntVal();

	if (DEBUG)
		debugOperands(operands, "CUMIPMT");
	if (rate <= 0 || pv <= 0 || nper <= 0)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (startperiod < 1 || endperiod < 1)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (startperiod > endperiod)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (type < 0 || type > 1)
		return new PtgErr(PtgErr.ERROR_NUM);

	// CumIPMT= pmt*period - FV for start-1 - pmt - FV for end period and
	// pmt
	double A, B;
	//	PMT used in fv calc
	double Rn = Math.pow(1 + rate, nper);
	A = -pv * Rn * rate;
	B = (Rn - 1) * (1 + rate * type);
	double pmt = A / B;

	// WORKS on everything BUT type=1 AND startperiod=1 !!!!!!
	double n = startperiod - 1 - type;
	int period = endperiod - startperiod + 1;

	// FVa (StartPeriod)
	A = Math.pow(1 + rate, n);
	B = pmt * (1 + rate * type);
	B *= (Math.pow(1 + rate, n) - 1) / rate;
	double fva = -(pv * A + B);
	// FVb (endPeriod)
	A = Math.pow(1 + rate, endperiod - type);
	B = pmt * (1 + rate * type);
	B *= (Math.pow(1 + rate, endperiod - type) - 1) / rate;
	double fvb = -(pv * A + B);

	double result = fva - fvb - (pmt * period); //- (fva - fvb);
	if (startperiod == 1 && type == 1)
		result = (pmt * period) + pv; // I'm sure there's a good reason for
									  // this!?!?!
	result *= -1;
	if (DEBUG)
		Logger.logInfo("Result from calcCumIPmt= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * CUMPRINC Returns the cumulative principal paid on a loan between
 * start_period and end_period. Syntax
 * 
 * CUMPRINC(rate,nper,pv,start_period,end_period,type)
 * 
 * Rate is the interest rate. Nper is the total number of payment periods.
 * Pv is the present value. Start_period is the first period in the
 * calculation. Payment periods are numbered beginning with 1. End_period is
 * the last period in the calculation. Type is the timing of the payment.
 * Type Timing 0 (zero) Payment at the end of the period 1 Payment at the
 * beginning of the period
 *  
 */
protected static Ptg calcCumPrinc(Ptg[] operands) {
	if (operands.length < 6) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	double rate = operands[0].getDoubleVal();
	double nper = operands[1].getDoubleVal();
	double pv = operands[2].getDoubleVal();
	int startperiod = operands[3].getIntVal();
	int endperiod = operands[4].getIntVal();
	int type = operands[5].getIntVal();

	if (DEBUG)
		debugOperands(operands, "CUMPRINC");
	// Cumprinc= FV for start-1 and pmt - FV for end period and pmt
	double A, B;
	//	PMT used in fv calc
	double Rn = Math.pow(1 + rate, nper);
	A = -pv * Rn * rate;
	B = (Rn - 1) * (1 + rate * type);
	double pmt = A / B;

	// FVa (StartPeriod)
	A = Math.pow(1 + rate, startperiod - type - 1);
	B = pmt * (1 + rate * type);
	B *= (Math.pow(1 + rate, startperiod - type - 1) - 1) / rate;
	double fva = -(pv * A + B);
	// FVb (endPeriod)
	A = Math.pow(1 + rate, endperiod - type);
	B = pmt * (1 + rate * type);
	B *= (Math.pow(1 + rate, endperiod - type) - 1) / rate;
	double fvb = -(pv * A + B);

	double result = fva - fvb;
	if (startperiod == 1 && type == 1)
		result = pv; // I'm sure there's a good reason for this!?!?!
	if (DEBUG)
		Logger.logInfo("Result from calcCUMPRINC= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * DB Returns the depreciation of an asset for a specified period using the
 * fixed-declining balance method
 * 
 * DB(cost,salvage,life,period,month)
 * 
 * Cost is the initial cost of the asset.
 * 
 * Salvage is the value at the end of the depreciation (sometimes called the
 * salvage value of the asset).
 * 
 * Life is the number of periods over which the asset is being depreciated
 * (sometimes called the useful life of the asset).
 * 
 * Period is the period for which you want to calculate the depreciation.
 * Period must use the same units as life.
 * 
 * Month is the number of months in the first year. If month is omitted, it
 * is assumed to be 12.
 */

protected static Ptg calcDB(Ptg[] operands) {
	if (operands.length < 4 || operands[0].getComponents() != null) { // not
		// supported
		// by
		// function
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	double cost, salvage;
	int life, period, month;
	cost = new Double(String.valueOf(operands[0].getValue())).doubleValue();
	salvage = new Double(String.valueOf(operands[1].getValue()))
			.doubleValue();
	life = Integer.valueOf(String.valueOf(operands[2].getValue())).intValue();
	period = Integer.valueOf(String.valueOf(operands[3].getValue())).intValue();
	if (operands.length > 4) {
		if (operands[4] instanceof PtgMissArg) {
			month = 12;
		} else {
			month = Integer.valueOf(String.valueOf(operands[4].getValue()))
					.intValue();
		}
	} else {
		month = 12;
	}
	double salCost = salvage / cost;
	// this section longhand due to some wierd calcs when calling lifdiv =
	// 1/life;
	double lifdiv = 1;
	lifdiv /= life;
	double rate = Math.pow(salCost, lifdiv);
	rate = 1 - rate;
	rate = rate * 1000;
	rate = Math.round(rate);
	rate /= 1000;

	double totalDepreciation = cost * rate * month / 12;
	double result= totalDepreciation;
	// 1st and last (i.e. period==life) are special cases 
	for (int i = 2; i < period || (i==period && period <= life); i++) {
		result = (cost - totalDepreciation) * rate;
		totalDepreciation+= (cost - totalDepreciation) * rate;
	}		
	if (period>life)	// last depreciation is special calc
		result= (cost - totalDepreciation) * rate * (12-month)/12;
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * DDB Returns the depreciation of an asset for a spcified period using the
 * double-declining balance method or some other method you specify
 * 
 * DDB(cost,salvage,life,period,factor)
 * 
 * Cost is the initial cost of the asset.
 * 
 * Salvage is the value at the end of the depreciation (sometimes called the
 * salvage value of the asset).
 * 
 * Life is the number of periods over which the asset is being depreciated
 * (sometimes called the useful life of the asset).
 * 
 * Period is the period for which you want to calculate the depreciation.
 * Period must use the same units as life.
 * 
 * Factor is the rate at which the balance declines. If factor is omitted,
 * it is assumed to be 2 (the double-declining balance method).
 * 
 * All five arguments must be positive numbers.
 *  
 */
protected static Ptg calcDDB(Ptg[] operands) {
	if (operands.length < 4 || operands[0].getComponents() != null) { // not
		// supported
		// by
		// function
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	double cost, salvage;
	int life, period, factor;
	cost = new Double(String.valueOf(operands[0].getValue())).doubleValue();
	salvage = new Double(String.valueOf(operands[1].getValue()))
			.doubleValue();
	life = Integer.valueOf(String.valueOf(operands[2].getValue())).intValue();
	period = Integer.valueOf(String.valueOf(operands[3].getValue())).intValue();
	factor= 2;
	if (operands.length > 4) {
		if (!(operands[4] instanceof PtgMissArg)) 
			factor = Integer.valueOf(String.valueOf(operands[4].getValue()))
					.intValue();
	}
	double salCost = salvage / cost;
	// this section longhand due to some wierd calcs when calling lifdiv =
	// 1/life;
	double facLife= factor/((double) life);
	double totalDepreciation = 0;

/* original calc
for (int i = 1; i < period; i++) {
//((cost-salvage) - total depreciation from prior periods) * (factor/life)
totalDepreciation += (cost - salvage - totalDepreciation)*facLife;
}
// double result= (cost - salvage - totalDepreciation) * (facLife);
*
*/
for (int i = 1; i < period; i++) {
totalDepreciation+= (cost-totalDepreciation)*facLife;
}
double result= 0.0;
if (cost-salvage-totalDepreciation > 0)
result = (cost-totalDepreciation)*facLife;
PtgNumber pnum = new PtgNumber(result);
return pnum;
}

/**
 * DISC Returns the discount rate for a security
 * 
 * Settlement is the security's settlement date. The security settlement
 * date is the date after the issue date when the security is traded to the
 * buyer. Maturity is the security's maturity date. The maturity date is the
 * date when the security expires. Pr is the security's price per $100 face
 * value. Redemption is the security's redemption value per $100 face value.
 * Basis (optional) is the type of day count basis to use.
 */
protected static Ptg calcDISC(Ptg[] operands) {
	if (operands.length < 4) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcDISC");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate)))
			.longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate)))
			.longValue();
	double pr = operands[2].getDoubleVal();
	double redemption = operands[3].getDoubleVal();
	int basis = 0;
	if (operands.length > 4)
		basis = operands[4].getIntVal();
	//		 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (settlementDate > maturityDate)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (pr <= 0 || redemption <= 0)
		return new PtgErr(PtgErr.ERROR_NUM);

// double DSM = maturityDate - settlementDate;
// double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate);
// double result = (redemption - pr) / redemption * (B / DSM);
double result = (redemption - pr) / (redemption * yearFrac(basis, settlementDate, maturityDate));

	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcDISC= " + result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * DOLLARDE Converts a dollar price, expressed as a fraction, into a dollar
 * price, expressed as a decimal number Fractional_dollar is a number
 * expressed as a fraction. Fraction is the integer to use in the
 * denominator of the fraction.
 *  
 */
protected static Ptg calcDollarDE(Ptg[] operands) {
	if (operands.length < 2) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcDOLLARDE");
	double fractional_dollar = operands[0].getDoubleVal();
	int fraction = operands[1].getIntVal();
	if (fraction < 0)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (fraction == 0)
		return new PtgErr(PtgErr.ERROR_DIV_ZERO);

	int n = String.valueOf(fraction).length();
	double x = Math.floor(fractional_dollar);
	double y = (fractional_dollar - x) * Math.pow(10, n);
	double result = x + y / fraction;
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcDOLLARDE= " + result);
	return pnum;
}

/**
 * DOLLARFR Converts a dollar price, expressed as a decimal number, into a
 * dollar price, expressed as a fraction Decimal_dollar is a decimal number.
 * Fraction is the integer to use in the denominator of a fraction
 */
protected static Ptg calcDollarFR(Ptg[] operands) {
	if (operands.length < 2) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcDOLLARFR");
	double decimal_dollar = operands[0].getDoubleVal();
	int fraction = operands[1].getIntVal();
	if (fraction < 0)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (fraction == 0)
		return new PtgErr(PtgErr.ERROR_DIV_ZERO);

	int n = String.valueOf(fraction).length();

	double x = Math.floor(decimal_dollar);
	double y = (decimal_dollar - x);
	double result = x + (y * fraction) / Math.pow(10, n);
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcDOLLARFR= " + result);
	return pnum;
}

/**
 * DURATION Returns the annual duration of a security with periodic interest
 * payments 
 * DURATION(settlement,maturity,coupon,yld,frequency,basis)
 * Settlement is the security's settlement date. Maturity is the security's
 * maturity date. Coupon is the security's annual coupon rate. Yld is the
 * security's annual yield. Frequency is the number of coupon payments per
 * year. For annual payments, frequency = 1; for semiannual, frequency = 2;
 * for quarterly, frequency = 4. Basis is the type of day count basis to use
 */
protected static Ptg calcDURATION(Ptg[] operands) {
	if (operands.length < 5) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcDURATION");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	double coupon = operands[2].getDoubleVal();
	double yld = operands[3].getDoubleVal();
	int frequency = operands[4].getIntVal();
	int basis = 0;
	if (operands.length > 5)
		basis = operands[5].getIntVal();

	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))
		return new PtgErr(PtgErr.ERROR_NUM);
	if (coupon < 0 || yld < 0)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (!(frequency == 1 || frequency == 2 || frequency == 4))
		return new PtgErr(PtgErr.ERROR_NUM);

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate)))
			.longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate)))
			.longValue();

	Ptg[] ops = new Ptg[4];
	ops[0] = operands[0];
	ops[1] = operands[1];
	ops[2] = operands[4];
	ops[3] = new PtgInt(basis);

	/*
	 * Duration= (A + SumC)/(D+ SumB * 1/frequency
	 * 
	 * where 
	 * Y= 1+ yield/frequency
	 * R= 100*rate
	 * F= DSC/E
	 * A= (F*100)/Y^(n-1+F)
	 * SumC= Sum(1,n): R/(frequency*Y^(i-1+F)) * (i-1+F)
	 * D= 100/Y^(n-1+F)
	 * SumB= Sum(1,n): R/(frequency*Y^(i-1+F))
	 * 
	 */
	double n =calcCoupNum(ops).getDoubleVal();
	double DSC= calcCoupDaysNC(ops).getDoubleVal();
	double E= calcCoupDays(ops).getDoubleVal();
	double F= DSC/E;
	double R= coupon*100;
	double Y= 1+ yld/frequency;
	double Yx= Math.pow(Y, n-1+F);
	double SumA= 0;
	for (int i=1; i<=n; i++) {
		SumA+= R*(i-1+F)/(Math.pow(Y, i-1+F)*frequency);
	}
	double SumB= 0;
	for (int i=1; i<=n; i++) {
		SumB+= R/(Math.pow(Y, i-1+F)*frequency);
	}
	double C= 0.0, D= 0.0;
	if (n > 1) {
		C= ((n-1+F)*100)/Yx;
		D= 100/Yx;
	}
	double result= (SumA+C)/((SumB+D) * frequency);
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG) Logger.logInfo("Result from calcDURATION= " + result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * EFFECT Returns the effective annual interest rate Nominal_rate is the
 * nominal interest rate. Npery is the number of compounding periods per
 * year
 */
protected static Ptg calcEffect(Ptg[] operands) {
	if (operands.length < 2) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcEFFECT");
	double nominal_rate = operands[0].getDoubleVal();
	int npery = operands[1].getIntVal();
	// TODO: If either argument is non-numeric, #VALUE! error
	if (npery <= 0 || npery < 1) // funny guard!!!!
		return new PtgErr(PtgErr.ERROR_NUM);
	/*
	 * // KSC: TESTING for (int j= 1; j <= 10; j++) { double i; if ((j % 2) ==
	 * 0) i= 1; else i= .5; npery= j; while (i <= 10) { nominal_rate= i /
	 * 100;
	 * 
	 * 
	 * double x= nominal_rate / npery; double result= Math.pow(1 + x, npery) -
	 * 1; Logger.logInfo("nominal_rate= " + nominal_rate + " npery= " +
	 * npery + " result= " + result); java.math.BigDecimal bd= new
	 * java.math.BigDecimal(i-Math.floor(i)).setScale(5,
	 * java.math.BigDecimal.ROUND_HALF_UP); if (((bd.doubleValue()*100) % 4) ==
	 * 0) // if ((((i-Math.floor(i))*100) % 4)==0) i+= 0.3; else i+= 0.25; } }
	 */
	double x = nominal_rate / npery;
	double result = Math.pow(1 + x, npery) - 1;
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG) Logger.logInfo("Result from calcEFFECT= " + result);
	return pnum;
}

/**
 * FV Returns the future value of an investment FV(rate,nper,pmt,pv,type)
 * 
 * 
 * Rate is the interest rate per period. Nper is the total number of payment
 * periods in an annuity. Pmt is the payment made each period; it cannot
 * change over the life of the annuity . Typically, pmt contains principal
 * and interest but no other fees or taxes. If pmt is omitted, you must
 * include the pv argument. Pv is the present value, or the lump-sum amount
 * that a series of future payments is worth right now. If pv is omitted, it
 * is assumed to be 0 (zero), and you must include the pmt argument. Type is
 * the number 0 or 1 and indicates when payments are due. If type is
 * omitted, it is assumed to be 0.
 * 
 *  
 */
protected static Ptg calcFV(Ptg[] operands) {
	if (operands.length < 3) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcFV");
	double rate = operands[0].getDoubleVal();
	int nper = operands[1].getIntVal();
	double pmt = operands[2].getDoubleVal();
	double pv = 0;
	int type = 0;
	if (operands.length > 3 && !(operands[3] instanceof PtgMissArg))
		pv = operands[3].getDoubleVal();
	if (operands.length > 4)
		type = operands[4].getIntVal();

	double A = Math.pow(1 + rate, nper);
	double B = pmt * (1 + rate * type);
	B *= (Math.pow(1 + rate, nper) - 1) / rate;
	double result = -(pv * A + B);
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcFV= " + result);
	return pnum;

}

/**
 * FVSCHEDULE Returns the future value of an initial principal after
 * applying a series of compound interest rates
 */
protected static Ptg calcFVSCHEDULE(Ptg[] operands) {
	if (operands.length < 2) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG) debugOperands(operands, "calcFVSCHEDULE");
	double principal = operands[0].getDoubleVal();
	Ptg[] schedule = PtgCalculator.getAllComponents(operands[1]);
	if (DEBUG) debugOperands(schedule, "calcFVSCHEDULE"); // AFTER converting
											   // references ...
	double result = 1.0;
	for (int i = 0; i < schedule.length; i++) {
		result *= principal + schedule[i].getDoubleVal();
	}
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcFVSCHEDULE= " + result);
	return pnum;
}

/**
 * INTRATE Returns the interest rate for a fully invested security
 */
protected static Ptg calcINTRATE(Ptg[] operands) {
	if (operands.length < 4) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcINTRATE");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	double investment = operands[2].getDoubleVal();
	double redemption = operands[3].getDoubleVal();
	int basis = 0;
	if (operands.length > 4)
		basis = operands[4].getIntVal();

	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4) 			return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))			return new PtgErr(PtgErr.ERROR_NUM);
	if (investment <= 0 || redemption <= 0)			return new PtgErr(PtgErr.ERROR_NUM);

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue();

// double delta = maturityDate - settlementDate;
// double result = ((redemption - investment) / investment) * ((getDaysInYearFromBasis(basis, settlementDate, maturityDate) / delta));
double result = ((redemption - investment) / investment)/yearFrac(basis, settlementDate, maturityDate);
PtgNumber pnum = new PtgNumber(result);
if (DEBUG)
Logger.logInfo("Result from calcINTRATE= " + result);
return pnum;
}catch (Exception e) { }
return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * IPMT Returns the interest payment for an investment for a given period.
 * 
 * Syntax IPMT(rate,per,nper,pv,fv,type)
 * 
 * Rate is the interest rate per period. Per is the period for which you
 * want to find the interest and must be in the range 1 to nper. Nper is the
 * total number of payment periods in an annuity. Pv is the present value,
 * or the lump-sum amount that a series of future payments is worth right
 * now. Fv is the future value, or a cash balance you want to attain after
 * the last payment is made. If fv is omitted, it is assumed to be 0 (the
 * future value of a loan, for example, is 0). Type is the number 0 or 1 and
 * indicates when payments are due. If type is omitted, it is assumed to be
 * 0.
 */
protected static Ptg calcIPMT(Ptg[] operands) {
	if (operands.length < 4) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcIPMT");
	double rate = operands[0].getDoubleVal();
	double per = operands[1].getDoubleVal();
	double nper = operands[2].getDoubleVal();
	double pv = operands[3].getDoubleVal();
	double fv = 0;
	int type = 0;
	if (operands.length > 4 && !(operands[4] instanceof PtgMissArg))
		fv = operands[4].getDoubleVal();
	if (operands.length > 5)
		type = operands[5].getIntVal();

	if (per < 0 || per > nper)
		return new PtgErr(PtgErr.ERROR_NUM);

	// IPMT= pmt- (fv(b) - fv(a) i.e. = payement less the principal balance
	// btwn two periods

	double n;
	if (type == 0)
		n = per;
	else
		n = per - 1;
	// PMT
	double Rn = Math.pow(1 + rate, nper);
	double A = -fv * rate - pv * Rn * rate;
	double B = (Rn - 1) * (1 + rate * type);
	double pmt = A / B;
	// FVa
	A = Math.pow(1 + rate, n);
	B = pmt * (1 + rate * type);
	B *= (Math.pow(1 + rate, n) - 1) / rate;
	double fva = -(pv * A + B);
	// FVb
	A = Math.pow(1 + rate, n - 1);
	B = pmt * (1 + rate * type);
	B *= (Math.pow(1 + rate, n - 1) - 1) / rate;
	double fvb = -(pv * A + B);

	double result = pmt - (fvb - fva);
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcIPMT= " + result);
	return pnum;
}

/**
 * IRR Returns the internal rate of return for a series of cash flows
 * Returns the internal rate of return for a series of cash flows
 * represented by the numbers in values. These cash flows do not have to be
 * even, as they would be for an annuity. However, the cash flows must occur
 * at regular intervals, such as monthly or annually. The internal rate of
 * return is the interest rate received for an investment consisting of
 * payments (negative values) and income (positive values) that occur at
 * regular periods.
 * 
 * SEE: NPV
 * 
 * Syntax IRR(values,guess)
 * 
 * Values is an array or a reference to cells that contain numbers for which
 * you want to calculate the internal rate of return.
 * 
 * Values must contain at least one positive value and one negative value to
 * calculate the internal rate of return.
 * 
 * IRR uses the order of values to interpret the order of cash flows. Be
 * sure to enter your payment and income values in the sequence you want.
 * 
 * If an array or reference argument contains text, logical values, or empty
 * cells, those values are ignored. Guess is a number that you guess is
 * close to the result of IRR.
 * 
 * Microsoft Excel uses an iterative technique for calculating IRR. Starting
 * with guess, IRR cycles through the calculation until the result is
 * accurate within 0.00001 percent. If IRR can't find a result that works
 * after 20 tries, the #NUM! error value is returned.
 * 
 * In most cases you do not need to provide guess for the IRR calculation.
 * If guess is omitted, it is assumed to be 0.1 (10 percent).
 * 
 * If IRR gives the #NUM! error value, or if the result is not close to what
 * you expected, try again with a different value for guess
 */
protected static Ptg calcIRR(Ptg[] operands) {
	if (operands.length < 1 || operands[0].getComponents() == null) { // not
		// supported
		// by
		// function
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	if (DEBUG)
		debugOperands(operands, "calcIRR");
	double guess = .1;
	if (operands.length > 1)
		guess = operands[1].getDoubleVal();
	Ptg[] params = PtgCalculator.getAllComponents(operands);
	if (DEBUG)
		debugOperands(params, "calcIRR"); // AFTER converting references ...
	int n = params.length;

	// examine values array, sum all outflows (= negative values) + inflows
	// (= positive values)
	double outflow = 0.0, inflow = 0.0;
	// get outflow (- values)
	for (int i = 0; i < n; i++) {
		double val = params[i].getDoubleVal();
		if (val < 0)
			outflow += Math.abs(val);
		else
			inflow += val;
	}
	if (outflow == 0.0 || inflow == 0.0)
		return new PtgErr(PtgErr.ERROR_VALUE); // TODO: Excel doesn't specify which
	// error to return

	// iterate over possible irr values; value is correct when
	// outflow-pv <= tolerance, defined as .00001%
	final double TOLERANCE = 0.0000001;
	boolean bIsCorrect = false;
	double xl, xh, fl, fh, f, trial = guess;
	xl = 0;
	xh = guess;
	double delta = xh - xl;
	fl = outflow - inflow;
	fh = outflow;
	for (int i = 0; i < n; i++) {
		double val = params[i].getDoubleVal();
		if (val > 0) {
			fh -= val / (Math.pow(1 + xh, i));
		}
	}
	for (int j = 0; j < 50 && !bIsCorrect; j++) { // maximum 20 tries - need
		// more!!!!
		trial = xl + delta * fl / (fl - fh);
		f = outflow;
		for (int i = 0; i < n; i++) {
			double val =  params[i].getDoubleVal();
			if (val > 0) {
				f -= val / (Math.pow(1 + trial, i));
			}
		}

		if (f < 0) {
			delta = xl - trial;
			xl = trial;
			fl = f;
		} else {
			delta = xh - trial;
			xh = trial;
			fh = f;
		}
		bIsCorrect = (Math.abs(delta) <= TOLERANCE);
		delta = xh - xl;
	}
	if (!bIsCorrect)
		return new PtgErr(PtgErr.ERROR_NUM);

	if (DEBUG)
		Logger.logInfo("Result from calcIRR= " + trial);
	PtgNumber pnum = new PtgNumber(trial);
	return pnum;
}

/**
 * ISPMT Calculates the interest paid during a specific period of an
 * investment. This function is provided for compatibility with Lotus 1-2-3.
 * ISPMT(rate,per,nper,pv) Rate is the interest rate for the investment. Per
 * is the period for which you want to find the interest, and must be
 * between 1 and nper. Nper is the total number of payment periods for the
 * investment. Pv is the present value of the investment. For a loan, pv is
 * the loan amount.
 */
protected static Ptg calcISPMT(Ptg[] operands) {
	if (operands.length < 4) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcISPMT");
	double rate = operands[0].getDoubleVal();
	double per = operands[1].getDoubleVal();
	double nper = operands[2].getDoubleVal();
	double pv = operands[3].getDoubleVal();
	if (per < 0 || per > nper)
		return new PtgErr(PtgErr.ERROR_NUM);

	double result = (-pv * rate * (nper - per)) / nper;
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcISPMT= " + result);
	return pnum;
}

/**
 * MDURATION Returns the Macauley modified duration for a security with an
 * assumed par value of $100
 * DURATION(settlement,maturity,coupon,yld,frequency,basis) Settlement is
 * the security's settlement date. Maturity is the security's maturity date.
 * Coupon is the security's annual coupon rate. Yld is the security's annual
 * yield. Frequency is the number of coupon payments per year. For annual
 * payments, frequency = 1; for semiannual, frequency = 2; for quarterly,
 * frequency = 4. Basis is the type of day count basis to use
 */
protected static Ptg calcMDURATION(Ptg[] operands) {
	if (operands.length < 5) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcMDURATION");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	double coupon = operands[2].getDoubleVal();
	double yld = operands[3].getDoubleVal();
	int frequency = operands[4].getIntVal();
	int basis = 0;
	if (operands.length > 5)
		basis = operands[5].getIntVal();

	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))
		return new PtgErr(PtgErr.ERROR_NUM);
	if (coupon < 0 || yld < 0)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (!(frequency == 1 || frequency == 2 || frequency == 4))
		return new PtgErr(PtgErr.ERROR_NUM);

	double result = calcDURATION(operands).getDoubleVal();
	// above is regular duration calculation; to get modified duration:
	result = result / (1 + yld / frequency);
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcMDURATION= " + result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * MIRR Returns the internal rate of return where positive and negative cash
 * flows are financed at different rates
 * 
 * MIRR(values,finance_rate,reinvest_rate)
 * 
 * Values is an array or a reference to cells that contain numbers. These
 * numbers represent a series of payments (negative values) and income
 * (positive values) occurring at regular periods.
 * 
 * Values must contain at least one positive value and one negative value to
 * calculate the modified internal rate of return. Otherwise, MIRR returns
 * the #DIV/0! error value.
 * 
 * If an array or reference argument contains text, logical values, or empty
 * cells, those values are ignored; however, cells with the value zero are
 * included.
 * 
 * Finance_rate is the interest rate you pay on the money used in the cash
 * flows.
 * 
 * Reinvest_rate is the interest rate you receive on the cash flows as you
 * reinvest them.
 */
protected static Ptg calcMIRR(Ptg[] operands) {
	if (operands.length < 3) { // not supported by function
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcMIRR");
	double finance_rate = operands[1].getDoubleVal();
	double reinvest_rate = operands[2].getDoubleVal();
	Ptg[] params = PtgCalculator.getAllComponents(operands);
	if (DEBUG)
		debugOperands(params, "calcMIRR"); // AFTER converting references
	// ...

	// Get + Values and - Values in separate Ptg arrays
	Vector posVals = new Vector();
	Vector negVals = new Vector();
	Ptg[] positiveValues;
	Ptg[] negativeValues;
	int n = params.length - 2; // skip last 2 params (= rates)
	for (int i = 0; i < n; i++) {
		double val =  params[i].getDoubleVal();
		if (val < 0)
			negVals.addElement(params[i]);
		else
			posVals.addElement(params[i]);
	}
	positiveValues = new Ptg[posVals.size() + 1];
	negativeValues = new Ptg[negVals.size() + 1];
	System.arraycopy(posVals.toArray(), 0, positiveValues, 1, posVals
			.size());
	System.arraycopy(negVals.toArray(), 0, negativeValues, 1, negVals
			.size());

	// add rate to Ptg array for call to calcNPV
	positiveValues[0] = operands[2]; // reinvest rate
	negativeValues[0] = operands[1]; // finance rate

	// Calculate MIRR from NPV values
	double X = calcNPV(positiveValues).getDoubleVal();
	X = -1 * X * Math.pow(1 + reinvest_rate, posVals.size());
	double Y = calcNPV(negativeValues).getDoubleVal();
	Y = Y * (1 + finance_rate);
	double result = Math.pow(X / Y, 1.0 / (n - 1)) - 1;

	if (DEBUG)
		Logger.logInfo("Result from calcMIRR= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * NOMINAL Returns the annual nominal interest rate
 */
protected static Ptg calcNominal(Ptg[] operands) {
	if (operands.length < 2) {
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	if (DEBUG)
		debugOperands(operands, "calcNominal");
	double effect = operands[0].getDoubleVal();
	int npery = operands[1].getIntVal();
	// TODO: if either is non-numeric, return #VALUE!
	if (effect <= 0 || npery < 1)
		return new PtgErr(PtgErr.ERROR_NUM);

	// solve for nominal_rate in:
	// effect= (1 + nominal_rate/npery)^npery -1
	// nominal_rate= ((10^(log10(y)/n)) - 1)*npery
	double y = effect + 1;
	double log10y = Math.log(y) / Math.log(10); // base 10 log
	double z = Math.pow(10, log10y / npery);
	double result = (z - 1) * npery;
	if (DEBUG)
		Logger.logInfo("Result from calcNominal= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * NPER Returns the number of periods for an investment NPER(rate, pmt, pv,
 * fv, type)
 * 
 * Rate is the interest rate per period. Pmt is the payment made each
 * period; it cannot change over the life of the annuity. Typically, pmt
 * contains principal and interest but no other fees or taxes. Pv is the
 * present value, or the lump-sum amount that a series of future payments is
 * worth right now. Fv is the future value, or a cash balance you want to
 * attain after the last payment is made. If fv is omitted, it is assumed to
 * be 0 (the future value of a loan, for example, is 0). Type is the number
 * 0 or 1 and indicates when payments are due.
 */
protected static Ptg calcNPER(Ptg[] operands) {
	if (operands.length < 3) {
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	if (DEBUG)
		debugOperands(operands, "calcNPER");
	double rate = operands[0].getDoubleVal();
	double pmt = operands[1].getDoubleVal();
	double pv = operands[2].getDoubleVal();
	double fv = 0;
	if (operands.length > 3 && !(operands[3] instanceof PtgMissArg))
		fv = operands[3].getDoubleVal();
	int type = 0;
	if (operands.length > 4)
		type = operands[4].getIntVal();

	double A = pmt * (1 + type * rate) - rate * fv;
	double B = pmt * (1 + type * rate) + rate * pv;
	double C = 1 + rate;
	double result = Math.log(A / B) / Math.log(C);
	if (DEBUG)
		Logger.logInfo("Result from calcNPER= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * NPV(rate,value1,value2, ...) Calculates the net present value of an
 * investment by using a discount rate and a series of future payments
 * (negative values) and income (positive values).
 * 
 * Rate is the rate of discount over the length of one period. Value1,
 * value2, ... are 1 to 29 arguments representing the payments and income.
 * Value1, value2, ... must be equally spaced in time and occur at the end
 * of each period
 * 
 * Returns the net present value of an investment based on a series of
 * periodic cash flows and a discount rate = Sum(values / (1 + rate) )
 * 
 */
protected static Ptg calcNPV(Ptg[] operands) {
	if (operands.length < 2 || operands[0].getComponents() != null) { // not
		// supported
		// by
		// function
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	if (DEBUG) debugOperands(operands, "calcNPV");
	Ptg[] params = PtgCalculator.getAllComponents(operands);
	if (DEBUG) debugOperands(params, "calcNPV"); // AFTER converting references ...
	double rate = params[0].getDoubleVal();
	int n = Math.min(params.length, 30); // at most 29 values
	double result = 0;
	for (int i = 1; i < n; i++) {
		double valuei = params[i].getDoubleVal();
		// TODO: if valuei is an error, empty, etc., ignore
		result += valuei / Math.pow(1 + rate, i);
	}
	if (DEBUG) Logger.logInfo("Result from calcNPV= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * ODDFPRICE Returns the price per $100 face value of a security with an odd
 * first period
 * ODDFPRICE(settlement,maturity,issue,first_coupon,rate,yld,redemption,frequency,basis)
 * 
 */
protected static Ptg calcODDFPRICE(Ptg[] operands) throws
	FunctionNotSupportedException {
	if (true) {
        String wn = "WARNING: this version of OpenXLS does not support the formula ODDFPRICE.";
        Logger.logWarn(wn);
        throw new FunctionNotSupportedException(wn);
	}		
	if (operands.length < 8) { // not supported by function
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcODDFPRICE");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue());
	GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue());
	GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue());
	double rate = operands[4].getDoubleVal();
	double yld = operands[5].getDoubleVal();
	double redemption = operands[6].getDoubleVal();
	int frequency = operands[7].getIntVal();
	int basis = 0;
	if (operands.length > 8)
		basis = operands[8].getIntVal();
	
	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue();
	long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue();
	long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue();
	
	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))  	return new PtgErr(PtgErr.ERROR_NUM);
	if (yld < 0 || rate < 0)  	return new PtgErr(PtgErr.ERROR_NUM);
	if (redemption <= 0) 		return new PtgErr(PtgErr.ERROR_NUM);
	/*maturity > first_coupon > settlement > issue */
	if (issueDate >= settlementDate || settlementDate >= firstCouponDate ||
			firstCouponDate >= maturityDate) return new PtgErr(PtgErr.ERROR_NUM);
	

	Ptg[] ops = new Ptg[4];
	ops[0] = new PtgNumber(settlementDate);
	ops[1] = new PtgNumber(maturityDate);
	ops[2] = new PtgInt(frequency);
	ops[3] = new PtgInt(basis);
	double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement
	double DSC = calcCoupDaysNC(ops).getDoubleVal();  // days from  settlement to next coupon		
	double E = calcCoupDays(ops).getDoubleVal();  // total # days in coupon period 
	double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity
	double NCD= calcCoupNCD(ops).getDoubleVal();  // next coupon after 1st coupon date?????
	double DFC= firstCouponDate-settlementDate;  	// # days from odd first coupon to next coupon date

double z= getDaysFromBasis(basis, settlementDate, firstCouponDate);

	double R = 100*rate/frequency;
	double Y = 1 + yld/frequency;

	double result= 0.0;
	if (DFC < E) {	// odd short first coupon			
		double firstTerm= redemption/Math.pow(Y, N-1+DSC/E);
		double secondTerm= (R*DFC/E)/Math.pow(Y, DSC/E);
		double thirdTerm= 0.0;
		for (int i= 2; i <=N; i++) {
			thirdTerm+= R/Math.pow(Y, i-1+DSC/E);
		}
		double fourthTerm= R*A/E;
		result= firstTerm + secondTerm + thirdTerm - fourthTerm;
	}
	else {	// odd long first coupon
		
	}
	
	if (DEBUG) Logger.logInfo("Result from calcODDFPRICE= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

 /* 
 * ODDFYIELD Returns the yield of a security with an odd first period
 * NOT COMPLETED!!
 */  
protected static Ptg calcODDFYIELD(Ptg[] operands) throws
FunctionNotSupportedException {
	if (true) {
        String wn = "WARNING: this version of OpenXLS does not support the formula ODDFYIELD.";
        Logger.logWarn(wn);
        throw new FunctionNotSupportedException(wn);
	}
	if (operands.length < 8) { // not supported by function
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcODDFYIELD");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue());
	GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue());
	GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue());
	double rate = operands[4].getDoubleVal();
	double yld = operands[5].getDoubleVal();
	double redemption = operands[6].getDoubleVal();
	int frequency = operands[7].getIntVal();
	int basis = 0;
	if (operands.length > 8)
		basis = operands[8].getIntVal();

	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))  	return new PtgErr(PtgErr.ERROR_NUM);
	if (yld < 0 || rate < 0)  	return new PtgErr(PtgErr.ERROR_NUM);
	if (redemption <= 0) 		return new PtgErr(PtgErr.ERROR_NUM);

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue();
	long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue();
	long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue();

	/*
	 * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC
	 */
	Ptg[] ops = new Ptg[4];
	ops[0] = new PtgNumber(settlementDate);
	ops[1] = new PtgNumber(maturityDate);
	ops[2] = new PtgInt(frequency);
	ops[3] = new PtgInt(basis);
	double A = calcCoupDayBS(ops).getDoubleVal();  // days from beg. coupon period to settlement
	double DFC= getDaysFromBasis(basis, firstCouponDate, (long)A);	// days from 1st odd coupon to 1st coupon
	double DSC = calcCoupDaysNC(ops).getDoubleVal();  // days from  settlement to next coupon		
	double E = calcCoupDays(ops).getDoubleVal();  // total # days in coupon period 
	double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity
	double R = 100*rate/frequency;
	double Y = 1 + yld/frequency;

	double result= 0.0;
	
	if (DEBUG) Logger.logInfo("Result from calcODDFYIELD= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}
 /* 
  * ODDLPRICE Returns the price per $100 face value of a security with an odd
 * last period
 * NOT COMPLETED!!!!
 */
protected static Ptg calcODDLPRICE(Ptg[] operands) throws
FunctionNotSupportedException {
	if (true) {
        String wn = "WARNING: this version of OpenXLS does not support the formula ODDLPRICE.";
        Logger.logWarn(wn);
        throw new FunctionNotSupportedException(wn);
	}
	if (operands.length < 8) { // not supported by function
		return new PtgErr(PtgErr.ERROR_NULL);
	} 
	if (DEBUG) debugOperands(operands, "calcODDLPRICE");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue());
	GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue());
	GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue());
	double rate = operands[4].getDoubleVal();
	double yld = operands[5].getDoubleVal();
	double redemption = operands[6].getDoubleVal();
	int frequency = operands[7].getIntVal();
	int basis = 0;
	if (operands.length > 8)
		basis = operands[8].getIntVal();

	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))  	return new PtgErr(PtgErr.ERROR_NUM);
	if (yld < 0 || rate < 0)  	return new PtgErr(PtgErr.ERROR_NUM);
	if (redemption <= 0) 		return new PtgErr(PtgErr.ERROR_NUM);

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue();
	long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue();
	long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue();

	/*
	 * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC
	 */
	Ptg[] ops = new Ptg[4];
	ops[0] = new PtgNumber(settlementDate);
	ops[1] = new PtgNumber(maturityDate);
	ops[2] = new PtgInt(frequency);
	ops[3] = new PtgInt(basis);
	double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement
	double DFC= getDaysFromBasis(basis, firstCouponDate, (long)A);	// days from 1st odd coupon to 1st coupon
	double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from  settlement to next coupon		
	double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period 
	double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity
	double R = 100*rate/frequency;
	double Y = 1 + yld/frequency;

	double result= 0.0;
	
	if (DEBUG) Logger.logInfo("Result from calcODDLPRICE= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}
 /*   
 * ODDLYIELD Returns the yield of a security with an odd last period
 * NOT COMPLETED!!
 */ 
protected static Ptg calcODDLYIELD(Ptg[] operands) throws
FunctionNotSupportedException {
	if (true) {
        String wn = "WARNING: this version of OpenXLS does not support the formula ODDLYIELD.";
        Logger.logWarn(wn);
        throw new FunctionNotSupportedException(wn);
	}
	if (operands.length < 8) { // not supported by function
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG) debugOperands(operands, "calcODDLYIELD");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue());
	GregorianCalendar iDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[2].getValue());
	GregorianCalendar fcDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[3].getValue());
	double rate = operands[4].getDoubleVal();
	double yld = operands[5].getDoubleVal();
	double redemption = operands[6].getDoubleVal();
	int frequency = operands[7].getIntVal();
	int basis = 0;
	if (operands.length > 8)
		basis = operands[8].getIntVal();

	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4) return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))  	return new PtgErr(PtgErr.ERROR_NUM);
	if (yld < 0 || rate < 0)  	return new PtgErr(PtgErr.ERROR_NUM);
	if (redemption <= 0) 		return new PtgErr(PtgErr.ERROR_NUM);

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue();
	long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue();
	long firstCouponDate = (new Double(DateConverter.getXLSDateVal(fcDate))).longValue();

	/*
	 * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC
	 */
	Ptg[] ops = new Ptg[4];
	ops[0] = new PtgNumber(settlementDate);
	ops[1] = new PtgNumber(maturityDate);
	ops[2] = new PtgInt(frequency);
	ops[3] = new PtgInt(basis);
	double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement
	double DFC= getDaysFromBasis(basis, firstCouponDate, (long)A);	// days from 1st odd coupon to 1st coupon
	double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from  settlement to next coupon		
	double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period 
	double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity
	double R = 100*rate/frequency;
	double Y = 1 + yld/frequency;

	double result= 0.0;
	
	if (DEBUG) Logger.logInfo("Result from calcODDLYIELD= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * PMT Returns the periodic payment for an annuity
 * 
 * pmt= - (fv + (1+rate)**nper * pv) * rate / ( ( (1+rate)**nper - 1 ) *
 * (1+rate*type) )
 * 
 * if fv or type are omitted they should be treated as 0 values.
 */
protected static Ptg calcPmt(Ptg[] operands) {
	if (operands.length < 3) { // not
		// supported
		// by
		// function
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	double rate, nper, pv, fv, type;
	rate = new Double(String.valueOf(operands[0].getValue())).doubleValue();
	nper = new Double(String.valueOf(operands[1].getValue())).doubleValue();
	pv = new Double(String.valueOf(operands[2].getValue())).doubleValue();
	if (operands.length > 3) {
		if (operands[3] instanceof PtgMissArg) {
			fv = 0;
		} else {
			fv = new Double(String.valueOf(operands[3].getValue()))
					.doubleValue();
		}
	} else {
		fv = 0;
	}
	if (operands.length > 4) {
		if (operands[4] instanceof PtgMissArg) {
			type = 0;
		} else {
			type = new Double(String.valueOf(operands[4].getValue()))
					.doubleValue();
		}
	} else {
		type = 0;
	}

	//KSC: For some strange odd weird reason, original calculation was off
	// even though this should be exactly the same thing. Go figure!
	double Rn = Math.pow(1 + rate, nper);
	double A = -fv * rate - pv * Rn * rate;
	double B = (Rn - 1) * (1 + rate * type);
	double result = A / B;
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * PPMT Returns the payment on the principal for an investment for a given
 * period PPMT(rate,per,nper,pv,fv,type) Rate is the interest rate per
 * period. Per specifies the period and must be in the range 1 to nper. Nper
 * is the total number of payment periods in an annuity. Pv is the present
 * value  the total amount that a series of future payments is worth now.
 * Fv is the future value, or a cash balance you want to attain after the
 * last payment is made. If fv is omitted, it is assumed to be 0 (zero),
 * that is, the future value of a loan is 0. Type is the number 0 or 1 and
 * indicates when payments are due
 *  
 */
protected static Ptg calcPPMT(Ptg[] operands) {
	if (operands.length < 4) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcPPMT");
	double rate = operands[0].getDoubleVal();
	int per = operands[1].getIntVal();
	int nper = operands[2].getIntVal();
	double pv = operands[3].getDoubleVal();
	double fv = 0;
	int type = 0;
	if (operands.length > 4 && !(operands[4] instanceof PtgMissArg))
		fv = operands[4].getDoubleVal();
	if (operands.length > 5)
		type = operands[5].getIntVal();

	double result;
	// 1st, get payment for entire period
	double Rn = Math.pow(1 + rate, nper);
	double A = -fv * rate - pv * Rn * rate;
	double B = (Rn - 1) * (1 + rate * type);
	double pmt = A / B;

	double n;
	if (type == 0)
		n = per;
	else
		n = per - 1;
	// FVa
	A = Math.pow(1 + rate, n);
	B = pmt * (1 + rate * type);
	B *= (Math.pow(1 + rate, n) - 1) / rate;
	double fva = -(pv * A + B);
	// FVb
	A = Math.pow(1 + rate, n - 1);
	B = pmt * (1 + rate * type);
	B *= (Math.pow(1 + rate, n - 1) - 1) / rate;
	double fvb = -(pv * A + B);

	result = fvb - fva;
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcPPMT= " + result);
	return pnum;

}

/**
 * PRICE Returns the price per $100 face value of a security that pays
 * periodic interest
 * PRICE(settlement,maturity,rate,yld,redemption,frequency,basis) Settlement
 * is the security's settlement date. The security settlement date is the
 * date after the issue date when the security is traded to the buyer.
 * Maturity is the security's maturity date. The maturity date is the date
 * when the security expires. Rate is the security's annual coupon rate. Yld
 * is the security's annual yield. Redemption is the security's redemption
 * value per $100 face value. Frequency is the number of coupon payments per
 * year. For annual payments, frequency = 1; for semiannual, frequency = 2;
 * for quarterly, frequency = 4. Basis is the type of day count basis to
 * use.
 *  
 */
protected static Ptg calcPRICE(Ptg[] operands) {
	if (operands.length < 6) { // not supported by function
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcPRICE");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue());
	double rate = operands[2].getDoubleVal();
	double yld = operands[3].getDoubleVal();
	double redemption = operands[4].getDoubleVal();
	int frequency = operands[5].getIntVal();
	int basis = 0;
	if (operands.length > 6)
		basis = operands[6].getIntVal();

	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))
		return new PtgErr(PtgErr.ERROR_NUM);
	if (yld < 0 || rate < 0)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (redemption <= 0)
		return new PtgErr(PtgErr.ERROR_NUM);

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate)))
			.longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate)))
			.longValue();

	/*
	 * A= coupDayBS E= coupDays N= coupNum DSC= coupDaysNC
	 */
	Ptg[] ops = new Ptg[4];
	ops[0] = operands[0];
	ops[1] = operands[1];
	ops[2] = operands[5];
	ops[3] = new PtgInt(basis);
	double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from  settlement to next coupon
	double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period in which settlementfalls
	double N = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity
	double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement
	double R = rate / frequency;
	double Y = 1 + yld / frequency;
	double result = redemption / Math.pow(Y, N - 1 + DSC / E);
	for (int i = 1; i <= N; i++) {
		result += (R * 100) / Math.pow(Y, i - 1 + DSC / E);
	}
	result -= (100 * R * A / E);

	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcPRICE= " + result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * PRICEDISC Returns the price per $100 face value of a discounted security
 * 
 * PRICEDISC(settlement,maturity,discount,redemption,frequency,basis)
 * Settlement is the security's settlement date. Maturity is the security's
 * maturity date. Discount is the security's discount rate. Redemption is
 * the security's redemption value per $100 face value. Basis is the type of
 * day count basis to use.
 *  
 */
protected static Ptg calcPRICEDISC(Ptg[] operands) {
	if (operands.length < 4) { // not supported by function
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcPRICEDISC");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	double discount = operands[2].getDoubleVal();
	double redemption = operands[3].getDoubleVal();
	int basis = 0;
	if (operands.length > 4)
		basis = operands[4].getIntVal();

	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))
		return new PtgErr(PtgErr.ERROR_NUM);
	if (redemption <= 0)
		return new PtgErr(PtgErr.ERROR_NUM);

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate)))
			.longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate)))
			.longValue();

	/*
	 * Ptg[] ops; if (operands.length > 6) ops = new Ptg[4]; else ops = new
	 * Ptg[3]; ops[0] = operands[0]; ops[1] = operands[1]; ops[2] =
	 * operands[5]; if (operands.length > 6) ops[3] = operands[6];
	 */double result = redemption
			- (discount * redemption * yearFrac(basis, settlementDate,
					maturityDate));
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcPRICEDISC= " + result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * PRICEMAT Returns the price per $100 face value of a security that pays
 * interest at maturity
 */
protected static Ptg calcPRICEMAT(Ptg[] operands) {
	if (operands.length < 4) { // not supported by function
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcPRICEMAT");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	GregorianCalendar iDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[2].getValue());
	double rate = operands[3].getDoubleVal();
	double yld = operands[4].getDoubleVal();
	int basis = 0;
	if (operands.length > 5)
		basis = operands[5].getIntVal();

	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)	 return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))  return new PtgErr(PtgErr.ERROR_NUM);
	if (rate < 0 || yld < 0)  return new PtgErr(PtgErr.ERROR_NUM);

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue();
	long issueDate = (new Double(DateConverter.getXLSDateVal(iDate))).longValue();
	
	double B= getDaysInYearFromBasis(basis,settlementDate, maturityDate);
	double DSM= getDaysFromBasis(basis, settlementDate, maturityDate);
	double DIM= getDaysFromBasis(basis, issueDate, maturityDate);
	double A= getDaysFromBasis(basis, issueDate, settlementDate);

	double result = (100 + (DIM/B) * rate * 100) / (1 + ((DSM/B) * yld));
	result -= (A/B) * rate * 100;
	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcPRICEMAT= " + result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * PV(rate,nper,pmt,fv,type) Returns the present value of an investment Rate
 * is the interest rate per period. For example, if you obtain an automobile
 * loan at a 10 percent annual interest rate and make monthly payments, your
 * interest rate per month is 10%/12, or 0.83%. You would enter 10%/12, or
 * 0.83%, or 0.0083, into the formula as the rate. Nper is the total number
 * of payment periods in an annuity. For example, if you get a four-year car
 * loan and make monthly payments, your loan has 4*12 (or 48) periods. You
 * would enter 48 into the formula for nper. Pmt is the payment made each
 * period and cannot change over the life of the annuity. Typically, pmt
 * includes principal and interest but no other fees or taxes. For example,
 * the monthly payments on a $10,000, four-year car loan at 12 percent are
 * $263.33. You would enter -263.33 into the formula as the pmt. If pmt is
 * omitted, you must include the fv argument. Fv is the future value, or a
 * cash balance you want to attain after the last payment is made. If fv is
 * omitted, it is assumed to be 0 (the future value of a loan, for example,
 * is 0). For example, if you want to save $50,000 to pay for a special
 * project in 18 years, then $50,000 is the future value. You could then
 * make a conservative guess at an interest rate and determine how much you
 * must save each month. If fv is omitted, you must include the pmt
 * argument. Type is the number 0 or 1 and indicates when payments are due.
 * 0 or omitted At the end of the period 1 At the beginning of the period
 * 
 *  
 */
protected static Ptg calcPV(Ptg[] operands) {
	if (operands.length < 3) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcPV");
	double rate = operands[0].getDoubleVal();
	double nper = operands[1].getDoubleVal();
	double pmt = 0;
	// TODO: No specified error trapping?
	if (!(operands[2] instanceof PtgMissArg))
		pmt = operands[2].getDoubleVal();
	double fv = 0.0;
	int type = 0;
	if (operands.length > 3 && !(operands[3] instanceof PtgMissArg))
		fv = operands[3].getDoubleVal();
	if (operands.length > 4)
		type = operands[4].getIntVal();

	double A = Math.pow(1 + rate, nper);
	double B = pmt * (1 + rate * type);
	B *= (A - 1) / rate;
	double result = (-fv - B) / A;
	
	//double testresults= (result * A + pmt*(1+rate*type) * (A-1)/rate + fv);
	//testresults must==0  for Pv to be correct
	

	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG)
		Logger.logInfo("Result from calcPV= " + result);
	return pnum;

}

/**
 * RATE Returns the interest rate per period of an annuity. RATE is
 * calculated by iteration and can have zero or more solutions. If the
 * successive results of RATE do not converge to within 0.0000001 after 20
 * iterations, RATE returns the #NUM! error value.
 * 
 * RATE(nper,pmt,pv,fv,type,guess)
 * 
 * Nper is the total number of payment periods in an annuity. Pmt is the
 * payment made each period and cannot change over the life of the annuity.
 * Typically, pmt includes principal and interest but no other fees or
 * taxes. If pmt is omitted, you must include the fv argument. Pv is the
 * present value  the total amount that a series of future payments is
 * worth now. Fv is the future value, or a cash balance you want to attain
 * after the last payment is made. If fv is omitted, it is assumed to be 0
 * (the future value of a loan, for example, is 0). Type is the number 0 or
 * 1 and indicates when payments are due. Guess is your guess for what the
 * rate will be.
 */
protected static Ptg calcRate(Ptg[] operands) {
	if (operands.length < 3) { // not supported by function
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcRate");
	double nper = operands[0].getDoubleVal();
	double pmt = operands[1].getDoubleVal();
	double pv = 0.0, fv = 0.0;
	int type = 0;
	double guess = 0.1;
	if (!(operands[2] instanceof PtgMissArg))
		pv = operands[2].getDoubleVal();
	if (operands.length > 3) {
		if (!(operands[3] instanceof PtgMissArg))
			fv = operands[3].getDoubleVal();
	}
	if (operands.length > 4) {
		if (!(operands[4] instanceof PtgMissArg))
			type = operands[4].getIntVal();
	}
	if (operands.length > 5)
		guess = operands[5].getDoubleVal();
	// validate params
	if (type != 0 && type != 1)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (pv == 0 && fv == 0)
		return new PtgErr(PtgErr.ERROR_NUM);

	// iterate over possible Rate values; value is correct when
	// f <= tolerance, defined as .00001%
	final double TOLERANCE = 0.000000001;
	boolean bIsCorrect = false;
	double x0= guess;
	double x1= x0;
	double fx0;
	double fprimex0;
	// iterate, using Newton's approximation
	for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 20 tries
		// Calculate f(x) = (a+ f*g*h + c)			
		double R= Math.pow(1 + x0, nper);
		double U= 1/x0;
		double a= pv*R;
		double f= pmt*(1+x0*type);
		double g= R-1;
		double h= U; 
		fx0= a + f*g*U +fv;
		// 
		// (a + f*g*h + c)' 
		//   = a' + f'gh + fg'h + fgh' 
		double T= Math.pow(1 + x0, nper-1);
		double aprime= pv * nper * T;
		double fprime= pmt*type;
		double gprime= nper * T;
		double hprime= -1*Math.pow(x0, -2);
		
		fprimex0= aprime + fprime*g*h + f*gprime*h + f*g*hprime;
		// calculate x1, the next iteration
		x1= x0 - fx0/fprimex0;
		double delta= x1-x0;
		bIsCorrect = (Math.abs(delta) <= TOLERANCE);
		x0= x1;
	}

/* THIS CALCULATION IS OFF!!!
// x= rate
// f(x)= PV + PMT*((1-(1+x)-NPER)/x)*(1+x)TYPE + FV*(1+x)^-NPER = 0
// f’(x)=
double x0, f0, trial, f;
x0 = 0;
f0 = pv * pmt + type + fv;
trial = guess;
// iterate, using Newton’s approximation
double delta;
double fprime = 0; // derivative of f(x0)
for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 20 tries
//trial= xl+delta*fl/(fl-fh);
// Calculate f(x)
double R = Math.pow(1 + trial, -nper);
double T = Math.pow(1 + trial, type);
f = pv + pmt * ((1 - R) / trial) * T + fv * R;
// Calculate f’(x)
double gofx = fv * R;
double gprimex = fv * -nper * Math.pow(1 + trial, -nper - 1);
double hofx = T;
double hprimex = type * Math.pow(1 + trial, type - 1);
double zofx = (1 - R) / trial;
double zprimex = (1 + nper * Math.pow(1 + trial, -nper - 1))
/ Math.pow(trial, 2);
fprime = zprimex * hofx + zofx * hprimex + gprimex;
delta = f / fprime;
// testing!
if (trial - delta <= 0)
delta = trial / 2;
trial -= delta;
bIsCorrect = (Math.abs(delta) <= TOLERANCE);
}
/**/
if (!bIsCorrect)
return new PtgErr(PtgErr.ERROR_NUM);

	if (DEBUG)
		Logger.logInfo("Result from calcRate= " + x1);
	PtgNumber pnum = new PtgNumber(x1);
	return pnum;
}

/**
 * RECEIVED Returns the amount received at maturity for a fully invested
 * security
 */
protected static Ptg calcReceived(Ptg[] operands) {
	if (operands.length < 4) {
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	if (DEBUG)
		debugOperands(operands, "calcRECEIVED");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	double investment = operands[2].getDoubleVal();
	double rate = operands[3].getDoubleVal();
	int basis = 0;
	if (operands.length > 4)
		basis = operands[4].getIntVal();

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate)))
			.longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate)))
			.longValue();

// double DSM = maturityDate - settlementDate;
// double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate);
// double result = investment / (1 - (rate * DSM / B));
double result = investment/(1 - (rate *yearFrac(basis, settlementDate, maturityDate)));
if (DEBUG)
Logger.logInfo("Result from calcRECEIVED= " + result);
PtgNumber pnum = new PtgNumber(result);
return pnum;
}catch (Exception e) { }
return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * SLN Returns the straight-line depreciation of an asset for one period
 * SLN(cost,salvage,life) Cost is the initial cost of the asset. Salvage is
 * the value at the end of the depreciation (sometimes called the salvage
 * value of the asset). Life is the number of periods over which the asset
 * is depreciated (sometimes called the useful life of the asset).
 */
protected static Ptg calcSLN(Ptg[] operands) {
	if (operands.length < 3) {
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	double cost = operands[0].getDoubleVal();
	double salvage = operands[1].getDoubleVal();
	double life = operands[2].getDoubleVal();
	if (DEBUG)
		debugOperands(operands, "calcSLN");
	if (life == 0)
		return new PtgErr(PtgErr.ERROR_NUM);
	double result = (cost - salvage) / life;
	if (DEBUG)
		Logger.logInfo("Result from calcSLN= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * SYD Returns the sum-of-years' digits depreciation of an asset for a
 * specified period SYD(cost,salvage,life,per) Cost is the initial cost of
 * the asset. Salvage is the value at the end of the depreciation (sometimes
 * called the salvage value of the asset). Life is the number of periods
 * over which the asset is depreciated (sometimes called the useful life of
 * the asset). Per is the period and must use the same units as life.
 */
protected static Ptg calcSYD(Ptg[] operands) {
	if (operands.length < 4) {
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	if (DEBUG)
		debugOperands(operands, "calcSYD");
	double cost = operands[0].getDoubleVal();
	double salvage = operands[1].getDoubleVal();
	double life = operands[2].getDoubleVal();
	double per = operands[3].getDoubleVal();
	if (life == 0)
		return new PtgErr(PtgErr.ERROR_NUM);
	double A = (cost - salvage) * (life - per + 1) * 2;
	double B = life * (life + 1);
	double result = A / B;
	if (DEBUG)
		Logger.logInfo("Result from calcSYD= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * TBILLEQ Returns the bond-equivalent yield for a Treasury bill Settlement
 * is the Treasury bill's settlement date. The security settlement date is
 * the date after the issue date when the Treasury bill is traded to the
 * buyer. Maturity is the Treasury bill's maturity date. The maturity date
 * is the date when the Treasury bill expires. Discount is the Treasury
 * bill's discount rate.
 */
protected static Ptg calcTBillEq(Ptg[] operands) {
	if (operands.length < 3) {
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	if (DEBUG)
		debugOperands(operands, "calcTBILLEQ");
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	double rate = operands[2].getDoubleVal();

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate)))
			.longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate)))
			.longValue();
	if (settlementDate >= maturityDate)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (maturityDate - settlementDate > 365)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (rate <= 0)
		return new PtgErr(PtgErr.ERROR_NUM);

	double DSM = maturityDate - settlementDate;
	double result;
	if (DSM <= 182) {
		result = (365 * rate) / (360 - rate * DSM);
	} else {
		double A= DSM/365;
		double B= rate*DSM;
		
		double C = (2*A - 1)*B/(B - 360);
		double D=  Math.pow(A, 2)-C;
		result = (-2*A +  2*Math.sqrt(D))/(2*A - 1);
	}
	if (DEBUG)
		Logger.logInfo("Result from calcTBILLEQ= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * TBILLPRICE Returns the price per $100 face value for a Treasury bill
 */
protected static Ptg calcTBillPrice(Ptg[] operands) {
	if (operands.length < 3) {
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	if (DEBUG)
		debugOperands(operands, "calcTBILLPRICE");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	double rate = operands[2].getDoubleVal();
	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate)))
			.longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate)))
			.longValue();

	if (settlementDate >= maturityDate)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (maturityDate - settlementDate > 365)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (rate <= 0)
		return new PtgErr(PtgErr.ERROR_NUM);

	double DSM = maturityDate - settlementDate;
	double result = 100 * (1 - (rate * DSM / 360));
	if (DEBUG)
		Logger.logInfo("Result from calcTBILLPRICE= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * TBILLYIELD Returns the yield for a Treasury bill
 * 
 * VDB Returns the depreciation of an asset for a specified or partial
 * period using a declining balance method
 */
protected static Ptg calcTBillYield(Ptg[] operands) {
	if (operands.length < 3) {
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	if (DEBUG)
		debugOperands(operands, "calcTBILLYIELD");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	double price = operands[2].getDoubleVal();

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate)))
			.longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate)))
			.longValue();
	if (settlementDate >= maturityDate)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (maturityDate - settlementDate > 365)
		return new PtgErr(PtgErr.ERROR_NUM);
	if (price <= 0)
		return new PtgErr(PtgErr.ERROR_NUM);

	double DSM = maturityDate - settlementDate;
	double result = ((100 - price) / price) * (360 / DSM);
	if (DEBUG)
		Logger.logInfo("Result from calcTBILLYIELD= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}
/**
 * VDB
 * Returns the depreciation of an asset for any period you specify, 
 * including partial periods, using the double-declining balance method or 
 * some other method you specify. VDB stands for variable declining balance.
 * 
 * VDB(cost,salvage,life,start_period,end_period,factor,no_switch)
 */
protected static Ptg calcVDB(Ptg[] operands) {
	if (operands.length < 5) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG) debugOperands(operands, "calcVDB");
	double cost= operands[0].getDoubleVal();
	double salvage= operands[1].getDoubleVal();
	int life= operands[2].getIntVal();
	int start_period= operands[3].getIntVal();
	int end_period= operands[4].getIntVal();
	int factor= 2;
	if (operands.length > 5 && !(operands[5] instanceof PtgMissArg)) 
		factor= operands[5].getIntVal();
	boolean bNoSwitch= false;
	if (operands.length > 6)
		bNoSwitch= PtgCalculator.getBooleanValue(operands[6]);
	
	if (cost <= 0 || salvage <= 0 || life <= 0 || 
			start_period < 0 || end_period < 0 || 
			factor < 0 || end_period > life)
		return new PtgErr(PtgErr.ERROR_NUM);
	
	double result= 0.0;
	
	Ptg[] ops= new Ptg[5];
	ops[0]= new PtgNumber(cost);
	ops[1]= new PtgNumber(salvage);
	ops[2]= new PtgInt(life);
	ops[4]= new PtgInt(factor);
	if (bNoSwitch) {	// just sum ddb
		for (int i= start_period+1; i<=end_period; i++) {
			ops[3]= new PtgInt(i);
			result+= calcDDB(ops).getDoubleVal(); 
		}
	}
	else {  // switch to straight-line depreciation when dep. > ddb calc
		boolean bSwitch= false;
		double A= 0.0;
		int i= start_period + 1;
		while (i <= end_period && !bSwitch) {
			double sl= (cost-A-salvage)/(life-i+1);
			ops[3]= new PtgInt(i);
			double ddb= calcDDB(ops).getDoubleVal();
			if (sl <= ddb) {
				A+= ddb;
				i++;
			}
			else 	// straight-line depreciation is greater than ddb; switch
				bSwitch= true;	
		}
		result= A;
		// use straight-line depreciation for rest of period
		for(int j= i; j <=end_period; j++) {
			result+= (cost - A - salvage)/(life-i+1); 
		}
	}
			
	if (DEBUG) Logger.logInfo("Result from calcVDB= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * XIRR Returns the internal rate of return for a schedule of cash flows
 * that is not necessarily periodic
 */
protected static Ptg calcXIRR(Ptg[] operands) {
	if (operands.length < 2) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcXIRR");
	double guess = .1;
	if (operands.length > 2)
		guess = operands[2].getDoubleVal();
	// convert references to Ptg[]s
	Ptg[] values = PtgCalculator.getAllComponents(operands[0]);
	Ptg[] dates = PtgCalculator.getAllComponents(operands[1]);
	if (DEBUG)
		debugOperands(values, "calcXIRR");
	if (DEBUG)
		debugOperands(dates, "calcXIRR");
	/*
	 * 'Newton-Raphson method: ' Given PV a function of X, determine the
	 * value of X ' such that SUM[PV] = 0, using iteration. ' dPVdX =
	 * derivative of PV with respect to X ' NB: if X = 1 + Rate, dX/dRate =
	 * 1 ' ' Calculate PV and dPVdX using an arbitrary initial estimate of
	 * X. ' Change X by -PV/dPVdX and calculate again. ' Repeat until X
	 * changes by less than some arbitrary small amount.
	 * 
	 * Let X = 1 + Rate 
	 * Let EXPi = -YearFrax(i) 
	 * PVi = Val(i) * X^(EXPi)
	 * dPV/dX = Val(i) * EXPi * X^(EXPi- 1) 
	 *        = Val(i) * X^(EXPi) *EXPi / X 
	 * 		  = PVi * EXPi / X 
	 * dPVSum/dX = SUM[dPV/dX] 
	 * dPVSum/dX = 1/X * SUM[PVi * EXPi] 
	 *  
	 */
	if (values.length != dates.length)
		return new PtgErr(PtgErr.ERROR_NUM);
	// validate dates
	double date0 = dates[0].getDoubleVal();
	for (int i = 1; i < dates.length; i++) {
		// TODO: if not valid date, return PtgErr.ERROR_VALUE
		long val = PtgCalculator.getLongValue(dates[i]);
		if (val < date0)
			return new PtgErr(PtgErr.ERROR_NUM);
	}
	// validate values: sum all outflows (= negative values) + inflows (=
	// positive values)
	double outflow = 0.0, inflow = 0.0;
	// get outflow (- values)
	for (int i = 0; i < values.length; i++) {
		double val = values[i].getDoubleVal();
		if (val < 0)
			outflow += Math.abs(val);
		else
			inflow += val;
	}
	if (outflow == 0.0 || inflow == 0.0)
		return new PtgErr(PtgErr.ERROR_NUM);

	int n = values.length;

	// iterate over possible irr values; value is correct when
	// f <= tolerance, defined as .000001%
	boolean bIsCorrect = false;
	final double TOLERANCE = 0.00000001;
	double trial = guess;
	double x0, f0, f;
	x0 = 1; //1+0 rate, x_0, lower bounds of guess
	f0 = inflow - outflow; // =sum of values when rate is 0
	trial = 1 + guess; // x_1, upper bounds
	// for secant method
	//  double f1= 0.0;
	//	double x1;
	//	double dx= xh-xl;
	double delta = 0;
	double fprime = 0; // derivative of f(x0)
	for (int i = 0; i < n; i++) {
		double val = values[i].getDoubleVal();
		double exp = (PtgCalculator.getLongValue(dates[i]) - date0) / 365;
		//		f+= val*(Math.pow(xh, -exp));
		fprime += val * -exp; // f' of x0
	}
	for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 100 tries
		// SECANT METHOD: trial= x0+dx*f0/(f0-f1);
		// NEWTON'S:
		f = 0.0;
		for (int i = 0; i < n; i++) {
			double val = values[i].getDoubleVal();
			double exp = (dates[i].getDoubleVal() - date0) / 365.0;
			f += val * (Math.pow(trial, -exp));
			fprime += val * (Math.pow(trial, -exp)) * -exp; // derivative of
			// f(trial)
		}
		fprime = fprime / trial; // final f' expression
		delta = f / fprime;
		if (trial - delta <= 0)
			delta = trial / 2;
		trial -= delta;
		/*
		 * secant method if (f < 0) { delta= xl-trial; xl= trial; fl= f; }
		 * else { delta= xh-trial; xh= trial; fh= f; } dx= xh-xl;
		 */
		bIsCorrect = (Math.abs(delta) <= TOLERANCE);
	}
	if (!bIsCorrect)
		return new PtgErr(PtgErr.ERROR_NUM);

	trial -= 1;
	if (DEBUG)
		Logger.logInfo("Result from calcXIRR= " + trial);
	PtgNumber pnum = new PtgNumber(trial);
	return pnum;
}

/**
 * XNPV Returns the net present value for a schedule of cash flows that is
 * not necessarily periodic
 *  
 */
protected static Ptg calcXNPV(Ptg[] operands) {
	if (operands.length < 2 || operands[1].getComponents() == null) {
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcXNPV");
	double rate = operands[0].getDoubleVal();
	// convert references to Ptg[]s
	Ptg[] values = PtgCalculator.getAllComponents(operands[1]);
	Ptg[] dates = PtgCalculator.getAllComponents(operands[2]);
	if (DEBUG)
		debugOperands(values, "calcXNPV");
	if (DEBUG)
		debugOperands(dates, "calcXNPV");

	if (values.length != dates.length)
		return new PtgErr(PtgErr.ERROR_NUM);
	// TODO: validate dates
	double date0 = dates[0].getDoubleVal();
	for (int i = 1; i < dates.length; i++) {
		// TODO: if not valid date, return PtgErr.ERROR_VALUE
		long val = PtgCalculator.getLongValue(dates[i]);
		if (val < date0)
			return new PtgErr(PtgErr.ERROR_NUM);
	}
	// validate values: sum all outflows (= negative values) + inflows (=
	// positive values)
	double outflow = 0.0, inflow = 0.0;
	// get outflow (- values)
	for (int i = 0; i < values.length; i++) {
		double val = values[i].getDoubleVal();
		if (val < 0)
			outflow += Math.abs(val);
		else
			inflow += val;
	}
	if (outflow == 0.0 || inflow == 0.0)
		return new PtgErr(PtgErr.ERROR_NUM);

	int n = values.length;
	double result = 0.0;
	for (int i = 0; i < n; i++) {
		double val = values[i].getDoubleVal();
		double exp = (dates[i].getDoubleVal() - date0) / 365.0;
		result += val / (Math.pow(1 + rate, exp));
	}
	if (DEBUG)
		Logger.logInfo("Result from calcXNPV= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
}

/**
 * YIELD Returns the yield on a security that pays periodic interest
 * 
 * YIELD(settlement,maturity,rate,pr,redemption,frequency,basis) Settlement
 * is the security's settlement date. The security settlement date is the
 * date after the issue date when the security is traded to the buyer.
 * Maturity is the security's maturity date. The maturity date is the date
 * when the security expires. Rate is the security's annual coupon rate. Pr
 * is the security's price per $100 face value. Redemption is the security's
 * redemption value per $100 face value. Frequency is the number of coupon
 * payments per year. For annual payments, frequency = 1; for semiannual,
 * frequency = 2; for quarterly, frequency = 4. Basis (optional) is the type
 * of day count basis to use.
 */
protected static Ptg calcYIELD(Ptg[] operands) {
	if (operands.length < 6) { // not supported by function
		return new PtgErr(PtgErr.ERROR_NULL);
	}
	if (DEBUG)
		debugOperands(operands, "calcINTRATE");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter.getCalendarFromNumber(operands[1].getValue());
	double rate = operands[2].getDoubleVal();
	double pr = operands[3].getDoubleVal();
	double redemption = operands[4].getDoubleVal();
	int frequency = operands[5].getIntVal();
	int basis = 0;
	if (operands.length > 6)
		basis = operands[6].getIntVal();

	//	 TODO: if dates are not valid, return #VALUE! error
	if (basis < 0 || basis > 4)  return new PtgErr(PtgErr.ERROR_NUM);
	if (!sDate.before(mDate))    return new PtgErr(PtgErr.ERROR_NUM);
	if (rate < 0) 				return new PtgErr(PtgErr.ERROR_NUM);
	if (pr <= 0 || redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM);

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue();
	Ptg[] ops;
	ops = new Ptg[4];
	ops[0] = operands[0];
	ops[1] = operands[1];
	ops[2] = operands[5];
	ops[3] = new PtgInt(basis);
	double result = 0.0;
	/*
	 * A= coupDayBS E= coupDays n= coupNum DSC= coupDaysNC DSR= getDaysFromBasis
	 */		
	double n = PtgCalculator.getLongValue(calcCoupNum(ops)); // n is the number of coupons btwn settlement and maturity		
	double E = calcCoupDays(ops).getDoubleVal(); // total # days in coupon period in which settlementfalls
	double A = calcCoupDayBS(ops).getDoubleVal(); // days from beg. coupon period to settlement
	
	if (n <= 1) {
		double DSR = getDaysFromBasis(basis, settlementDate, maturityDate);
		double R = rate / frequency;
		double P = pr / 100;
		result = ((redemption / 100) + R) - (P + ((A / E) * R));
		result /= P + ((A / E) * R);
		result *= (frequency * E) / DSR;
	} else {
		// for n values > 1, must employ an iterated approach to find yield using formula for price
		double DSC = calcCoupDaysNC(ops).getDoubleVal(); // days from  settlement to next coupon
		result= yieldIteration(DSC, E, n, A, rate, frequency, redemption, pr);
		if (result==-1) {// didn't find it
			return new PtgErr(PtgErr.ERROR_NUM);
		}
	}

	PtgNumber pnum = new PtgNumber(result);
	if (DEBUG) Logger.logInfo("Result from calcYIELD= " + result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

private static double yieldIteration(double DSC, double E, double n, double A, double rate, int frequency, double redemption, double pr) {		
	/* Using Newton-Raphson, iterate over possible yield values; value is correct when		
	* f <= tolerance, defined as .000001%
	// 100 iterations, formula for PRICE
	// each trial= f(trial)/f'(trial)
	// f(trial)= price calculation
	// f'(trial): (deep breath)
	 * 
	 *  if f= price, and 
	 *  G and F are given
	 *  C= redemption
	 *  B= 100*R
	 *  E= -(n-1+F)
	 *  Ei = -(i-1+F)
	 *  x= 1+trial/frequency
	 * 
	 *  then,
	 * 
	 *  ui(trial)= B*x^Ei
	 *  f'(ui(trial))= -Ei*B*x^(Ei-1) 
	 * 			  = Ei*B*x^Ei*1/x
	 * 			  = B*x^Ei*1/x*Ei
	 * 			  = ui(trial)*Ei/x
	 *  f'(sum(ui(trial)))= sum(f'(ui))
	 * 			  = sum(ui(trial)*Ei/x
	 * 			  = 1/x*sum(ui(trial)*Ei)
	 *  
	 *   
	 *  f(trial)= C*x^E + sum(ui(trial)) - B*G
	 *  f'(trial)= f'(f(x)+sum(ui(trial))
	 * 			 = f'(x) + f'(sum(ui(trial))
	 * 			 = C*E*x^(E-1) + sum(ui(trial)*Ei/x)
	 * 			 = (C*E*x^E)*(1/x) + (1/x)*sum(ui(trial)*Ei)
	 * 			 = (C*E*x^E)*(1/x) + (1/x)*sum(B*x^Ei*Ei)
	 */
	double guess = .1;
	boolean bIsCorrect = false;
	final double TOLERANCE = 0.00000001;
	double trial = guess;
	double f;
	double F= DSC/E;
	double G= A/E;
	double R= rate/frequency;
	double Y;
	double B= R*100;
	double Exp= n-1+F;

	// N-R iteration
	double fprime; // derivative of f(x0)
	double delta = 0;
	for (int j = 0; j < 100 && !bIsCorrect; j++) { // maximum 100 tries
		// f= f(trial)
		Y= 1+ trial/frequency;
		f= redemption/Math.pow(Y, Exp);
		fprime= 0;
		for (int i = 1; i <= n; i++) {
			f+= B/Math.pow(Y, i - 1 + F);
			fprime+= (B/Math.pow(Y, i - 1 + F))*(i-1+F);
		}
		f -= (B * G);		
		// pr-f =>0
		fprime/=Y;		// final 
		fprime+= redemption/Math.pow(Y, Exp)*Exp*(1/Y);
		
		// N-R:  use f/fprime as iterative factor
		delta = (pr-f) / fprime;			
		if (trial < delta)
			trial= delta-trial;
		else
			trial= trial-delta;

// while (trial - delta <= 0) delta= delta/2; // sanity check
// trial-= delta;
bIsCorrect = (Math.abs(pr-f) <= TOLERANCE);
}
if (bIsCorrect)
return (trial);
else
return -1;
/*
MinYield = -1#
MaxYield = .Rate
If MaxYield = 0 Then MaxYield = 0.1
Do While CalculatedPrice(BondInfo, MaxYield) > .Price
MaxYield = MaxYield * 2
Loop
Yld = 0.5 * (MinYield + MaxYield)
For i = 1 To MaxIterations
Diff = CalculatedPrice(BondInfo, Yld) - .Price
If Abs(Diff) < Accuracy Then Exit For
'if calculated price is greater, correct yield is greater
If Diff > 0 Then MinYield = Yld Else MaxYield = Yld
Yld = 0.5 * (MinYield + MaxYield)
Next i
End If
BondYield = Yld
*/
}

/**
 * YIELDDISC Returns the annual yield for a discounted security. For
 * example, a treasury bill
 */
protected static Ptg calcYieldDisc(Ptg[] operands) {
	if (operands.length < 4) {
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	if (DEBUG)
		debugOperands(operands, "calcYIELDDISC");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	double pr = operands[2].getDoubleVal();
	double redemption = operands[3].getDoubleVal();
	int basis = 0;
	if (operands.length > 4)
		basis = operands[4].getIntVal();

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate))).longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate))).longValue();

	if (pr <= 0 || redemption <= 0) return new PtgErr(PtgErr.ERROR_NUM);
	if (basis < 0 || basis > 4)  return new PtgErr(PtgErr.ERROR_NUM);
	if (maturityDate <= settlementDate)  return new PtgErr(PtgErr.ERROR_NUM);

// double DSM = maturityDate - settlementDate;
// double B = getDaysInYearFromBasis(basis, settlementDate, maturityDate);
// double result = ((redemption - pr) / pr) * (B / DSM);
double result = ((redemption - pr) / pr)/yearFrac(basis, settlementDate, maturityDate);
if (DEBUG)
Logger.logInfo("Result from calcYIELDDISC= " + result);
PtgNumber pnum = new PtgNumber(result);
return pnum;
}catch (Exception e) { }
return new PtgErr(PtgErr.ERROR_VALUE);
}

/**
 * YIELDMAT Returns the annual yield of a security that pays interest at
 * maturity
 *  
 */
protected static Ptg calcYieldMat(Ptg[] operands) {
	if (operands.length < 5) {
		PtgErr perr = new PtgErr(PtgErr.ERROR_NULL);
		return perr;
	}
	if (DEBUG)
		debugOperands(operands, "calcYIELDMAT");
	try {
	GregorianCalendar sDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[0].getValue());
	GregorianCalendar mDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[1].getValue());
	GregorianCalendar iDate = (GregorianCalendar) DateConverter
			.getCalendarFromNumber(operands[2].getValue());
	double rate = operands[3].getDoubleVal();
	double price = operands[4].getDoubleVal();
	int basis = 0;
	if (operands.length > 5)
		basis = operands[5].getIntVal();

	long settlementDate = (new Double(DateConverter.getXLSDateVal(sDate)))
			.longValue();
	long maturityDate = (new Double(DateConverter.getXLSDateVal(mDate)))
			.longValue();
	long issueDate = (new Double(DateConverter.getXLSDateVal(iDate)))
			.longValue();

	double B= getDaysInYearFromBasis(basis,settlementDate, maturityDate);
	double DSM= getDaysFromBasis(basis, settlementDate, maturityDate);
	double DIM= getDaysFromBasis(basis, issueDate, maturityDate);
	double A= getDaysFromBasis(basis, issueDate, settlementDate);

	double result= (((1 + (DIM/B)*rate) - ((price/100) + (A/B)*rate))/((price/100) + (A/B)*rate)) * (B/DSM);
	if (DEBUG)  	Logger.logInfo("Result from calcYIELDMAT= " + result);
	PtgNumber pnum = new PtgNumber(result);
	return pnum;
	}catch (Exception e) { }
	return new PtgErr(PtgErr.ERROR_VALUE);
}

static void debugOperands(Ptg[] operands, String f) {
	if (DEBUG) {
		Logger.logInfo("Operands for " + f);
		for (int i = 0; i < operands.length; i++) {
			String s = operands[i].getString();
			if (!(operands[i] instanceof PtgMissArg)) {
				String v = operands[i].getValue().toString();
				Logger.logInfo("\tOperand[" + i + "]=" + s + " " + v);
			} else
				Logger.logInfo("\tOperand[" + i + "]=" + s + " is Missing");
		}
	}
}

public static boolean isLeapYear(int year) {
	return (year % 400 == 0) || ((year % 100 != 0) && (year % 4 == 0));
}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值