问题
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
In above the first values gave wrong result than what I get in Excel but on 2nd values it gave correct result.
回答1:
I think your method is not converging for the example that doesn't work.
Nobody seems sure what method Excel uses, but there is speculation that it uses the Newton Raphson method, rather than the secant method.
I've done an implementation below using N-R which seems to work.
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
回答2:
Here is a Swift port of the C++ code used for the RATE function in LibreOffice. The original source code can be found at https://cgit.freedesktop.org/libreoffice/core/tree/sc/source/core/tool/interpr2.cxx
These are setup to return nil
if there is any error trying to calculate the rate.
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
}
Here are your two test cases:
RATE(nper: 252, pmt: -29002.85, pv: 2500000, fv: 0)
RATE(nper: 24, pmt: -46.14, pv: 1000, fv: 0)
The result is:
0.010833331202068584
0.0083244384774994358
来源:https://stackoverflow.com/questions/49092747/rate-function-from-excel-in-swift-providing-different-results