x=1;9.times{x=x-(n=d=t=0;i.map{|e|n+=_=e*x**t-=1;d+=t*_/x};n/d)};x-1
Well, my solution is slightly more verbose. Which doesn't imply it
yields better results. It uses the Secant method (which no other
solution has used yet, ha!) and the code in the core method was
shamelessly copied from wikipedia. Yeah! By default, my solution tries
to find values that converge with with Float::EPSILON. If the values
diverge, the interval is scanned for zero-values, which could also be
done in the first place (since this allows to find multiple roots).
Regards,
Thomas.
#!/usr/bin/env ruby19
require 'date'
module IRR
module_function
# Use Secant method to find the roots. In case, the upper and
lower
# limit diverge, reset the start values.
#
# This method may miss certain IRRs outside the [0.0..1.0]
# intervall. In such a case use #irrer or set the optional :a
# (lower limit) and :b (upper limit) arguments.
#
# Based on:
#
http://en.wikipedia.org/w/index.php?title=Secant_method
def irr(values, args={})
a = a0 = args[:a] || 0.0
b = b0 = args[:b] || 1.0
n = args[:n] || 100
e = args[:e] || Float::EPSILON
c0 = (a - b).abs
ab = nil
n.times do
fa = npv(values, a)
fb = npv(values, b)
c = (a - b) / (fa - fb) * fa;
if c.nan?
does_not_compute(values)
elsif c.infinite?
# break
return c
elsif c.abs < e
return a
end
# Protect against bad start values.
if c.abs > c0
ab ||= guess_start_values(values, args.merge
min =>
a0, :max => b0))
if !ab.empty?
a, b, _ = ab.shift
c0 = (a - b).abs
next
end
end
b = a
a = a - c
c0 = c.abs
end
does_not_compute(values)
end
# Guess appropriate start values, return all solutions as Array.
def irrer(values, args={})
guess_start_values(values, args).map do |a, b|
irr(values, args.merge
a => a, :b => b))
end
end
# Calculate the NPV for a hypothetical IRR.
# Values are either an array of cash flows or of pairs [cash,
# date or days].
def npv(values, irr)
sum = 0
d0 = nil
values.each_with_index do |(v, d), t|
# I have no idea if this is the right way to deal with
# irregular time series.
if d
if d0
t = (d - d0).to_f / 365.25
else
d0 = d
end
end
sum += v / (1 + irr) ** t
end
sum
end
def does_not_compute(values)
raise RuntimeError, %{Does not compute: %s} % values.inspect
end
# Check whether computation will converge easily.
def check_values(values)
csgn = 0
val, dat = values[-1]
values.reverse.each do |v, d|
csgn += 1 if val * v < 0
val = v
end
return csgn == 1
end
# Try to find appropriate start values.
def guess_start_values(values, args={})
min = args[:min] || -1.0
max = args[:max] || 2.0
delta = args[:delta] || (max - min).to_f / (args[:steps] ||
100)
vals = []
b, fb = nil
# The NPV is undefined for IRR < -100% or so they say.
min.step(max, delta) do |a|
fa = npv(values, a)
if fb and !fa.infinite? and !fb.infinite? and fa * fb < 0
vals << [b, a]
end
b = a
fb = fa
end
return vals
end
end
if __FILE__ == $0
values = ARGV.map do |e|
v, d = e.split(/,/)
v = v.to_f
d ? [v, Date.parse(d)] : v
end
puts "Default solution: #{IRR.irr(values) rescue puts $!.message}"
begin
IRR.irrer(values).zip(IRR.guess_start_values(values)) do |irr,
(a, b)|
puts '[%5.2f..%5.2f] %13.10f -> %13.10f' % [a, b, irr,
IRR.npv(values, irr)]
end
rescue RuntimeError => e
puts e.message
end
puts "Possibly incorrect IRR value(s)" unless
IRR.check_values(values)
end