time_value 1.0.0 → 1.1.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 +17 -15
- data/lib/time_value.rb +42 -32
- 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: 8cb8d84e2f74389800bec8f0d5bf9c8858dbecf5
|
4
|
+
data.tar.gz: 009f75557b934dd9cf8bd71ad9b8d83db6889d29
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea2d1631b57c1d0b95df5886981c312c8c1bc2ed7a0a704b4c3e24391e7ab7e3d1074656347e1dd321b224746756f67f3cd5cf9c7060172dab0e67ba9c5b59c9
|
7
|
+
data.tar.gz: 95e053b40b686c0d961de9991648b448b810fe0f369536ba61716f99bb159aa8bcce1ccf7d37af7a926009677cb76b53a7d7309e20ab2700288673875644fa77
|
data/lib/solver.rb
CHANGED
@@ -1,40 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
class Solver
|
2
3
|
PRECISION = 3
|
3
|
-
INTERVAL = 10
|
4
|
+
INTERVAL = 10**(-1 * PRECISION)
|
4
5
|
MAX_ITERATIONS = 20
|
5
6
|
attr_reader :time_value, :goal
|
6
|
-
attr_accessor :lower_bound, :upper_bound, :upper_cap_met
|
7
|
+
attr_accessor :lower_bound, :upper_bound, :upper_cap_met, :iteration_count
|
7
8
|
|
8
|
-
def initialize(time_value:, lower_bound: 0.00, upper_bound: nil, guess:
|
9
|
+
def initialize(time_value:, lower_bound: 0.00, upper_bound: nil, guess: 10.00)
|
9
10
|
@upper_bound = upper_bound || guess
|
10
11
|
@lower_bound = lower_bound
|
11
12
|
@time_value = time_value.dup
|
12
13
|
@time_value.i = guess
|
13
14
|
@goal = time_value.fv
|
14
15
|
@upper_cap_met = false
|
16
|
+
@iteration_count = 0
|
15
17
|
end
|
16
18
|
|
17
19
|
def solve!
|
18
|
-
iteration_count
|
19
|
-
|
20
|
-
|
21
|
-
iteration_count += 1
|
22
|
-
begin
|
23
|
-
result = time_value.calc_fv
|
24
|
-
rescue FloatDomainError
|
25
|
-
return nil
|
26
|
-
end
|
27
|
-
adjust_bounds!(result)
|
20
|
+
while !within_range? && iteration_count < MAX_ITERATIONS
|
21
|
+
result = time_value.calc_fv
|
22
|
+
adjust_bounds!(result: result)
|
28
23
|
time_value.i = new_guess
|
24
|
+
self.iteration_count += 1
|
29
25
|
end
|
30
26
|
# TODO: This will not handle the case where the 20th iteration
|
31
27
|
# finds the solution
|
32
|
-
rate if iteration_count < MAX_ITERATIONS
|
28
|
+
return rate if iteration_count < MAX_ITERATIONS
|
29
|
+
rescue FloatDomainError
|
30
|
+
return nil
|
33
31
|
end
|
34
32
|
|
35
33
|
private
|
36
34
|
|
37
|
-
def
|
35
|
+
def within_range?
|
36
|
+
(upper_bound - lower_bound).round(PRECISION) <= INTERVAL
|
37
|
+
end
|
38
|
+
|
39
|
+
def adjust_bounds!(result:)
|
38
40
|
if result > goal
|
39
41
|
# interest rate too high
|
40
42
|
self.upper_cap_met = true
|
data/lib/time_value.rb
CHANGED
@@ -1,51 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require 'solver'
|
2
3
|
|
3
4
|
class TimeValue
|
4
5
|
attr_accessor :n, :i, :pv, :pmt, :fv
|
5
6
|
|
6
7
|
def initialize(n: 0, i: 0, pv: 0.0, pmt: 0.0, fv: 0.0)
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
@n = n
|
9
|
+
@i = i.to_f
|
10
|
+
@pv = pv.to_f
|
11
|
+
@pmt = pmt.to_f
|
12
|
+
@fv = fv.to_f
|
12
13
|
end
|
13
14
|
|
14
|
-
#Base formula, ordinary annuity: -PV = [FV/((1+i)^n)] +
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
15
|
+
# Base formula, ordinary annuity: -PV = [FV/((1+i)^n)] +
|
16
|
+
# (PMT/i)*[1-(1/(1+i)^n)]
|
17
|
+
# rubocop:disable Metrics/AbcSize
|
18
|
+
def calc_pv
|
19
|
+
i = @i / 100.0
|
20
|
+
# Initial contribution
|
21
|
+
pvf = @fv / ((1 + i)**@n)
|
22
|
+
# Present value of annuity
|
23
|
+
pva = (@pmt / i) * (1 - (1 / ((1 + i)**@n)))
|
24
|
+
@pv = -1 * (pvf + pva)
|
25
|
+
# Round
|
26
|
+
@pv = (@pv * 100).round / 100.0
|
24
27
|
end
|
28
|
+
# rubocop:enable Metrics/AbcSize
|
25
29
|
|
26
|
-
def calc_fv
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
def calc_fv
|
31
|
+
i = @i / 100.0
|
32
|
+
# Growth of initial contribution
|
33
|
+
fvp = @pv * ((1 + i)**@n)
|
34
|
+
# Growth of payments
|
35
|
+
fva = @pmt * (((1 + i)**@n) - 1) / i
|
36
|
+
@fv = -1 * (fvp + fva)
|
37
|
+
# Round
|
38
|
+
@fv = (@fv * 100).round / 100.0
|
35
39
|
end
|
36
40
|
|
37
|
-
def calc_n
|
38
|
-
|
39
|
-
|
41
|
+
def calc_n
|
42
|
+
i = @i / 100.0
|
43
|
+
numerator = Math.log((@pmt - (i * @fv)) / (@pmt + (i * @pv)))
|
44
|
+
denominator = Math.log(1 + i)
|
45
|
+
@n = (numerator / denominator).round(0)
|
40
46
|
end
|
41
47
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
48
|
+
# rubocop:disable Metrics/AbcSize
|
49
|
+
def calc_pmt
|
50
|
+
i = @i / 100.0
|
51
|
+
numerator = (-i * (@fv + (@pv * ((1 + i)**n))))
|
52
|
+
denominator = (((1 + i)**n) - 1)
|
53
|
+
@pmt = numerator / denominator
|
54
|
+
@pmt = (@pmt * 100).round / 100.0
|
46
55
|
end
|
56
|
+
# rubocop:enable Metrics/AbcSize
|
47
57
|
|
48
|
-
def calc_i
|
58
|
+
def calc_i
|
49
59
|
solver = Solver.new(time_value: self, lower_bound: 0.00, guess: 10.00)
|
50
60
|
solver.solve!
|
51
61
|
end
|
data/lib/time_value/version.rb
CHANGED