--- a/src/core/periodic.rs
+++ b/src/core/periodic.rs
@@ -373,7 +373,7 @@
}
rn.zip_mut_with(&diff, |x, &d| {
- if d > 1e-6 {
+ if !d.is_finite() || d.abs() >= 1e-6 {
*x = f64::NAN
}
});
@@ -405,7 +405,33 @@
+ nper * pmt * &t2 * (rate * when + 1.) / rate
+ pmt * (&t1 - 1.0) * when / rate;
- g / gp
+ let mut result = g / gp;
+
+ // Near zero, the differences of powers in g and gp lose precision.
+ // Expand the annuity factor through r^4 and differentiate that series.
+ // At zero these give the exact limits n and n * (n - 1) / 2.
+ for (ref idx, result) in result.indexed_iter_mut() {
+ let r = rate[idx];
+ let n = nper[idx];
+ if r.abs() * n.abs().max(1.0) <= 1e-4 {
+ let t1 = (n - 1.0) * r / 2.0;
+ let t2 = t1 * ((n - 2.0) * r / 3.0);
+ let t3 = t2 * ((n - 3.0) * r / 4.0);
+ let t4 = t3 * ((n - 4.0) * r / 5.0);
+ let annuity = n * (1.0 + t1 + t2 + t3 + t4);
+ let d1 = (n - 2.0) * r / 3.0;
+ let d2 = d1 * ((n - 3.0) * r / 4.0);
+ let d3 = d2 * ((n - 4.0) * r / 5.0);
+ let derivative = n * (n - 1.0) / 2.0 * (1.0 + 2.0 * d1 + 3.0 * d2 + 4.0 * d3);
+ let timing = 1.0 + r * when[idx];
+ let g = fv[idx] + pv[idx] * (1.0 + r * annuity) + pmt[idx] * timing * annuity;
+ let gp = pv[idx] * (annuity + r * derivative)
+ + pmt[idx] * (when[idx] * annuity + timing * derivative);
+ *result = g / gp;
+ }
+ }
+
+ result
}
// http://westclintech.com/SQL-Server-Financial-Functions/SQL-Server-NFV-function
--- a/tests/test_periodic.rs
+++ b/tests/test_periodic.rs
@@ -573,7 +573,72 @@
})
}
+#[rstest]
+fn test_rate_vec_rejects_unconverged_negative_step() {
+ Python::with_gil(|py| {
+ // The first element converges, but the second is still taking steps
+ // larger than the tolerance when the iteration limit is reached.
+ let actual: Vec