Repository: numpy/numpy

Issue:
Allow Decimal to be used in ipmt, pmt, ppmt, nper, and fv
This change allows for a custom type to be passed for each of the values in ipmt, pmt, and ppmt. 

Example:

```py
import numpy as np
from decimal import Decimal

rate = np.array(Decimal('10'), dtype=Decimal)
nper = np.array(Decimal('1'), dtype=Decimal)
per = np.array(Decimal('1'), dtype=Decimal)
pv = np.array(Decimal('25000.00'), dtype=Decimal)

np.ipmt(rate/Decimal(360), per, nper, pv, fv=Decimal(0.0), when=0)
>>> array(Decimal('-694.4444444444444444444444445'), dtype=object)

np.ipmt(Decimal(10)/Decimal(360), Decimal(1), Decimal(1), Decimal(25000), Decimal(0.0))
>>> array(Decimal('-694.4444444444444444444444445'), dtype=object)

np.pmt(rate/Decimal(360), nper, pv, fv=Decimal(0.0))
>>> Decimal('-25694.44444444444444444444424')

np.pmt(Decimal(10)/Decimal(360), Decimal(1), Decimal(25000), fv=Decimal(0.0))
>>> Decimal('-25694.44444444444444444444424')
```

For fun you can see how float is still different:

```python
np.pmt(10/360, 1, 25000)
>>>  -25694.444444444533
```

If this is a desired/wanted feature I can add some tests specifically for passing `Decimal` as the type. 

Note: if all the tests fail in terrible ways because of this change, I'll likely close this PR in shame. 😸

Also, I'll update my commit message to match the guidelines if this feature enhancement is desired.  


Discussion:
Just in case you though it was doing something clever, `dtype=Decimal` is equivalent to `dtype=object`.
> If this is a desired/wanted feature I can add some tests specifically for passing Decimal as the type.

If it's easy to support it, then I see no reason not to. Please do write these tests.
I plan to come back to this PR in about a week with the proposed changes and tests. 
I have not forgotten about this code. I do still have plans to fix.  Sorry for the delay. 

Resolving patch:
diff --git a/numpy/lib/financial.py b/numpy/lib/financial.py
--- a/numpy/lib/financial.py
+++ b/numpy/lib/financial.py
@@ -7,9 +7,13 @@
 broadcasting and being able to be called with scalars
 or arrays (or other sequences).
 
+Functions support the :class:`decimal.Decimal` type unless
+otherwise stated.
 """
 from __future__ import division, absolute_import, print_function
 
+from decimal import Decimal
+
 import numpy as np
 
 __all__ = ['fv', 'pmt', 'nper', 'ipmt', 'ppmt', 'pv', 'rate',
@@ -32,7 +36,6 @@ def _convert_when(when):
     except (KeyError, TypeError):
         return [_when_to_num[x] for x in when]
 
-
 def fv(rate, nper, pmt, pv, when='end'):
     """
     Compute the future value.
@@ -117,10 +120,8 @@ def fv(rate, nper, pmt, pv, when='end'):
     when = _convert_when(when)
     (rate, nper, pmt, pv, when) = map(np.asarray, [rate, nper, pmt, pv, when])
     temp = (1+rate)**nper
-    miter = np.broadcast(rate, nper, pmt, pv, when)
-    zer = np.zeros(miter.shape)
-    fact = np.where(rate == zer, nper + zer,
-                    (1 + rate*when)*(temp - 1)/rate + zer)
+    fact = np.where(rate == 0, nper,
+                    (1 + rate*when)*(temp - 1)/rate)
     return -(pv*temp + pmt*fact)
 
 def pmt(rate, nper, pv, fv=0, when='end'):
@@ -209,17 +210,18 @@ def pmt(rate, nper, pv, fv=0, when='end'):
     when = _convert_when(when)
     (rate, nper, pv, fv, when) = map(np.array, [rate, nper, pv, fv, when])
     temp = (1 + rate)**nper
-    mask = (rate == 0.0)
-    masked_rate = np.where(mask, 1.0, rate)
-    z = np.zeros(np.broadcast(masked_rate, nper, pv, fv, when).shape)
-    fact = np.where(mask != z, nper + z,
-                    (1 + masked_rate*when)*(temp - 1)/masked_rate + z)
+    mask = (rate == 0)
+    masked_rate = np.where(mask, 1, rate)
+    fact = np.where(mask != 0, nper,
+                    (1 + masked_rate*when)*(temp - 1)/masked_rate)
     return -(fv + pv*temp) / fact
 
 def nper(rate, pmt, pv, fv=0, when='end'):
     """
     Compute the number of periodic payments.
 
+    :class:`decimal.Decimal` type is not supported.
+
     Parameters
     ----------
     rate : array_like
@@ -271,20 +273,18 @@ def nper(rate, pmt, pv, fv=0, when='end'):
     use_zero_rate = False
     with np.errstate(divide="raise"):
         try:
-            z = pmt*(1.0+rate*when)/rate
+            z = pmt*(1+rate*when)/rate
         except FloatingPointError:
             use_zero_rate = True
 
     if use_zero_rate:
-        return (-fv + pv) / (pmt + 0.0)
+        return (-fv + pv) / pmt
     else:
-        A = -(fv + pv)/(pmt+0.0)
-        B = np.log((-fv+z) / (pv+z))/np.log(1.0+rate)
-        miter = np.broadcast(rate, pmt, pv, fv, when)
-        zer = np.zeros(miter.shape)
-        return np.where(rate == zer, A + zer, B + zer) + 0.0
+        A = -(fv + pv)/(pmt+0)
+        B = np.log((-fv+z) / (pv+z))/np.log(1+rate)
+        return np.where(rate == 0, A, B)
 
-def ipmt(rate, per, nper, pv, fv=0.0, when='end'):
+def ipmt(rate, per, nper, pv, fv=0, when='end'):
     """
     Compute the interest portion of a payment.
 
@@ -374,7 +374,7 @@ def ipmt(rate, per, nper, pv, fv=0.0, when='end'):
     ipmt = _rbl(rate, per, total_pmt, pv, when)*rate
     try:
         ipmt = np.where(when == 1, ipmt/(1 + rate), ipmt)
-        ipmt = np.where(np.logical_and(when == 1, per == 1), 0.0, ipmt)
+        ipmt = np.where(np.logical_and(when == 1, per == 1), 0, ipmt)
     except IndexError:
         pass
     return ipmt
@@ -388,7 +388,7 @@ def _rbl(rate, per, pmt, pv, when):
     """
     return fv(rate, (per - 1), pmt, pv, when)
 
-def ppmt(rate, per, nper, pv, fv=0.0, when='end'):
+def ppmt(rate, per, nper, pv, fv=0, when='end'):
     """
     Compute the payment against loan principal.
 
@@ -416,7 +416,7 @@ def ppmt(rate, per, nper, pv, fv=0.0, when='end'):
     total = pmt(rate, nper, pv, fv, when)
     return total - ipmt(rate, per, nper, pv, fv, when)
 
-def pv(rate, nper, pmt, fv=0.0, when='end'):
+def pv(rate, nper, pmt, fv=0, when='end'):
     """
     Compute the present value.
 
@@ -505,9 +505,7 @@ def pv(rate, nper, pmt, fv=0.0, when='end'):
     when = _convert_when(when)
     (rate, nper, pmt, fv, when) = map(np.asarray, [rate, nper, pmt, fv, when])
     temp = (1+rate)**nper
-    miter = np.broadcast(rate, nper, pmt, fv, when)
-    zer = np.zeros(miter.shape)
-    fact = np.where(rate == zer, nper+zer, (1+rate*when)*(temp-1)/rate+zer)
+    fact = np.where(rate == 0, nper, (1+rate*when)*(temp-1)/rate)
     return -(fv + pmt*fact)/temp
 
 # Computed with Sage
@@ -529,7 +527,7 @@ def _g_div_gp(r, n, p, x, y, w):
 #     where
 #  g(r) is the formula
 #  g'(r) is the derivative with respect to r.
-def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100):
+def rate(nper, pmt, pv, fv, when='end', guess=None, tol=None, maxiter=100):
     """
     Compute the rate of interest per period.
 
@@ -545,10 +543,10 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100):
         Future value
     when : {{'begin', 1}, {'end', 0}}, {string, int}, optional
         When payments are due ('begin' (1) or 'end' (0))
-    guess : float, optional
-        Starting guess for solving the rate of interest
-    tol : float, optional
-        Required tolerance for the solution
+    guess : Number, optional
+        Starting guess for solving the rate of interest, default 0.1
+    tol : Number, optional
+        Required tolerance for the solution, default 1e-6
     maxiter : int, optional
         Maximum iterations in finding the solution
 
@@ -573,15 +571,26 @@ def rate(nper, pmt, pv, fv, when='end', guess=0.10, tol=1e-6, maxiter=100):
 
     """
     when = _convert_when(when)
+    default_type = Decimal if isinstance(pmt, Decimal) else float
+
+    # Handle casting defaults to Decimal if/when pmt is a Decimal and
+    # guess and/or tol are not given default values
+    if guess is None:
+        guess = default_type('0.1')
+
+    if tol is None:
+        tol = default_type('1e-6')
+
     (nper, pmt, pv, fv, when) = map(np.asarray, [nper, pmt, pv, fv, when])
+
     rn = guess
-    iter = 0
+    iterator = 0
     close = False
-    while (iter < maxiter) and not close:
+    while (iterator < maxiter) and not close:
         rnp1 = rn - _g_div_gp(rn, nper, pmt, pv, fv, when)
         diff = abs(rnp1-rn)
         close = np.all(diff < tol)
-        iter += 1
+        iterator += 1
         rn = rnp1
     if not close:
         # Return nan's in array of the same shape as rn
@@ -597,6 +606,8 @@ def irr(values):
     that gives a net present value of 0.0; for a more complete explanation,
     see Notes below.
 
+    :class:`decimal.Decimal` type is not supported.
+
     Parameters
     ----------
     values : array_like, shape(N,)
@@ -650,6 +661,11 @@ def irr(values):
     (Compare with the Example given for numpy.lib.financial.npv)
 
     """
+    # `np.roots` call is why this function does not support Decimal type.
+    #
+    # Ultimately Decimal support needs to be added to np.roots, which has
+    # greater implications on the entire linear algebra module and how it does
+    # eigenvalue computations.
     res = np.roots(values[::-1])
     mask = (res.imag == 0) & (res.real > 0)
     if not mask.any():
@@ -657,7 +673,7 @@ def irr(values):
     res = res[mask].real
     # NPV(rate) = 0 can have more than one solution so we return
     # only the solution closest to zero.
-    rate = 1.0/res - 1
+    rate = 1/res - 1
     rate = rate.item(np.argmin(np.abs(rate)))
     return rate
 
@@ -727,12 +743,19 @@ def mirr(values, finance_rate, reinvest_rate):
         Modified internal rate of return
 
     """
-    values = np.asarray(values, dtype=np.double)
+    values = np.asarray(values)
     n = values.size
+
+    # Without this explicit cast the 1/(n - 1) computation below
+    # becomes a float, which causes TypeError when using Decimal
+    # values.
+    if isinstance(finance_rate, Decimal):
+        n = Decimal(n)
+
     pos = values > 0
     neg = values < 0
     if not (pos.any() and neg.any()):
         return np.nan
     numer = np.abs(npv(reinvest_rate, values*pos))
     denom = np.abs(npv(finance_rate, values*neg))
-    return (numer/denom)**(1.0/(n - 1))*(1 + reinvest_rate) - 1
+    return (numer/denom)**(1/(n - 1))*(1 + reinvest_rate) - 1


Test patch:
diff --git a/numpy/lib/tests/test_financial.py b/numpy/lib/tests/test_financial.py
--- a/numpy/lib/tests/test_financial.py
+++ b/numpy/lib/tests/test_financial.py
@@ -1,16 +1,23 @@
 from __future__ import division, absolute_import, print_function
 
+from decimal import Decimal
+
 import numpy as np
 from numpy.testing import (
     run_module_suite, assert_, assert_almost_equal, assert_allclose,
-    assert_equal
-    )
+    assert_equal, assert_raises
+)
 
 
 class TestFinancial(object):
     def test_rate(self):
-        assert_almost_equal(np.rate(10, 0, -3500, 10000),
-                            0.1107, 4)
+        assert_almost_equal(
+            np.rate(10, 0, -3500, 10000),
+            0.1107, 4)
+
+    def test_rate_decimal(self):
+        rate = np.rate(Decimal('10'), Decimal('0'), Decimal('-3500'), Decimal('10000'))
+        assert_equal(Decimal('0.1106908537142689284704528100'), rate)
 
     def test_irr(self):
         v = [-150000, 15000, 25000, 35000, 45000, 60000]
@@ -34,28 +41,84 @@ def test_irr(self):
     def test_pv(self):
         assert_almost_equal(np.pv(0.07, 20, 12000, 0), -127128.17, 2)
 
+    def test_pv_decimal(self):
+        assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0')),
+                     Decimal('-127128.1709461939327295222005'))
+
     def test_fv(self):
-        assert_almost_equal(np.fv(0.075, 20, -2000, 0, 0), 86609.36, 2)
+        assert_equal(np.fv(0.075, 20, -2000, 0, 0), 86609.362673042924)
+
+    def test_fv_decimal(self):
+        assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), 0, 0),
+                     Decimal('86609.36267304300040536731624'))
 
     def test_pmt(self):
-        res = np.pmt(0.08/12, 5*12, 15000)
+        res = np.pmt(0.08 / 12, 5 * 12, 15000)
         tgt = -304.145914
         assert_allclose(res, tgt)
         # Test the edge case where rate == 0.0
-        res = np.pmt(0.0, 5*12, 15000)
+        res = np.pmt(0.0, 5 * 12, 15000)
         tgt = -250.0
         assert_allclose(res, tgt)
         # Test the case where we use broadcast and
         # the arguments passed in are arrays.
-        res = np.pmt([[0.0, 0.8],[0.3, 0.8]],[12, 3],[2000, 20000])
-        tgt = np.array([[-166.66667, -19311.258],[-626.90814, -19311.258]])
+        res = np.pmt([[0.0, 0.8], [0.3, 0.8]], [12, 3], [2000, 20000])
+        tgt = np.array([[-166.66667, -19311.258], [-626.90814, -19311.258]])
         assert_allclose(res, tgt)
 
+    def test_pmt_decimal(self):
+        res = np.pmt(Decimal('0.08') / Decimal('12'), 5 * 12, 15000)
+        tgt = Decimal('-304.1459143262052370338701494')
+        assert_equal(res, tgt)
+        # Test the edge case where rate == 0.0
+        res = np.pmt(Decimal('0'), Decimal('60'), Decimal('15000'))
+        tgt = -250
+        assert_equal(res, tgt)
+        # Test the case where we use broadcast and
+        # the arguments passed in are arrays.
+        res = np.pmt([[Decimal('0'), Decimal('0.8')], [Decimal('0.3'), Decimal('0.8')]],
+                     [Decimal('12'), Decimal('3')], [Decimal('2000'), Decimal('20000')])
+        tgt = np.array([[Decimal('-166.6666666666666666666666667'), Decimal('-19311.25827814569536423841060')],
+                        [Decimal('-626.9081401700757748402586600'), Decimal('-19311.25827814569536423841060')]])
+
+        # Cannot use the `assert_allclose` because it uses isfinite under the covers
+        # which does not support the Decimal type
+        # See issue: https://github.com/numpy/numpy/issues/9954
+        assert_equal(res[0][0], tgt[0][0])
+        assert_equal(res[0][1], tgt[0][1])
+        assert_equal(res[1][0], tgt[1][0])
+        assert_equal(res[1][1], tgt[1][1])
+
     def test_ppmt(self):
-        np.round(np.ppmt(0.1/12, 1, 60, 55000), 2) == 710.25
+        assert_equal(np.round(np.ppmt(0.1 / 12, 1, 60, 55000), 2), -710.25)
+
+    def test_ppmt_decimal(self):
+        assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000')),
+                     Decimal('-710.2541257864217612489830917'))
+
+    # Two tests showing how Decimal is actually getting at a more exact result
+    # .23 / 12 does not come out nicely as a float but does as a decimal
+    def test_ppmt_special_rate(self):
+        assert_equal(np.round(np.ppmt(0.23 / 12, 1, 60, 10000000000), 8), -90238044.232277036)
+
+    def test_ppmt_special_rate_decimal(self):
+        # When rounded out to 8 decimal places like the float based test, this should not equal the same value
+        # as the float, substituted for the decimal
+        def raise_error_because_not_equal():
+            assert_equal(
+                round(np.ppmt(Decimal('0.23') / Decimal('12'), 1, 60, Decimal('10000000000')), 8),
+                Decimal('-90238044.232277036'))
+
+        assert_raises(AssertionError, raise_error_because_not_equal)
+        assert_equal(np.ppmt(Decimal('0.23') / Decimal('12'), 1, 60, Decimal('10000000000')),
+                     Decimal('-90238044.2322778884413969909'))
 
     def test_ipmt(self):
-        np.round(np.ipmt(0.1/12, 1, 24, 2000), 2) == 16.67
+        assert_almost_equal(np.round(np.ipmt(0.1 / 12, 1, 24, 2000), 2), -16.67)
+
+    def test_ipmt_decimal(self):
+        result = np.ipmt(Decimal('0.1') / Decimal('12'), 1, 24, 2000)
+        assert_equal(result.flat[0], Decimal('-16.66666666666666666666666667'))
 
     def test_nper(self):
         assert_almost_equal(np.nper(0.075, -2000, 0, 100000.),
@@ -70,6 +133,11 @@ def test_npv(self):
             np.npv(0.05, [-15000, 1500, 2500, 3500, 4500, 6000]),
             122.89, 2)
 
+    def test_npv_decimal(self):
+        assert_equal(
+            np.npv(Decimal('0.05'), [-15000, 1500, 2500, 3500, 4500, 6000]),
+            Decimal('122.894854950942692161628715'))
+
     def test_mirr(self):
         val = [-4500, -800, 800, 800, 600, 600, 800, 800, 700, 3000]
         assert_almost_equal(np.mirr(val, 0.08, 0.055), 0.0666, 4)
@@ -83,86 +151,195 @@ def test_mirr(self):
         val = [39000, 30000, 21000, 37000, 46000]
         assert_(np.isnan(np.mirr(val, 0.10, 0.12)))
 
+    def test_mirr_decimal(self):
+        val = [Decimal('-4500'), Decimal('-800'), Decimal('800'), Decimal('800'),
+               Decimal('600'), Decimal('600'), Decimal('800'), Decimal('800'),
+               Decimal('700'), Decimal('3000')]
+        assert_equal(np.mirr(val, Decimal('0.08'), Decimal('0.055')),
+                     Decimal('0.066597175031553548874239618'))
+
+        val = [Decimal('-120000'), Decimal('39000'), Decimal('30000'),
+               Decimal('21000'), Decimal('37000'), Decimal('46000')]
+        assert_equal(np.mirr(val, Decimal('0.10'), Decimal('0.12')), Decimal('0.126094130365905145828421880'))
+
+        val = [Decimal('100'), Decimal('200'), Decimal('-50'),
+               Decimal('300'), Decimal('-200')]
+        assert_equal(np.mirr(val, Decimal('0.05'), Decimal('0.06')), Decimal('0.342823387842176663647819868'))
+
+        val = [Decimal('39000'), Decimal('30000'), Decimal('21000'), Decimal('37000'), Decimal('46000')]
+        assert_(np.isnan(np.mirr(val, Decimal('0.10'), Decimal('0.12'))))
+
     def test_when(self):
-        #begin
-        assert_almost_equal(np.rate(10, 20, -3500, 10000, 1),
-                            np.rate(10, 20, -3500, 10000, 'begin'), 4)
-        #end
-        assert_almost_equal(np.rate(10, 20, -3500, 10000),
-                            np.rate(10, 20, -3500, 10000, 'end'), 4)
-        assert_almost_equal(np.rate(10, 20, -3500, 10000, 0),
-                            np.rate(10, 20, -3500, 10000, 'end'), 4)
+        # begin
+        assert_equal(np.rate(10, 20, -3500, 10000, 1),
+                     np.rate(10, 20, -3500, 10000, 'begin'))
+        # end
+        assert_equal(np.rate(10, 20, -3500, 10000),
+                     np.rate(10, 20, -3500, 10000, 'end'))
+        assert_equal(np.rate(10, 20, -3500, 10000, 0),
+                     np.rate(10, 20, -3500, 10000, 'end'))
 
         # begin
-        assert_almost_equal(np.pv(0.07, 20, 12000, 0, 1),
-                            np.pv(0.07, 20, 12000, 0, 'begin'), 2)
+        assert_equal(np.pv(0.07, 20, 12000, 0, 1),
+                     np.pv(0.07, 20, 12000, 0, 'begin'))
         # end
-        assert_almost_equal(np.pv(0.07, 20, 12000, 0),
-                            np.pv(0.07, 20, 12000, 0, 'end'), 2)
-        assert_almost_equal(np.pv(0.07, 20, 12000, 0, 0),
-                            np.pv(0.07, 20, 12000, 0, 'end'), 2)
+        assert_equal(np.pv(0.07, 20, 12000, 0),
+                     np.pv(0.07, 20, 12000, 0, 'end'))
+        assert_equal(np.pv(0.07, 20, 12000, 0, 0),
+                     np.pv(0.07, 20, 12000, 0, 'end'))
 
         # begin
-        assert_almost_equal(np.fv(0.075, 20, -2000, 0, 1),
-                            np.fv(0.075, 20, -2000, 0, 'begin'), 4)
+        assert_equal(np.fv(0.075, 20, -2000, 0, 1),
+                     np.fv(0.075, 20, -2000, 0, 'begin'))
         # end
-        assert_almost_equal(np.fv(0.075, 20, -2000, 0),
-                            np.fv(0.075, 20, -2000, 0, 'end'), 4)
-        assert_almost_equal(np.fv(0.075, 20, -2000, 0, 0),
-                            np.fv(0.075, 20, -2000, 0, 'end'), 4)
+        assert_equal(np.fv(0.075, 20, -2000, 0),
+                     np.fv(0.075, 20, -2000, 0, 'end'))
+        assert_equal(np.fv(0.075, 20, -2000, 0, 0),
+                     np.fv(0.075, 20, -2000, 0, 'end'))
 
         # begin
-        assert_almost_equal(np.pmt(0.08/12, 5*12, 15000., 0, 1),
-                            np.pmt(0.08/12, 5*12, 15000., 0, 'begin'), 4)
+        assert_equal(np.pmt(0.08 / 12, 5 * 12, 15000., 0, 1),
+                     np.pmt(0.08 / 12, 5 * 12, 15000., 0, 'begin'))
         # end
-        assert_almost_equal(np.pmt(0.08/12, 5*12, 15000., 0),
-                            np.pmt(0.08/12, 5*12, 15000., 0, 'end'), 4)
-        assert_almost_equal(np.pmt(0.08/12, 5*12, 15000., 0, 0),
-                            np.pmt(0.08/12, 5*12, 15000., 0, 'end'), 4)
+        assert_equal(np.pmt(0.08 / 12, 5 * 12, 15000., 0),
+                     np.pmt(0.08 / 12, 5 * 12, 15000., 0, 'end'))
+        assert_equal(np.pmt(0.08 / 12, 5 * 12, 15000., 0, 0),
+                     np.pmt(0.08 / 12, 5 * 12, 15000., 0, 'end'))
 
         # begin
-        assert_almost_equal(np.ppmt(0.1/12, 1, 60, 55000, 0, 1),
-                            np.ppmt(0.1/12, 1, 60, 55000, 0, 'begin'), 4)
+        assert_equal(np.ppmt(0.1 / 12, 1, 60, 55000, 0, 1),
+                     np.ppmt(0.1 / 12, 1, 60, 55000, 0, 'begin'))
         # end
-        assert_almost_equal(np.ppmt(0.1/12, 1, 60, 55000, 0),
-                            np.ppmt(0.1/12, 1, 60, 55000, 0, 'end'), 4)
-        assert_almost_equal(np.ppmt(0.1/12, 1, 60, 55000, 0, 0),
-                            np.ppmt(0.1/12, 1, 60, 55000, 0, 'end'), 4)
+        assert_equal(np.ppmt(0.1 / 12, 1, 60, 55000, 0),
+                     np.ppmt(0.1 / 12, 1, 60, 55000, 0, 'end'))
+        assert_equal(np.ppmt(0.1 / 12, 1, 60, 55000, 0, 0),
+                     np.ppmt(0.1 / 12, 1, 60, 55000, 0, 'end'))
 
         # begin
-        assert_almost_equal(np.ipmt(0.1/12, 1, 24, 2000, 0, 1),
-                            np.ipmt(0.1/12, 1, 24, 2000, 0, 'begin'), 4)
+        assert_equal(np.ipmt(0.1 / 12, 1, 24, 2000, 0, 1),
+                     np.ipmt(0.1 / 12, 1, 24, 2000, 0, 'begin'))
         # end
-        assert_almost_equal(np.ipmt(0.1/12, 1, 24, 2000, 0),
-                            np.ipmt(0.1/12, 1, 24, 2000, 0, 'end'), 4)
-        assert_almost_equal(np.ipmt(0.1/12, 1, 24, 2000, 0, 0),
-                            np.ipmt(0.1/12, 1, 24, 2000, 0, 'end'), 4)
+        assert_equal(np.ipmt(0.1 / 12, 1, 24, 2000, 0),
+                     np.ipmt(0.1 / 12, 1, 24, 2000, 0, 'end'))
+        assert_equal(np.ipmt(0.1 / 12, 1, 24, 2000, 0, 0),
+                     np.ipmt(0.1 / 12, 1, 24, 2000, 0, 'end'))
 
         # begin
-        assert_almost_equal(np.nper(0.075, -2000, 0, 100000., 1),
-                            np.nper(0.075, -2000, 0, 100000., 'begin'), 4)
+        assert_equal(np.nper(0.075, -2000, 0, 100000., 1),
+                     np.nper(0.075, -2000, 0, 100000., 'begin'))
         # end
-        assert_almost_equal(np.nper(0.075, -2000, 0, 100000.),
-                            np.nper(0.075, -2000, 0, 100000., 'end'), 4)
-        assert_almost_equal(np.nper(0.075, -2000, 0, 100000., 0),
-                            np.nper(0.075, -2000, 0, 100000., 'end'), 4)
+        assert_equal(np.nper(0.075, -2000, 0, 100000.),
+                     np.nper(0.075, -2000, 0, 100000., 'end'))
+        assert_equal(np.nper(0.075, -2000, 0, 100000., 0),
+                     np.nper(0.075, -2000, 0, 100000., 'end'))
+
+    def test_decimal_with_when(self):
+        """Test that decimals are still supported if the when argument is passed"""
+        # begin
+        assert_equal(np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), Decimal('1')),
+                     np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'begin'))
+        # end
+        assert_equal(np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000')),
+                     np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'end'))
+        assert_equal(np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), Decimal('0')),
+                     np.rate(Decimal('10'), Decimal('20'), Decimal('-3500'), Decimal('10000'), 'end'))
+
+        # begin
+        assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), Decimal('1')),
+                     np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'begin'))
+        # end
+        assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0')),
+                     np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'end'))
+        assert_equal(np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), Decimal('0')),
+                     np.pv(Decimal('0.07'), Decimal('20'), Decimal('12000'), Decimal('0'), 'end'))
+
+        # begin
+        assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), Decimal('1')),
+                     np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'begin'))
+        # end
+        assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0')),
+                     np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'end'))
+        assert_equal(np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), Decimal('0')),
+                     np.fv(Decimal('0.075'), Decimal('20'), Decimal('-2000'), Decimal('0'), 'end'))
+
+        # begin
+        assert_equal(np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'),
+                            Decimal('0'), Decimal('1')),
+                     np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'),
+                            Decimal('0'), 'begin'))
+        # end
+        assert_equal(np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'),
+                            Decimal('0')),
+                     np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'),
+                            Decimal('0'), 'end'))
+        assert_equal(np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'),
+                            Decimal('0'), Decimal('0')),
+                     np.pmt(Decimal('0.08') / Decimal('12'), Decimal('5') * Decimal('12'), Decimal('15000.'),
+                            Decimal('0'), 'end'))
+
+        # begin
+        assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'),
+                             Decimal('0'), Decimal('1')),
+                     np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'),
+                             Decimal('0'), 'begin'))
+        # end
+        assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'),
+                             Decimal('0')),
+                     np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'),
+                             Decimal('0'), 'end'))
+        assert_equal(np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'),
+                             Decimal('0'), Decimal('0')),
+                     np.ppmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('60'), Decimal('55000'),
+                             Decimal('0'), 'end'))
+
+        # begin
+        assert_equal(np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'),
+                             Decimal('0'), Decimal('1')).flat[0],
+                     np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'),
+                             Decimal('0'), 'begin').flat[0])
+        # end
+        assert_equal(np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'),
+                             Decimal('0')).flat[0],
+                     np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'),
+                             Decimal('0'), 'end').flat[0])
+        assert_equal(np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'),
+                             Decimal('0'), Decimal('0')).flat[0],
+                     np.ipmt(Decimal('0.1') / Decimal('12'), Decimal('1'), Decimal('24'), Decimal('2000'),
+                             Decimal('0'), 'end').flat[0])
 
     def test_broadcast(self):
         assert_almost_equal(np.nper(0.075, -2000, 0, 100000., [0, 1]),
                             [21.5449442, 20.76156441], 4)
 
-        assert_almost_equal(np.ipmt(0.1/12, list(range(5)), 24, 2000),
+        assert_almost_equal(np.ipmt(0.1 / 12, list(range(5)), 24, 2000),
                             [-17.29165168, -16.66666667, -16.03647345,
-                                -15.40102862, -14.76028842], 4)
+                             -15.40102862, -14.76028842], 4)
 
-        assert_almost_equal(np.ppmt(0.1/12, list(range(5)), 24, 2000),
+        assert_almost_equal(np.ppmt(0.1 / 12, list(range(5)), 24, 2000),
                             [-74.998201, -75.62318601, -76.25337923,
-                                -76.88882405, -77.52956425], 4)
+                             -76.88882405, -77.52956425], 4)
 
-        assert_almost_equal(np.ppmt(0.1/12, list(range(5)), 24, 2000, 0,
+        assert_almost_equal(np.ppmt(0.1 / 12, list(range(5)), 24, 2000, 0,
                                     [0, 0, 1, 'end', 'begin']),
                             [-74.998201, -75.62318601, -75.62318601,
-                                -76.88882405, -76.88882405], 4)
+                             -76.88882405, -76.88882405], 4)
+
+    def test_broadcast_decimal(self):
+        # Use almost equal because precision is tested in the explicit tests, this test is to ensure
+        # broadcast with Decimal is not broken.
+        assert_almost_equal(np.ipmt(Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000')),
+                            [Decimal('-17.29165168'), Decimal('-16.66666667'), Decimal('-16.03647345'),
+                             Decimal('-15.40102862'), Decimal('-14.76028842')], 4)
+
+        assert_almost_equal(np.ppmt(Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000')),
+                            [Decimal('-74.998201'), Decimal('-75.62318601'), Decimal('-76.25337923'),
+                             Decimal('-76.88882405'), Decimal('-77.52956425')], 4)
+
+        assert_almost_equal(np.ppmt(Decimal('0.1') / Decimal('12'), list(range(5)), Decimal('24'), Decimal('2000'),
+                                    Decimal('0'), [Decimal('0'), Decimal('0'), Decimal('1'), 'end', 'begin']),
+                            [Decimal('-74.998201'), Decimal('-75.62318601'), Decimal('-75.62318601'),
+                             Decimal('-76.88882405'), Decimal('-76.88882405')], 4)
+
 
 if __name__ == "__main__":
     run_module_suite()