@@ -46,6 +46,23 @@ def _convert_when(when):
4646 return [_when_to_num [x ] for x in when ]
4747
4848
49+ def _return_ufunc_like (array ):
50+ try :
51+ # If size of array is one, return scalar
52+ return array .item ()
53+ except ValueError :
54+ # Otherwise, return entire array
55+ return array
56+
57+
58+ def _is_object_array (array ):
59+ return array .dtype == np .dtype ("O" )
60+
61+
62+ def _use_decimal_dtype (* arrays ):
63+ return any (_is_object_array (array ) for array in arrays )
64+
65+
4966def fv (rate , nper , pmt , pv , when = 'end' ):
5067 """Compute the future value.
5168
@@ -825,6 +842,15 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
825842 return np .nan
826843
827844
845+ def _to_decimal_array_1d (array ):
846+ return np .array ([Decimal (x ) for x in array .tolist ()])
847+
848+
849+ def _to_decimal_array_2d (array ):
850+ l = [Decimal (x ) for row in array .tolist () for x in row ]
851+ return np .array (l ).reshape (array .shape )
852+
853+
828854def npv (rate , values ):
829855 r"""Return the NPV (Net Present Value) of a cash flow series.
830856
@@ -892,15 +918,36 @@ def npv(rate, values):
892918 3065.22267
893919
894920 """
921+ rates = np .atleast_1d (rate )
895922 values = np .atleast_2d (values )
896- timestep_array = np .arange (0 , values .shape [1 ])
897- npv = (values / (1 + rate ) ** timestep_array ).sum (axis = 1 )
898- try :
899- # If size of array is one, return scalar
900- return npv .item ()
901- except ValueError :
902- # Otherwise, return entire array
903- return npv
923+
924+ if rates .ndim != 1 :
925+ msg = "invalid shape for rates. Rate must be either a scalar or 1d array"
926+ raise ValueError (msg )
927+
928+ if values .ndim != 2 :
929+ msg = "invalid shape for values. Values must be either a 1d or 2d array"
930+ raise ValueError (msg )
931+
932+ dtype = Decimal if _use_decimal_dtype (rates , values ) else np .float64
933+
934+ if dtype == Decimal :
935+ rates = _to_decimal_array_1d (rates )
936+ values = _to_decimal_array_2d (values )
937+ zero = dtype ("0.0" )
938+ one = dtype ("1.0" )
939+
940+ shape = tuple (array .shape [0 ] for array in (rates , values ))
941+ out = np .empty (shape = shape , dtype = dtype )
942+
943+ for i in range (rates .shape [0 ]):
944+ for j in range (values .shape [0 ]):
945+ acc = zero
946+ for t in range (values .shape [1 ]):
947+ acc += values [j , t ] / ((one + rates [i ]) ** t )
948+ out [i , j ] = acc
949+
950+ return _return_ufunc_like (out )
904951
905952
906953def mirr (values , finance_rate , reinvest_rate , * , raise_exceptions = False ):
0 commit comments