【问题标题】:RATE Function from EXCEL in Swift providing different resultsSwift 中 EXCEL 的 RATE 函数提供不同的结果
【发布时间】:2018-03-04 06:43:21
【问题描述】:
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 -- 错误(正确的是 0.010833331)

RATE(nper: 24, pmt: -46.14, pv: 1000, fv: 0, type: 0, guess: 0.1) //0.008324438477500274 --- 正确

在上面第一个值给出的结果比我在 Excel 中得到的结果错误,但在第二个值上它给出了正确的结果。

【问题讨论】:

  • 仅供参考 - 看来这是从stackoverflow.com/a/3199045/1226963 翻译的 PHP 代码,并且 PHP 代码给出的结果与您的 Swift 代码相同。
  • @rmaddy 是的。但是如何得到正确的结果呢?

标签: swift finance rate


【解决方案1】:

我认为对于不起作用的示例,您的方法没有收敛。

似乎没有人确定 Excel 使用什么方法,但有人猜测它使用的是牛顿拉夫森方法,而不是割线方法。

我在下面使用 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

【讨论】:

  • 我认为这不适用于 RATE(475, -1022.93, 272779.21, 0, 0, 0.1),尽管您的代码似乎比“使用牛顿法重新创建 Excel RATE 函数”中的代码更可靠”。在 Excel 中,计算结果为 0.2716%。您是否能够检查您的代码中的这些特定输入?
【解决方案2】:

这是用于 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

【讨论】:

    猜你喜欢
    • 2018-11-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-01
    • 2020-08-20
    • 1970-01-01
    • 2018-05-20
    • 2021-07-16
    相关资源
    最近更新 更多