# Re: Internal Rate of Return (#156)

**URL:** <https://www.ruby-forum.com/t/re-internal-rate-of-return-156/128691>\
**Category:** Ruby\
**Created:** [February 10, 2008, 5:29pm UTC](https://www.ruby-forum.com/t/re-internal-rate-of-return-156/128691 "2008-02-10T17:29:39Z")\
**Posts on this page:** 2\
**Page:** 1

<div class="post-metadata">

**Author:** ![jameskelly](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/3ab097/32.png) [@jameskelly](https://www.ruby-forum.com/u/jameskelly)\
**Post date:** [February 10, 2008, 5:29pm UTC](https://www.ruby-forum.com/t/re-internal-rate-of-return-156/128691/1 "2008-02-10T17:29:39Z")

</div>

Here’s my solution. I used Newton’s method to quickly find the zero of  
the NPV function.

Unfortunately, my calculus book failed to mention that Newton’s method  
will fail to converge when the function enters a horizontal asymptote if  
it’s moving toward y=0 as it does so, which happens in this function at  
deeply negative values of IRR. Thus, my solution can be a bit  
unreliable, especially for negative values of IRR.

Anyway, here’s the code:

DX = 1e-4  
EPSILON = 1e-7

def nderiv(f)  
lambda {|x| (f[x+DX]-f[x-DX])/(2\*DX)}  
end

MAX\_ITERATIONS = 500  
def find\_zero(f, start)  
iterations = 0  
f\_prime = nderiv(f)  
x = start  
until f[x].abs \< EPSILON or (iterations+=1) \> MAX\_ITERATIONS  
x = x - f[x]/f\_prime[x]  
end  
if iterations \> MAX\_ITERATIONS  
nil  
else  
x  
end  
end

def irr(cash\_flows)  
net\_value = lambda do |irr|  
(0…cash\_flows.length).to\_a.inject(0) do |s,t|  
s+cash\_flows[t]/((1+irr)\*\*t)  
end  
end

find\_zero(net\_value,0.1) or find\_zero(net\_value,-0.1)  
end

----- Original Message ----  
From: Ruby Q. [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
To: ruby-talk ML [removed\_email\_address@domain.invalid](mailto:removed_email_address@domain.invalid)  
Sent: Friday, February 8, 2008 8:01:13 AM  
Subject: [QUIZ] Internal Rate of Return (#156)

The  
three  
rules  
of  
Ruby  
Quiz:

1. 

Please  
do  
not  
post  
any  
solutions  
or  
spoiler  
discussion  
for  
this  
quiz  
until  
48  
hours  
have  
passed  
from  
the  
time  
on  
this  
message.

1. 

Support  
Ruby  
Quiz  
by  
submitting  
ideas  
as  
often  
as  
you  
can:

[http://www.rubyquiz.com/](http://www.rubyquiz.com/)

1. 

Enjoy!

Suggestion:  
A  
[QUIZ]  
in  
the  
subject  
of  
emails  
about  
the  
problem  
helps  
everyone  
on  
Ruby  
Talk  
follow  
the  
discussion.  
Please  
reply  
to  
the  
original  
quiz  
message,  
if  
you  
can.

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

by  
Harrison  
Reiser

Internal  
Rate  
of  
Return  
(IRR  
â€“  
[Internal rate of return - Wikipedia](http://en.wikipedia.org/wiki/Internal_rate_of_return))  
is  
a  
common  
financial  
metric,  
used  
by  
investment  
firms  
to  
predict  
the  
profitability  
of  
a  
company  
or  
project.  
Finding  
the  
IRR  
of  
a  
company  
amounts  
to  
solving  
for  
it  
in  
the  
equation  
for  
Net  
Present  
Value  
(NPV  
â€“  
[Net present value - Wikipedia](http://en.wikipedia.org/wiki/Net_present_value)),  
another  
valuable  
decision-making  
metric:

N

C\_t

# NPV

## Î£

t=0  
(1  
+  
IRR)\*\*t

This  
week’s  
quiz  
is  
to  
calculate  
the  
IRR  
for  
any  
given  
variable-length  
list  
of  
numbers,  
which  
represent  
yearly  
cash  
flows,  
the  
C\_t’s  
in  
the  
formula  
above:  
C\_0,  
C\_1,  
etc.  
(C\_0  
is  
typically  
a  
negative  
value,  
corresponding  
to  
the  
initial  
investment  
into  
the  
project.)  
From  
the  
example  
in  
the  
Wikipedia  
article  
([Internal rate of return - Wikipedia](http://en.wikipedia.org/wiki/Internal_rate_of_return)),  
for  
instance,  
you  
should  
be  
able  
to  
produce  
a  
rate  
of  
17.09%  
(to  
four  
decimal  
places,  
let’s  
say)  
from  
this  
or  
a  
similar  
command:

irr([-100,+30,+35,+40,+45])

=\>  
0.1709…

Keep  
in  
mind  
that  
an  
IRR  
greater  
than  
100%  
is  
possible.  
Extra  
credit  
if  
you  
can  
also  
correctly  
handle  
input  
that  
produces  
negative  
rates,  
disregarding  
the  
fact  
that  
they  
make  
no  
sense.

```
  ____________________________________________________________________________________

```

Be a better friend, newshound, and  
know-it-all with Yahoo! Mobile. Try it now.  
[http://mobile.yahoo.com/;\_ylt=Ahu06i62sR8HDtDypao8Wcj9tAcJ](http://mobile.yahoo.com/;_ylt=Ahu06i62sR8HDtDypao8Wcj9tAcJ)

---

<div class="post-metadata">

**Author:** ![jameskelly](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/j/3ab097/32.png) [@jameskelly](https://www.ruby-forum.com/u/jameskelly)\
**Post date:** [February 10, 2008, 5:54pm UTC](https://www.ruby-forum.com/t/re-internal-rate-of-return-156/128691/2 "2008-02-10T17:54:22Z")

</div>

Here’s my solution. Like others, it uses Newton’s method. It may have  
problems because it always uses zero as its initial estimate, though  
all the test data I’ve thrown at it seems to work properly. The one  
thing that is different than the other solutions I’ve seen posted is  
that I wrapped everything up in a class. This is also my first quiz.

class IncomeStream  
DEFAULT\_TOLERANCE = 0.000\_000\_05

def self.irr(\*flows)  
self.new(flows).irr  
end

def self.npv(discount\_rate, \*flows)  
self.new(flows).npv(discount\_rate)  
end

def self.dpv(amount, time)  
lambda {|rate| (-time) \* amount \* (1+rate)\*\*(-time-1)}  
end

def self.pv(amount, time)  
lambda {|rate| amount \* (1+rate)\*\*(-time)}  
end

def initialize(flows)  
@flows = flows.to\_a  
if (not @flows.empty?) and (@flows.first.kind\_of? Numeric)  
@flows.each\_index {|idx| @flows[idx]=[@flows[idx],idx]}  
end  
@pvs = @flows.map {|amount, time| IncomeStream.pv(amount, time)}  
@dpvs = @flows.map {|amount, time| IncomeStream.dpv(amount, time)}  
end

def npv(rate)  
@pvs.inject(0) {|npv, item| npv+item.call(rate)}  
end

def dnpv(rate)  
@dpvs.inject(0) {|dnpv, item| dnpv+item.call(rate)}  
end

def irr(tolerance=DEFAULT\_TOLERANCE)  
return nil if @flows.empty? or @flows.all? {|amount, time| amount =  
0}  
rate = 0.0  
while (((n=npv(rate)).abs \> tolerance) and rate.finite?)  
d = dnpv(rate)  
rate -= n/d  
end  
rate  
end
