Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bigdecimal, optimization, fixed rounding #2

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
3 changes: 2 additions & 1 deletion .rvmrc
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
rvm_gemset_create_on_use_flag=1
rvm gemset use monetico
rvm gemset use monetico
rvm use 1.9.3@monetico --create
3 changes: 3 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ source 'http://rubygems.org'

# Specify your gem's dependencies in monetico.gemspec
gemspec

group :development, :test do
end
36 changes: 36 additions & 0 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,2 +1,38 @@
# encoding: utf-8

#!/usr/bin/env rake
require "bundler/gem_tasks"

require 'rubygems'
require 'bundler'
begin
Bundler.setup(:default, :development)
rescue Bundler::BundlerError => e
$stderr.puts e.message
$stderr.puts "Run `bundle install` to install missing gems"
exit e.status_code
end
require 'rake'

require 'rspec/core'
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |spec|
spec.pattern = FileList['spec/**/*_spec.rb']
end

RSpec::Core::RakeTask.new(:rcov) do |spec|
spec.pattern = 'spec/**/*_spec.rb'
spec.rcov = true
end

task :default => :spec

require 'rake/rdoctask'
Rake::RDocTask.new do |rdoc|
version = File.exist?('VERSION') ? File.read('VERSION') : ""

rdoc.rdoc_dir = 'rdoc'
rdoc.title = "simple_metar_parser #{version}"
rdoc.rdoc_files.include('README*')
rdoc.rdoc_files.include('lib/**/*.rb')
end
35 changes: 35 additions & 0 deletions lib/excel.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
module Excel
def pmt(rate, nper, pv, fv=0, type=0)
((-pv * pvif(rate, nper) - fv ) / ((1.0 + rate * type) * fvifa(rate, nper)))
end

def ipmt(rate, per, nper, pv, fv=0, type=0)
p = pmt(rate, nper, pv, fv, 0);
ip = -(pv * pow1p(rate, per - 1) * rate + p * pow1pm1(rate, per - 1))
(type == 0) ? ip : ip / (1 + rate)
end

def ppmt(rate, per, nper, pv, fv=0, type=0)
p = pmt(rate, nper, pv, fv, type)
ip = ipmt(rate, per, nper, pv, fv, type)
p - ip
end

protected

def pow1pm1(x, y)
(x <= -1) ? ((1 + x) ** y) - 1 : Math.exp(y * Math.log(1.0 + x)) - 1
end

def pow1p(x, y)
(x.abs > 0.5) ? ((1 + x) ** y) : Math.exp(y * Math.log(1.0 + x))
end

def pvif(rate, nper)
pow1p(rate, nper)
end

def fvifa(rate, nper)
(rate == 0) ? nper : pow1pm1(rate, nper) / rate
end
end
23 changes: 11 additions & 12 deletions lib/monetico.rb
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
require "bigdecimal"
require "excel"
require "monetico/money_array"
require "monetico/calculable"
require "monetico/loan"
require "monetico/version"

class Float
def big; BigDecimal(self.to_s); end
include Monetico::Calculable
end

def round_to(x)
(self * 10**x).ceil.to_f / 10**x
end
class BigDecimal
include Monetico::Calculable
end

def round_down(x)
if self >= 0
(self * 10**x).floor.to_f / 10**x
else
-((-self * 10**x).floor.to_f / 10**x)
end
end
class Fixnum
# usable for #big
include Monetico::Calculable
end

module Monetico
# Your code goes here...
end
25 changes: 25 additions & 0 deletions lib/monetico/calculable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Monetico
module Calculable
DEF_PREC = 2

def big
BigDecimal(self.to_s);
end

alias_method :to_big, :big

def round_to(x = DEF_PREC)
(self * 10**x).ceil.to_f / 10**x
end

alias_method :round_up, :round_to

def round_down(x = DEF_PREC)
if self >= 0
(self * 10**x).floor.to_f / 10**x
else
-((-self * 10**x).floor.to_f / 10**x)
end
end
end
end
198 changes: 178 additions & 20 deletions lib/monetico/loan.rb
Original file line number Diff line number Diff line change
@@ -1,57 +1,215 @@
module Monetico
class Loan
include Excel

CADENCE = {
monthly: 12,
weekly: 52,
}

def initialize(amount, interest_rate, no_installments, cadence=:monthly, kind=:desc)
# methods
# <ex. capital_real> - rounded
# <ex. capital_real_real> - float, used in internal calculations
# <ex. calculate_capital> - calculate for the first time, float

def initialize(amount, interest_rate, no_installments, cadence = :monthly, kind = :desc)
@amount = amount.big
@interest_rate = interest_rate.big / CADENCE[cadence]
@interest_rate = interest_rate.big / CADENCE[cadence]
@no_installments = no_installments
@kind = kind
end

def capital
private

# Capital - amount / number of installments
def capital
round capital_real
end

public :capital

def capital_real
@capital = calculate_capital if @capital.nil?
@capital
end

def calculate_capital
@amount / @no_installments
end

# Total amount of interests
def total_interests
round total_interests_real
end

public :total_interests

def total_interests_real
@total_interests = calculate_total_interests if @total_interests.nil?
@total_interests
end

def calculate_total_interests
if const?
par = (1 + @interest_rate) ** @no_installments
payback_amount = @amount * @interest_rate * par / (par - 1)

payback_amount * @no_installments - @amount
payback_amount = @amount * @interest_rate * par / (par - 1)
payback_amount * @no_installments - @amount
else
0.5.big * @interest_rate * @no_installments * (@amount + capital)
0.5.big * @interest_rate * @no_installments * (@amount + capital_real)
end
end

# Interests for period/installment
def interests(idx)
(@amount - (idx - 1) * capital) * @interest_rate
round interests_real(idx)
end

public :interests

def interests_real(idx)
@interests = Array.new if @interests.nil?
@interests[idx] = calculate_interests(idx) if @interests[idx].nil?
@interests[idx]
end

# little refactoring
alias_method :interests_for_period, :interests

def calculate_interests(idx)
if const?
ipmt(@interest_rate, idx, @no_installments, @amount).abs
else
(@amount - (idx - 1) * capital_real) * @interest_rate
end
end

# Monthly payment
def monthly_payment
round monthly_payment_real
end

public :monthly_payment

def monthly_payment_real
@monthly_payment = calculate_monthly_payment if @monthly_payment_float.nil?
@monthly_payment
end

def calculate_monthly_payment
pmt(@interest_rate, @no_installments, @amount).abs
end

# Capital for period
def capital_for_period(idx)
round capital_for_period_real(idx)
end

public :capital_for_period

def capital_for_period_real(idx)
# only for desc
return capital_real if not const?

# only for const
@capitals = Array.new if @capitals.nil?
@capitals[idx] = calculate_capital_for_period(idx) if @capitals[idx].nil?
@capitals[idx]
end

def calculate_capital_for_period(idx)
ppmt(@interest_rate, idx, @no_installments, @amount).abs
end

# Amount for period
def amounts(idx)
capital_for_period(idx) + interests(idx)
end

public :amounts

def const?
@kind == :const
end

def payback(range)
from = range.begin
to = range.end
public 'const?'

# round money value
def round(v)
MoneyArray.money_round_value(v)
end

# Loan debug
def to_s
s = "Loan\n"
table = payback_all
table.each_with_index do |t, i|
s += "#{t[:no]}: #{t[:amount].to_f};\t#{t[:balance].to_f};\t#{t[:capital].to_f};\t#{t[:interests].to_f}\n"
end
return s
end

public :to_s

# Paybacks items
# all payback items for all
def calculate_payback
from = 1
to = @no_installments
range = (from..to)

current_amount = 0.0

if const?
par = (1 + @interest_rate) ** @no_installments

range.map do |n|
{ no: n, interests: interests(n), amount: @amount * @interest_rate * par / (par - 1) }
res = range.map do |n|
current_amount += monthly_payment_real
{ no: n, interests: interests(n), amount: monthly_payment_real, capital: capital_for_period(n), balance: @amount + total_interests_real - current_amount }
end
else
range.map do |n|
{ no: n, interests: interests(n), amount: capital + interests(n) }
res = range.map do |n|
amount = capital_real + interests(n)
current_amount += amount
{ no: n, interests: interests(n), amount: amount, capital: capital_real, balance: @amount + total_interests_real - current_amount }
end
end
end
@paybacks = res
@paybacks_round = round_paybacks(res)
end

def const?
@kind == :const
def payback_real(range)
calculate_payback if @paybacks.nil?
@paybacks[range]
end

def payback(range)
calculate_payback if @paybacks.nil?
@paybacks_round[range]
end
private :const?

def payback_all
calculate_payback if @paybacks.nil?
@paybacks_round
end

public :payback_all

public :payback


def round_paybacks(res)
[:interests, :amount, :capital, :balance].each do |k|
tmp = MoneyArray.factory(res.collect { |r| r[k] })
tmp = tmp.money_round

res.each_with_index do |r, i|
r[k] = tmp[i]
end

end
return res
end


end
end
end
Loading