@@ -46,6 +46,23 @@ def _convert_when(when):
46
46
return [_when_to_num [x ] for x in when ]
47
47
48
48
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
+
49
66
def fv (rate , nper , pmt , pv , when = 'end' ):
50
67
"""Compute the future value.
51
68
@@ -825,6 +842,15 @@ def irr(values, *, guess=None, tol=1e-12, maxiter=100, raise_exceptions=False):
825
842
return np .nan
826
843
827
844
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
+
828
854
def npv (rate , values ):
829
855
r"""Return the NPV (Net Present Value) of a cash flow series.
830
856
@@ -892,15 +918,36 @@ def npv(rate, values):
892
918
3065.22267
893
919
894
920
"""
921
+ rates = np .atleast_1d (rate )
895
922
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 )
904
951
905
952
906
953
def mirr (values , finance_rate , reinvest_rate , * , raise_exceptions = False ):
0 commit comments