From cf1c5ee9e195b102c37f3736e3a24f69d58118a5 Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Wed, 19 Oct 2011 11:32:44 +0000 Subject: [PATCH] Add implementation of RATE from patch in bug #52050 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1186111 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/status.xml | 1 + .../poi/ss/formula/eval/FunctionEval.java | 1 + .../apache/poi/ss/formula/functions/Rate.java | 119 ++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 src/java/org/apache/poi/ss/formula/functions/Rate.java diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 97282270b..06bd108fd 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,6 +34,7 @@ + 52050 - Support for the Excel RATE function 51566 - HSLF fix for finishing parsing the picture stream on the first non-valid type 51974 - Avoid HWPF issue when identifying the picture type 52035 - Fix signed issue with very large word 6 files diff --git a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java index 428b6d797..b3a18c1a1 100644 --- a/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java +++ b/src/java/org/apache/poi/ss/formula/eval/FunctionEval.java @@ -102,6 +102,7 @@ public final class FunctionEval { retval[58] = FinanceFunction.NPER; retval[59] = FinanceFunction.PMT; + retval[60] = new Rate(); retval[62] = new Irr(); retval[63] = NumericFunction.RAND; retval[64] = new Match(); diff --git a/src/java/org/apache/poi/ss/formula/functions/Rate.java b/src/java/org/apache/poi/ss/formula/functions/Rate.java new file mode 100644 index 000000000..bc5358513 --- /dev/null +++ b/src/java/org/apache/poi/ss/formula/functions/Rate.java @@ -0,0 +1,119 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.ss.formula.functions; + +import org.apache.poi.ss.formula.eval.ErrorEval; +import org.apache.poi.ss.formula.eval.EvaluationException; +import org.apache.poi.ss.formula.eval.NumberEval; +import org.apache.poi.ss.formula.eval.OperandResolver; +import org.apache.poi.ss.formula.eval.ValueEval; + +/** + * Implements the Excel Rate function + */ +public class Rate implements Function { + public ValueEval evaluate(ValueEval[] args, int srcRowIndex, int srcColumnIndex) { + if (args.length < 3) { //First 3 parameters are mandatory + return ErrorEval.VALUE_INVALID; + } + + double periods, payment, present_val, future_val = 0, type = 0, estimate = 0.1, rate; + + try { + ValueEval v1 = OperandResolver.getSingleValue(args[0], srcRowIndex, srcColumnIndex); + ValueEval v2 = OperandResolver.getSingleValue(args[1], srcRowIndex, srcColumnIndex); + ValueEval v3 = OperandResolver.getSingleValue(args[2], srcRowIndex, srcColumnIndex); + ValueEval v4 = null; + if (args.length >= 4) + v4 = OperandResolver.getSingleValue(args[3], srcRowIndex, srcColumnIndex); + ValueEval v5 = null; + if (args.length >= 5) + v5 = OperandResolver.getSingleValue(args[4], srcRowIndex, srcColumnIndex); + ValueEval v6 = null; + if (args.length >= 6) + v6 = OperandResolver.getSingleValue(args[5], srcRowIndex, srcColumnIndex); + + periods = OperandResolver.coerceValueToDouble(v1); + payment = OperandResolver.coerceValueToDouble(v2); + present_val = OperandResolver.coerceValueToDouble(v3); + if (args.length >= 4) + future_val = OperandResolver.coerceValueToDouble(v4); + if (args.length >= 5) + type = OperandResolver.coerceValueToDouble(v5); + if (args.length >= 6) + estimate = OperandResolver.coerceValueToDouble(v6); + rate = calculateRate(periods, payment, present_val, future_val, type, estimate) ; + + checkValue(rate); + } catch (EvaluationException e) { + e.printStackTrace() ; + return e.getErrorEval(); + } + + 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 = 0, f = 0, i = 0; + double rate = guess; + 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 = 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; + } + return rate; + } + + /** + * Excel does not support infinities and NaNs, rather, it gives a #NUM! error in these cases + * + * @throws EvaluationException (#NUM!) if result is NaN or Infinity + */ + static final void checkValue(double result) throws EvaluationException { + if (Double.isNaN(result) || Double.isInfinite(result)) { + throw new EvaluationException(ErrorEval.NUM_ERROR); + } + } +}