From 9a53aca6421a86ea51e59d2fcb09af3cff8ccf03 Mon Sep 17 00:00:00 2001 From: kritagyadubey Date: Sat, 17 Oct 2026 17:52:08 +0530 Subject: [PATCH] BUG: take the fv result dtype from all inputs, not just rate An integer rate gave an integer output buffer, so fractional results were truncated and ipmt/ppmt raised ValueError when they tried to store NaN in the remaining-balance array. --- numpy_financial/_financial.py | 4 +++- numpy_financial/tests/test_financial.py | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/numpy_financial/_financial.py b/numpy_financial/_financial.py index 1adbbea..0305a65 100644 --- a/numpy_financial/_financial.py +++ b/numpy_financial/_financial.py @@ -242,7 +242,9 @@ def fv(rate, nper, pmt, pv, when: _When = 'end'): when = _convert_when(when) rate, nper, pmt, pv, when = np.broadcast_arrays(rate, nper, pmt, pv, when) - fv_array = np.empty_like(rate) + # An integer `rate` on its own would give an integer buffer, truncating + # the result, so take the dtype from all the inputs (gh-166). + fv_array = np.empty_like(rate, dtype=np.result_type(rate, nper, pmt, pv, 1.0)) zero = rate == 0 nonzero = ~zero diff --git a/numpy_financial/tests/test_financial.py b/numpy_financial/tests/test_financial.py index 765e1b2..9150490 100644 --- a/numpy_financial/tests/test_financial.py +++ b/numpy_financial/tests/test_financial.py @@ -579,6 +579,11 @@ def test_broadcast_decimal(self, when, desired): result = npf.ppmt(*args) if when is None else npf.ppmt(*args, when) assert_decimal_close(result, desired, tol=1e-8) + def test_gh_166(self): + # An integer rate gave an integer buffer, so storing NaN in it + # raised a ValueError. + assert_allclose(npf.ppmt(0, 1, 12, 1200), -100.0, rtol=1e-10) + class TestIpmt: def test_float(self): @@ -679,6 +684,11 @@ def test_0d_inputs(self): # 0d array inputs should return a scalar. assert numpy.isscalar(npf.ipmt(*args)) + def test_gh_166(self): + # An integer rate gave an integer buffer, so storing NaN in it + # raised a ValueError. + assert_allclose(npf.ipmt(0, 1, 12, 1200), 0.0, rtol=1e-10) + class TestFv: def test_float(self): @@ -749,6 +759,17 @@ def test_some_rates_zero(self): rtol=1e-10, ) + @pytest.mark.parametrize( + "args, desired", + [ + ((0, 12, -100.5, -1000.25), 2206.25), + (([0, 0], 12, -100.5, -1000.25), [2206.25, 2206.25]), + ], + ) + def test_gh_166(self, args, desired): + # An integer rate gave an integer buffer, truncating the result. + assert_allclose(npf.fv(*args), desired, rtol=1e-10) + class TestIrr: def test_npv_irr_congruence(self):