time_value 0.1.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/solver.rb +23 -13
- data/lib/time_value.rb +48 -49
- data/lib/time_value/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0c6e6b94a394a16cbd8e93aa1af3110fccff9377
|
4
|
+
data.tar.gz: 032d98858c34aa563b7d1e7a1407c9fefd6673d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db23d74db7237412e2bc019cfa4859b1239db26d32fabcb1bdd4ebd3dc7b789d7916e5f93d9702fe92f877ad6b26b106e37d95ca77346fe24d8b7c65c7d03be4
|
7
|
+
data.tar.gz: 2561d85d74677bbc4a3e77d37716bc8573219e5fb047494e4d632a00e51402b567b6544232b84f3cb7bf41782de4a4d4ecc9240dee7268767045ee5c48696dd0
|
data/lib/solver.rb
CHANGED
@@ -3,20 +3,19 @@ class Solver
|
|
3
3
|
INTERVAL = 10 ** (-1 * PRECISION)
|
4
4
|
MAX_ITERATIONS = 20
|
5
5
|
attr_reader :time_value, :goal
|
6
|
-
attr_accessor :
|
6
|
+
attr_accessor :lower_bound, :upper_bound, :upper_cap_met
|
7
7
|
|
8
|
-
def initialize(time_value
|
9
|
-
@upper_bound = upper_bound
|
8
|
+
def initialize(time_value:, lower_bound: 0.00, upper_bound: nil, guess: 0.00)
|
9
|
+
@upper_bound = upper_bound || guess
|
10
10
|
@lower_bound = lower_bound
|
11
|
-
@
|
12
|
-
@time_value =
|
11
|
+
@time_value = time_value.dup
|
12
|
+
@time_value.i = guess
|
13
13
|
@goal = time_value.fv
|
14
14
|
@upper_cap_met = false
|
15
15
|
end
|
16
16
|
|
17
17
|
def solve!
|
18
18
|
iteration_count = 0
|
19
|
-
time_value.i = guess
|
20
19
|
while (upper_bound - lower_bound).round(PRECISION) > INTERVAL &&
|
21
20
|
iteration_count < MAX_ITERATIONS
|
22
21
|
iteration_count += 1
|
@@ -25,22 +24,33 @@ class Solver
|
|
25
24
|
rescue FloatDomainError
|
26
25
|
return nil
|
27
26
|
end
|
28
|
-
adjust_bounds(result)
|
29
|
-
time_value.i =
|
27
|
+
adjust_bounds!(result)
|
28
|
+
time_value.i = new_guess
|
30
29
|
end
|
31
|
-
|
30
|
+
# TODO: This will not handle the case where the 20th iteration
|
31
|
+
# finds the solution
|
32
|
+
rate if iteration_count < MAX_ITERATIONS
|
32
33
|
end
|
33
34
|
|
34
|
-
|
35
|
+
private
|
36
|
+
|
37
|
+
def adjust_bounds!(result)
|
35
38
|
if result > goal
|
36
39
|
# interest rate too high
|
37
40
|
self.upper_cap_met = true
|
38
|
-
self.upper_bound =
|
41
|
+
self.upper_bound = rate
|
39
42
|
elsif result < goal
|
40
43
|
# interest rate too low
|
41
44
|
self.upper_bound *= 2 unless upper_cap_met
|
42
|
-
self.lower_bound =
|
45
|
+
self.lower_bound = rate
|
43
46
|
end
|
44
|
-
|
47
|
+
end
|
48
|
+
|
49
|
+
def new_guess
|
50
|
+
((upper_bound + lower_bound) / 2).round(PRECISION)
|
51
|
+
end
|
52
|
+
|
53
|
+
def rate
|
54
|
+
time_value.i.round(2)
|
45
55
|
end
|
46
56
|
end
|
data/lib/time_value.rb
CHANGED
@@ -1,53 +1,52 @@
|
|
1
1
|
require 'solver'
|
2
2
|
|
3
3
|
class TimeValue
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
4
|
+
attr_accessor :n, :i, :pv, :pmt, :fv
|
5
|
+
|
6
|
+
def initialize(n: 0, i: 0, pv: 0.0, pmt: 0.0, fv: 0.0)
|
7
|
+
@n = n
|
8
|
+
@i = i.to_f
|
9
|
+
@pv = pv.to_f
|
10
|
+
@pmt = pmt.to_f
|
11
|
+
@fv = fv.to_f
|
12
|
+
end
|
13
|
+
|
14
|
+
#Base formula, ordinary annuity: -PV = [FV/((1+i)^n)] + (PMT/i)*[1-(1/(1+i)^n)]
|
15
|
+
def calc_pv()
|
16
|
+
i = @i / 100.0
|
17
|
+
#Initial contribution
|
18
|
+
pvf = @fv / ((1 + i) ** @n)
|
19
|
+
#Present value of annuity
|
20
|
+
pva = (@pmt/i) * (1-(1/((1+i)**@n)))
|
21
|
+
@pv = (pvf + pva) * (-1)
|
22
|
+
#Round
|
23
|
+
@pv = (@pv*100).round / 100.0
|
24
|
+
end
|
25
|
+
|
26
|
+
def calc_fv()
|
27
|
+
i = @i / 100.0
|
28
|
+
#Growth of initial contribution
|
29
|
+
fvp = @pv * ((1 + i) ** @n)
|
30
|
+
#Growth of payments
|
31
|
+
fva = @pmt * (((1 + i) ** @n)-1) / i
|
32
|
+
@fv = (fvp + fva) * (-1)
|
33
|
+
#Round
|
34
|
+
@fv = (@fv*100).round / 100.0
|
35
|
+
end
|
36
|
+
|
37
|
+
def calc_n()
|
38
|
+
i = @i / 100.0
|
39
|
+
@n = (Math.log((@pmt - (i * @fv))/(@pmt + (i * @pv)))/Math.log(1 + i)).round(0)
|
40
|
+
end
|
41
|
+
|
42
|
+
def calc_pmt()
|
43
|
+
i = @i / 100.0
|
44
|
+
@pmt = (-i*(@fv+(@pv*((1+i)**n))))/(((1+i)**n)-1)
|
45
|
+
@pmt = (@pmt*100).round / 100.0
|
46
|
+
end
|
47
|
+
|
48
|
+
def calc_i(guess: 10.0)
|
49
|
+
solver = Solver.new(time_value: self, lower_bound: 0.00, guess: 10.00)
|
50
|
+
solver.solve!
|
51
|
+
end
|
53
52
|
end
|
data/lib/time_value/version.rb
CHANGED