func RATE(nper: Double, pmt: Double, pv: Double, fv: Double, type: Double, guess: Double) -> Double{
var rate = guess
var y: Double = 0
var f: Double = 0
var FINANCIAL_MAX_ITERATIONS: Double = 128
var FINANCIAL_PRECISION = 1.0e-08
if (abs(rate) < FINANCIAL_PRECISION) {
y = pv * (1 + nper * rate) + pmt * (1 + rate * type) * nper + fv
} else {
f = exp(nper * log(1 + rate))
y = pv * f + pmt * (1 / rate + type) * (f - 1) + fv
}
var y0 = pv + pmt * nper + fv
var y1 = pv * f + pmt * (1 / rate + type) * (f - 1) + fv
// find root by secant method
var i: Double = 0
var x0: Double = 0
var x1 = rate
while ((abs(y0 - y1) > FINANCIAL_PRECISION) && (i < FINANCIAL_MAX_ITERATIONS)) {
rate = (y1 * x0 - y0 * x1) / (y1 - y0)
x0 = x1
x1 = rate
if (abs(rate) < FINANCIAL_PRECISION) {
y = pv * (1 + nper * rate) + pmt * (1 + rate * type) * nper + fv
} else {
f = exp(nper * log(1 + rate))
y = pv * f + pmt * (1 / rate + type) * (f - 1) + fv
}
y0 = y1
y1 = y
i += 1
}
return rate
}
RATE(nper:252,pmt:-29002.85,pv:2500000,fv:0,type:0,guess:0.1)// -1.347153369879729 - WRONG(Correct is 0.010833331)
RATE(nper:24,pmt:-46.14,pv:1000,fv:0,type:0,guess:0.1)//0.008324438477500274 --- CORRECT
在上面,第一个值给出了错误的结果,而不是我在Excel中获得的结果,但是在第二个值上给出了正确的结果。
答案 0 :(得分:0)
我认为你的方法并没有收敛到不起作用的例子。
似乎没有人确定Excel使用什么方法,但有人猜测它使用Newton Raphson方法,而不是割线方法。
我在下面使用N-R完成了一项似乎有效的实现。
func pvCalc(nper: Double, pmt: Double, fv: Double, type: Int, rate: Double) -> Double {
let pvPayments = -pmt / rate * (1 - 1 / pow(1 + rate, nper))
let pvFV = -fv / pow(1 + rate, nper)
return type == 0 ? pvPayments + pvFV : pvPayments * (1 + rate) + pvFV
}
func pvDeriv(nper: Double, pmt: Double, fv: Double, type: Int, rate: Double) -> Double {
let derivPayments = pmt / pow(rate, 2) * (1 - 1 / pow(1 + rate, nper)) - pmt * nper / rate / pow(1 + rate, nper + 1)
let derivFV = fv * nper / pow(1 + rate, nper + 1)
if type == 0 {
return derivPayments + derivFV
} else {
return (1 + rate) * derivPayments - pmt / rate * (1 - 1 / pow(1 + rate, nper)) + derivFV
}
}
func RATE(nper: Double, pmt: Double, pv: Double, fv: Double, type: Int, guess: Double) -> Double{
let FINANCIAL_MAX_ITERATIONS = 512
let FINANCIAL_PRECISION = 1.0e-08
var rate = guess
for _ in 1...FINANCIAL_MAX_ITERATIONS {
let current_pv = pvCalc(nper: nper, pmt: pmt, fv: fv, type: type, rate: rate)
if abs(current_pv - pv) < FINANCIAL_PRECISION {
return rate
}
let current_pvDeriv = pvDeriv(nper: nper, pmt: pmt, fv: fv, type: type, rate: rate)
rate = rate - (current_pv - pv) / current_pvDeriv
}
return rate
}
RATE(nper: 252, pmt: -29002.85, pv: 2_500_000, fv: 0, type: 0, guess: 0.1) // 0.01083333120206857
RATE(nper: 24, pmt: -46.14, pv: 1000, fv: 0, type: 0, guess: 0.1) // 0.008324438477472666
答案 1 :(得分:0)
这是用于LibreOffice中RATE函数的C ++代码的Swift端口。原始源代码可在https://cgit.freedesktop.org/libreoffice/core/tree/sc/source/core/tool/interpr2.cxx
找到如果尝试计算费率时出现任何错误,则设置为返回nil
。
func rateIteration(nper: Double, pmt: Double, pval: Double, fval: Double, type: Bool, guess: Double) -> Double? {
// See also #i15090#
// Newton-Raphson method: x(i+1) = x(i) - f(x(i)) / f'(x(i))
// This solution handles integer and non-integer values of Nper different.
// If ODFF will constraint Nper to integer, the distinction of cases can be
// removed; only the integer-part is needed then.
var valid = true
var found = false
var x = 0.0
var xNew = 0.0
var term = 0.0
var termDerivation = 0.0
var geoSeries = 0.0
var geoSeriesDerivation = 0.0
let iterationsMax = 150
var count = 0
let epsilonSmall = 1.0E-14
let SCdEpsilon = 1.0E-7
var pv = pval
var fv = fval
if type {
// payment at beginning of each period
fv = fv - pmt
pv = pv + pmt
}
if nper == nper.rounded() {
// Integer nper
x = guess
while !found && count < iterationsMax {
let powNminues1 = pow(1 + x, nper - 1)
let powN = powNminues1 * (1 + x)
if x == 0.0 {
geoSeries = nper
geoSeriesDerivation = nper * (nper - 1) / 2
} else {
geoSeries = (powN - 1) / x
geoSeriesDerivation = nper * powNminues1 / x - geoSeries / x
}
term = fv + pv * powN + pmt * geoSeries
termDerivation = pv * nper * powNminues1 + pmt * geoSeriesDerivation
if abs(term) < epsilonSmall {
found = true // will catch root which is at an extreme
} else {
if termDerivation == 0.0 {
xNew = x + 1.1 * SCdEpsilon // move away from zero slope
} else {
xNew = x - term / termDerivation
}
count += 1
// more accuracy not possible in oscillating cases
found = abs(xNew - x) < SCdEpsilon
x = xNew
}
}
valid = x > -1.0
} else {
// nper is not an integer value
x = (guess < -1.0) ? -1.0 : guess
while valid && !found && count < iterationsMax {
if x == 0.0 {
geoSeries = nper
geoSeriesDerivation = nper * (nper - 1) / 2
} else {
geoSeries = (pow(1 + x, nper) - 1) / x
geoSeriesDerivation = nper * pow(1 + x, nper - 1) / x - geoSeries / x
}
term = fv + pv * pow(1 + x, nper) + pmt * geoSeries
termDerivation = pv * nper * pow(1 + x, nper - 1) + pmt * geoSeriesDerivation
if abs(term) < epsilonSmall {
found = true // will catch root which is at an extreme
} else {
if termDerivation == 0.0 {
xNew = x + 1.1 * SCdEpsilon
} else {
xNew = x - term / termDerivation
}
count += 1
// more accuracy not possible in oscillating cases
found = abs(xNew - x) < SCdEpsilon
x = xNew
valid = x >= -1.0 // otherwise pow(1 + x, nper) will fail
}
}
}
if valid && found {
return x
} else {
return nil
}
}
func RATE(nper: Double, pmt: Double, pv: Double, fv: Double, type: Double = 0, guess: Double = 0.1) -> Double? {
let payType = type != 0.0
if nper <= 0.0 { // constraint from ODFF spec
return nil
}
if let res = rateIteration(nper: nper, pmt: pmt, pval: pv, fval: fv, type: payType, guess: guess) {
return res
} else {
if guess == 0.1 {
/* TODO: this is rather ugly, instead of looping over different
* guess values and doing a Newton goal seek for each we could
* first insert the values into the RATE equation to obtain a set
* of y values and then do a bisecting goal seek, possibly using
* different algorithms. */
var x = guess
for step in 2...10 {
var g = x * Double(step)
if let res = rateIteration(nper: nper, pmt: pmt, pval: pv, fval: fv, type: payType, guess: g) {
return res
} else {
g = x / Double(step)
if let res = rateIteration(nper: nper, pmt: pmt, pval: pv, fval: fv, type: payType, guess: g) {
return res
}
}
}
}
}
return nil
}
以下是您的两个测试用例:
RATE(nper: 252, pmt: -29002.85, pv: 2500000, fv: 0)
RATE(nper: 24, pmt: -46.14, pv: 1000, fv: 0)
结果是:
0.010833331202068584
0.0083244384774994358