apache poi 的 Rate 函数声明它“// 通过牛顿正割法求根”。这是无稽之谈,因为Secant method 只是一种准牛顿方法。还有“If the initial values are not close enough to the root, then there is no guarantee that the secant method converges.”。
所以0.1 的默认guess 似乎不够“接近”,所以如果我们使用cell.setCellFormula("RATE(85.77534246575343, -1589.0, -18664.0, 5855586.0, 0, 0.06)"); - 请注意type 和guess 属性的显式设置并具有guess 属性“足够接近”到0.05819488005- 的结果,然后公式计算正确。
如果apache poi 真的会使用Newton's method,那么该函数也会使用0.1 的默认guess 正确评估。牛顿方法的缺点是它需要在每一步都评估f 及其导数f′。所以在某些情况下它可能比割线法慢。
例子:
import java.io.FileOutputStream;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFSheet;
public class ExcelRATEFunction {
private static double calculateRateNewton(double nper, double pmt, double pv, double fv, double type, double guess) {
int FINANCIAL_MAX_ITERATIONS = 20;
double FINANCIAL_PRECISION = 0.0000001;
double y, y1, xN = 0, f = 0, i = 0;
double rate = guess;
//find root by Newtons method (https://en.wikipedia.org/wiki/Newton%27s_method), not secant method!
//Formula see: https://wiki.openoffice.org/wiki/Documentation/How_Tos/Calc:_Derivation_of_Financial_Formulas#PV.2C_FV.2C_PMT.2C_NPER.2C_RATE
f = Math.pow(1 + rate, nper);
y = pv * f + pmt * ((f - 1) / rate) * (1 + rate * type) + fv;
//first derivative:
//y1 = (pmt * nper * type * Math.pow(rate,2) * f - pmt * f - pmt * rate * f + pmt * nper * rate * f + pmt * rate + pmt + nper * pv * Math.pow(rate,2) * f) / (Math.pow(rate,2) * (rate+1));
y1 = (f * ((pmt * nper * type + nper * pv) * Math.pow(rate,2) + (pmt * nper - pmt) * rate - pmt) + pmt * rate + pmt) / (Math.pow(rate,3) + Math.pow(rate,2));
xN = rate - y/y1;
while ((Math.abs(rate - xN) > FINANCIAL_PRECISION) && (i < FINANCIAL_MAX_ITERATIONS)) {
rate = xN;
f = Math.pow(1 + rate, nper);
y = pv * f + pmt * ((f - 1) / rate) * (1 + rate * type) + fv;
//first derivative:
//y1 = (pmt * nper * type * Math.pow(rate,2) * f - pmt * f - pmt * rate * f + pmt * nper * rate * f + pmt * rate + pmt + nper * pv * Math.pow(rate,2) * f) / (Math.pow(rate,2) * (rate+1));
y1 = (f * ((pmt * nper * type + nper * pv) * Math.pow(rate,2) + (pmt * nper - pmt) * rate - pmt) + pmt * rate + pmt) / (Math.pow(rate,3) + Math.pow(rate,2));
xN = rate - y/y1;
++i;
System.out.println(rate+", "+xN+", "+y+", "+y1);
}
rate = xN;
return rate;
}
public static void main(String[] args) throws Exception {
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet();
Row row = sheet.createRow(1);
Cell cell = row.createCell(1);
cell.setCellFormula("RATE(85.77534246575343, -1589.0, -18664.0, 5855586.0, 0, 0.06)");
FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator();
CellType celltype = evaluator.evaluateFormulaCellEnum(cell);
double value = 0.0;
if (celltype == CellType.NUMERIC) {
value = cell.getNumericCellValue();
System.out.println(value);
}
workbook.setForceFormulaRecalculation(true);
value = calculateRateNewton(85.77534246575343, -1589.0, -18664.0, 5855586.0, 0, 0.1);
System.out.println(value);
workbook.write(new FileOutputStream("ExcelRATEFunction.xlsx"));
workbook.close();
}
}