time_value 0.1.2 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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