return new NumberEval(rate);
}
- private double calculateRate(double nper, double pmt, double pv, double fv, double type, double guess) {
- //FROM MS http://office.microsoft.com/en-us/excel-help/rate-HP005209232.aspx
- int FINANCIAL_MAX_ITERATIONS = 20;//Bet accuracy with 128
- double FINANCIAL_PRECISION = 0.0000001;//1.0e-8
-
- double y, y0, y1, x0, x1, f = 0, i;
- double rate = guess;
- if (Math.abs(rate) < FINANCIAL_PRECISION) {
- y = pv * (1 + nper * rate) + pmt * (1 + rate * type) * nper + fv;
- } else {
- f = Math.pow(1 + rate, nper);
- y = pv * f + pmt * (1 / rate + type) * (f - 1) + fv;
+ private static double _g_div_gp(double r, double n, double p, double x, double y, double w) {
+ double t1 = Math.pow(r+1, n);
+ double t2 = Math.pow(r+1, n-1);
+ return (y + t1*x + p*(t1 - 1)*(r*w + 1)/r) /
+ (n*t2*x - p*(t1 - 1)*(r*w + 1)/(Math.pow(r, 2) + n*p*t2*(r*w + 1)/r +
+ p*(t1 - 1)*w/r));
+ }
+
+ /**
+ * Compute the rate of interest per period.
+ *
+ * The implementation was ported from the NumPy library,
+ * see https://github.com/numpy/numpy-financial/blob/d02edfb65dcdf23bd571c2cded7fcd4a0528c6af/numpy_financial/_financial.py#L602
+ *
+ *
+ * @param nper Number of compounding periods
+ * @param pmt Payment
+ * @param pv Present Value
+ * @param fv Future value
+ * @param type When payments are due ('begin' (1) or 'end' (0))
+ * @param guess Starting guess for solving the rate of interest
+ * @return rate of interest per period or NaN if the solution didn't converge
+ */
+ static double calculateRate(double nper, double pmt, double pv, double fv, double type, double guess){
+ double tol = 1e-8;
+ double maxiter = 100;
+
+ double rn = guess;
+ int iter = 0;
+ boolean close = false;
+ while (iter < maxiter && !close){
+ double rnp1 = rn - _g_div_gp(rn, nper, pmt, pv, fv, type);
+ double diff = Math.abs(rnp1 - rn);
+ close = diff < tol;
+ iter += 1;
+ rn = rnp1;
+
}
- y0 = pv + pmt * nper + fv;
- y1 = pv * f + pmt * (1 / rate + type) * (f - 1) + fv;
-
- // find root by Newton secant method
- i = x0 = 0.0;
- x1 = rate;
- while ((Math.abs(y0 - y1) > FINANCIAL_PRECISION) && (i < FINANCIAL_MAX_ITERATIONS)) {
- rate = (y1 * x0 - y0 * x1) / (y1 - y0);
- x0 = x1;
- x1 = rate;
-
- if (Math.abs(rate) < FINANCIAL_PRECISION) {
- y = pv * (1 + nper * rate) + pmt * (1 + rate * type) * nper + fv;
- } else {
- f = Math.exp(nper * Math.log(1 + rate));
- y = pv * f + pmt * (1 / rate + type) * (f - 1) + fv;
- }
-
- y0 = y1;
- y1 = y;
- ++i;
+ if(!close)
+ return Double.NaN;
+ else {
+ return rn;
}
- return rate;
}
/**
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.ss.usermodel.FormulaError;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.apache.poi.ss.util.Utils.addRow;
import static org.apache.poi.ss.util.Utils.assertDouble;
+import static org.apache.poi.ss.util.Utils.assertError;
/**
* Test cases for RATE()
}
}
+ // https://wiki.documentfoundation.org/Documentation/Calc_Functions/RATE
+ @Test
+ void testLibreOfficeExample1() throws Exception {
+ try (HSSFWorkbook wb = new HSSFWorkbook()) {
+ HSSFSheet sheet = wb.createSheet();
+ HSSFRow row = sheet.createRow(0);
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+ HSSFCell cell = row.createCell(0);
+ assertDouble(fe, cell, "RATE(3,-10,900,1,0,0.5)", -0.7634, 0.0001);
+ }
+ }
+
+ // https://wiki.documentfoundation.org/Documentation/Calc_Functions/RATE
+ @Test
+ void testLibreOfficeExample2() throws Exception {
+ try (HSSFWorkbook wb = new HSSFWorkbook()) {
+ HSSFSheet sheet = wb.createSheet();
+ HSSFRow row = sheet.createRow(0);
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+ HSSFCell cell = row.createCell(0);
+ assertDouble(fe, cell, "RATE(3,-10,900)", -0.7563, 0.0001);
+ }
+ }
+
+ /**
+ * LibreOffice and Excel return #!NUM, but POI and NumPy return a result
+ */
+ // https://wiki.documentfoundation.org/Documentation/Calc_Functions/RATE
@Disabled("test fails - see related issue")
+ @Test
+ void testLibreOfficeInfeasibleSolution() throws Exception {
+ try (HSSFWorkbook wb = new HSSFWorkbook()) {
+ HSSFSheet sheet = wb.createSheet();
+ HSSFRow row = sheet.createRow(0);
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+ HSSFCell cell = row.createCell(0);
+ assertDouble(fe, cell, "RATE(3,10,900,1,0,0.5)", -0.7634, 0.0001);
+ }
+ }
+
+ /**
+ * See https://github.com/numpy/numpy-financial/blob/d02edfb65dcdf23bd571c2cded7fcd4a0528c6af/numpy_financial/tests/test_financial.py#L126
+ */
+ @Test
+ void testNumPyExample1() throws Exception {
+ double[] expected = {-0.39920185, -0.02305873, -0.41818459, 0.26513414};
+ double nper = 2;
+ double pmt = 0;
+ double[] pv = {-593.06, -4725.38, -662.05, -428.78};
+ double[] fv = {214.07, 4509.97, 224.11, 686.29};
+ try (HSSFWorkbook wb = new HSSFWorkbook()) {
+ HSSFSheet sheet = wb.createSheet();
+ HSSFRow row = sheet.createRow(0);
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+ for(int i = 0; i < pv.length; i++){
+ String fmla = String.format("RATE(%2f, %2f, %2f, %2f, 0, 0.1)", nper, pmt, pv[i], fv[i]);
+ HSSFCell cell = row.createCell(i);
+ assertDouble(fe, cell, fmla, expected[i], 1e-8);
+ }
+ }
+ }
+
+ @Test
+ void testNumPyExample2() throws Exception {
+ try (HSSFWorkbook wb = new HSSFWorkbook()) {
+ HSSFSheet sheet = wb.createSheet();
+ HSSFRow row = sheet.createRow(0);
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+ HSSFCell cell = row.createCell(0);
+ assertDouble(fe, cell, "RATE(10, 0, -3500, 10000.0, 0, 0.1)", 0.1106908537142689284704528100, 1E-6);
+ }
+ }
+
+ /**
+ * RATE will return NaN, if the Newton Raphson method cannot find a
+ * feasible rate within the required tolerance or number of iterations.
+ * This can occur if both `pmt` and `pv` have the same sign, as it is
+ * impossible to repay a loan by making further withdrawls.
+ *
+ * See https://github.com/numpy/numpy-financial/blob/d02edfb65dcdf23bd571c2cded7fcd4a0528c6af/numpy_financial/tests/test_financial.py#L113
+ */
+ @Test
+ void testNumPyInfeasibleSolution1() throws Exception {
+ try (HSSFWorkbook wb = new HSSFWorkbook()) {
+ HSSFSheet sheet = wb.createSheet();
+ HSSFRow row = sheet.createRow(0);
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+ HSSFCell cell = row.createCell(0);
+ assertError(fe, cell, "RATE(12, 400, 10000, 5000.0, 0, 0.1)", FormulaError.NUM);
+ }
+ }
+
+ // https://github.com/numpy/numpy-financial/blob/d02edfb65dcdf23bd571c2cded7fcd4a0528c6af/numpy_financial/tests/test_financial.py#L126
+ @Test
+ void testNumPyInfeasibleSolution2() throws Exception {
+ try (HSSFWorkbook wb = new HSSFWorkbook()) {
+ HSSFSheet sheet = wb.createSheet();
+ HSSFRow row = sheet.createRow(0);
+ HSSFFormulaEvaluator fe = new HSSFFormulaEvaluator(wb);
+ HSSFCell cell = row.createCell(0);
+ assertError(fe, cell, "RATE(2, 0, -13.65, -329.67, 0, 0.1)", FormulaError.NUM);
+ }
+ }
+
@Test
void testBug65988() throws Exception {
try (HSSFWorkbook wb = new HSSFWorkbook()) {