Skip to content

Instantly share code, notes, and snippets.

@nmk
Created December 13, 2011 06:55
Show Gist options
  • Save nmk/1470975 to your computer and use it in GitHub Desktop.
Save nmk/1470975 to your computer and use it in GitHub Desktop.
Amount deviation
require 'bigdecimal'
class BigDecimal
def inspect; to_s('F'); end
end
class Account
attr_reader :intial_amount, :balance_changes
def initialize(amount)
@initial_amount = amount
@balance_changes = [BalanceChange.new(amount, BigDecimal('0'))]
end
def current_balance
@balance_changes.last
end
def total_win_percentage
((current_balance.amount - @initial_amount) / @initial_amount).mult(BigDecimal('100'), 6)
end
def change_by!(amount_diff)
new_amount = current_balance.amount + amount_diff
new_percentage = ((new_amount - current_balance.amount) / current_balance.amount).mult(BigDecimal('100'), 6)
@balance_changes << BalanceChange.new(new_amount, new_percentage)
end
end
BalanceChange = Struct.new(:amount, :diff_to_last_percentage)
module Metrics
module Common
ONE = BigDecimal('1') unless const_defined?(:ONE)
ONE_HUNDRED = BigDecimal('100') unless const_defined?(:ONE_HUNDRED)
DECIMAL_ROUNDING_SCALE = 6 unless const_defined?(:DECIMAL_ROUNDING_SCALE)
def self.sum_percent_deltas(pds)
result = ONE
pds.each { |pd|
# There is no need to call #round explicitly.
# BigDecimal#mult supports # inherent rounding to a specified scale.
#
# See http://ruby-doc.org/stdlib-1.8.7/libdoc/bigdecimal/rdoc/BigDecimal.html#method-i-mult
result = result.mult(ONE + pd / ONE_HUNDRED, DECIMAL_ROUNDING_SCALE)
}
(result - 1) * ONE_HUNDRED
end
end
end
NR_OF_REPETITIONS = [10, 100, 1_000, 10_000]
INITIAL_AMOUNT = BigDecimal('10_000')
DELTA = BigDecimal('10')
describe Account do
describe "#initialize with #{INITIAL_AMOUNT}" do
subject { Account.new(INITIAL_AMOUNT) }
it 'should have one balance change when initialized' do
subject.balance_changes.should have(1).item
end
it "should have a current balance of #{INITIAL_AMOUNT}" do
subject.current_balance.amount.should == INITIAL_AMOUNT
end
end
describe "#change_by" do
subject { Account.new(INITIAL_AMOUNT) }
context 'when the change is positive' do
before { subject.change_by!(BigDecimal('100')) }
it 'should have a new amount' do
subject.current_balance.amount.should == BigDecimal('10100')
end
it 'should record the correct diff percentage' do
subject.current_balance.diff_to_last_percentage.should == BigDecimal('1')
end
end
context 'when the change is negative' do
before { subject.change_by!(BigDecimal('-100')) }
it 'should have a new amount' do
subject.current_balance.amount.should == BigDecimal('9900')
end
it 'should record the correct diff percentage' do
subject.current_balance.diff_to_last_percentage.should == BigDecimal('-1')
end
end
end
NR_OF_REPETITIONS.each do |nr|
describe "account after #{nr} changes" do
subject { Account.new(INITIAL_AMOUNT) }
before {
nr.times do
delta = BigDecimal((rand * 100 - 50).to_s) # a random number between -50 and 50
subject.change_by!(delta)
end
}
it "the cumulative balance changes should be equal to the absolute change" do
cumulative_change = Metrics::Common::sum_percent_deltas(subject.balance_changes.map(&:diff_to_last_percentage))
cumulative_change.should be_within(0.01).of(subject.total_win_percentage)
end
end
end
end
# run with
rspec -cf p amount-deviation-spec.rb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment