Skip to content

Commit

Permalink
Code cleanup & test improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
TotalVerb committed Dec 9, 2015
1 parent 2d96fd0 commit edbc7b9
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 52 deletions.
2 changes: 1 addition & 1 deletion src/Currencies.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ using Requests

# Exports
export AbstractMonetary, Monetary
export currency, currencyinfo, @usingcurrencies
export currency, currencyinfo, decimals, @usingcurrencies
export valuate, ExchangeRateTable, ecbrates
export Basket, StaticBasket, DynamicBasket
export simplefv, compoundfv
Expand Down
3 changes: 1 addition & 2 deletions src/basket.jl
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,7 @@ function Base.length(b::Basket)
end
acc
end
Base.haskey(b::Basket, k) =
haskey(b.table, k) && !iszero(b.table[k])
Base.haskey(b::Basket, k) = haskey(b.table, k) && !iszero(b.table[k])
Base.getindex(b::Basket, k::Symbol) = get(b.table, k, zero(Monetary{k}))
Base.start(b::Basket) = start(b.table)
function Base.next(b::Basket, s)
Expand Down
2 changes: 1 addition & 1 deletion src/custom.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ This function may be called with either a symbol, a `Monetary` type, or a
"""
currencyinfo(s::Symbol) = DATA[s][2]
currencyinfo{T,U,V}(::Type{Monetary{T,U,V}}) = currencyinfo(T)
currencyinfo{T<:Monetary}(::Type{T}) = currencyinfo(fill_monetary_type(T))
currencyinfo{T<:Monetary}(::Type{T}) = currencyinfo(filltype(T))
currencyinfo(m::Monetary) = currencyinfo(typeof(m))
14 changes: 9 additions & 5 deletions src/investments.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

# calculate future value
"""
simplefv(amount, interest, periods) → Monetary
Compute the future value of the given monetary amount, at the given interest
rate, compounded each period on the principal only. This is known as "simple
interest"; note that this is very rare in practice. For example, to find the
future value of \$1000 (US) invested today in 12 years, at a simple interest
rate of 5% per year, compute:
future value of \$1000 (US) today in 12 years, at a simple interest rate of 5%
per year, compute:
simplefv(1000USD, 0.05, 12)
Expand All @@ -17,14 +19,16 @@ This method is generic; any type for the PV is accepted provided that it is
compatible with real multiplication. Negative values for the rate and the period
are allowed.
"""
function simplefv(pv, rate::Real, periods::Integer)
function simplefv(pv, rate::Real, periods::Real)
pv * (one(rate) + rate * periods)
end

"""
compoundfv(amount, interest, periods) → Monetary
Compute the future value of the given monetary amount, at the given interest
rate, compounded each period. For example, to find the future value of \$1000
(US) invested today in 12 years, at a rate of 3% per year, compute:
(US) today in 12 years, at an interest rate of 3% per year, compute:
compoundfv(1000USD, 0.03, 12)
Expand All @@ -36,6 +40,6 @@ This method is generic; any type for the PV is accepted provided that it is
compatible with real multiplication. Negative values for the rate and the period
are allowed.
"""
function compoundfv(pv, rate::Real, periods::Integer)
function compoundfv(pv, rate::Real, periods::Real)
pv * (one(rate) + rate)^periods
end
19 changes: 13 additions & 6 deletions src/monetary.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,14 @@ end
Monetary(T::Symbol, x; precision=decimals(T)) =
Monetary{T, typeof(x), precision}(x)

# Get the full type of a Monetary by filling in defaults
fill_monetary_type{T}(::Type{Monetary{T}}) = Monetary{T, Int, decimals(T)}
fill_monetary_type{T,U}(::Type{Monetary{T,U}}) = Monetary{T, U, decimals(T)}
"""
filltype(typ) → typ
Fill in default type parameters to get a fully-specified concrete type from a
partially-specified one.
"""
filltype{T}(::Type{Monetary{T}}) = Monetary{T, Int, decimals(T)}
filltype{T,U}(::Type{Monetary{T,U}}) = Monetary{T, U, decimals(T)}

"""
Return a symbol (of uppercase letters) corresponding to the ISO 4217 currency
Expand All @@ -47,6 +52,8 @@ example, `currency(80USD)` will return `:USD`.
currency{T}(m::Monetary{T}) = T

"""
decimals(money) → Int
Get the precision, in terms of the number of decimal places after the major
currency unit, of the given `Monetary` value or type. Alternatively, if given
a symbol, gets the default decimal value (the number of decimal places to
Expand All @@ -55,13 +62,13 @@ represent the minor currency unit) for that symbol.
decimals(c::Symbol) = DATA[c][1]
decimals{T,U,V}(::Monetary{T,U,V}) = V
decimals{T,U,V}(::Type{Monetary{T,U,V}}) = V
decimals{T<:Monetary}(::Type{T}) = decimals(fill_monetary_type(T))
decimals{T<:Monetary}(::Type{T}) = decimals(filltype(T))

# numeric operations
Base.zero{T,U,V}(::Type{Monetary{T,U,V}}) = Monetary{T,U,V}(0)
Base.one{T,U,V}(::Type{Monetary{T,U,V}}) = Monetary{T,U,V}(10^V)
Base.zero{T<:Monetary}(::Type{T}) = zero(fill_monetary_type(T))
Base.one{T<:Monetary}(::Type{T}) = one(fill_monetary_type(T))
Base.zero{T<:Monetary}(::Type{T}) = zero(filltype(T))
Base.one{T<:Monetary}(::Type{T}) = one(filltype(T))
Base.int(m::Monetary) = m.amt

# on types
Expand Down
11 changes: 7 additions & 4 deletions test/basket.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ basket_e = compoundfv(basket_c, 0.02, 12)
basket_f = basket_a - basket_b
basket_g = basket_f / 4

@testset "StaticBasket simple arithmetic" begin
@testset "Basket simple arithmetic" begin
@test basket_c == StaticBasket([100USD, 20EUR])
@test basket_d == StaticBasket([400USD, 80EUR])
@test basket_e == StaticBasket([126.82USD, 25.36EUR])
Expand Down Expand Up @@ -51,16 +51,19 @@ basket_m = StaticBasket([basket_i, basket_j, 100JPY])
basket_n = zero(StaticBasket)
basket_o = zero(basket_m)
basket_p = zero(DynamicBasket)
basket_q = DynamicBasket([basket_o, StaticBasket([10USD, 20USD])])

@testset "Basket constructor and zero" begin
@testset "Basket construction" begin
@test basket_m == StaticBasket([-20EUR, 100JPY])
@test basket_n == basket_o == StaticBasket() == basket_p
@test basket_n == basket_o == StaticBasket() == basket_p == zero(JPY)
@test basket_q == 30USD
end

# Errors
@testset "Basket constructor errors" begin
@test_throws ArgumentError StaticBasket([1, 2, 3])
@test_throws ArgumentError DynamicBasket([1USD, (1USD, 2USD, 3)])
@test_throws MethodError one(StaticBasket)
end

# Iteration & access
Expand All @@ -76,7 +79,7 @@ end
# Dynamic
basket_dyn = DynamicBasket() + basket_g

@testset "DynamicBasket operations" begin
@testset "DynamicBasket" begin
basket_dyn[:CAD] = 10CAD
@test basket_dyn == DynamicBasket([25USD, -5EUR, 10CAD])
@test haskey(basket_dyn, :USD)
Expand Down
9 changes: 9 additions & 0 deletions test/currencies.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ end
@test currencyinfo(:custom) == "Custom Currency"
@test currencyinfo(10custom) == "Custom Currency"
@test currencyinfo(Monetary{:xbt}) == "Bitcoin (100 satoshi unit)"
@test currencyinfo(Monetary{:BND, BigInt}) == "Brunei dollar"
end

# Currency
Expand All @@ -31,3 +32,11 @@ end
@test currency(10xbt) == :xbt
@test currency(custom) == :custom
end

# Default precision
@testset "decimals()" begin
@test decimals(:USD) == 2
@test decimals(:JPY) == 0
@test decimals(Monetary{:USD, BigInt}) == 2
@test decimals(Monetary{:JPY, Int, 4}) == 4
end
14 changes: 7 additions & 7 deletions test/display.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Display methods (show & print & writemime) tests

@testset "Display as text/plain" begin
@testset "text/plain" begin
@test contains(stringmime("text/plain", 1USD), "1.00")
@test contains(stringmime("text/plain", 1JPY), "1")
@test contains(stringmime("text/plain", StaticBasket([1USD, 1CAD])), "CAD")
Expand All @@ -19,30 +19,30 @@
"−1.00 JPY"
end

@testset "Display as text/latex" begin
@testset "text/latex" begin
@test stringmime("text/latex", 100USD) == "\$100.00\\,\\mathrm{USD}\$"
@test stringmime("text/latex", -100JPY) == "\$-100\\,\\mathrm{JPY}\$"
@test stringmime("text/latex", 0GBP) == "\$0.00\\,\\mathrm{GBP}\$"
@test stringmime("text/latex", zero(Monetary{:EUR, Int, 0})) ==
"\$0\\,\\mathrm{EUR}\$"
end

@testset "Display as text/markdown" begin
@testset "text/markdown" begin
basketstr = stringmime(
"text/markdown", DynamicBasket([1USD, 20CAD, -10JPY]))
"text/markdown", DynamicBasket([USD, 20CAD, -10JPY]))

@test contains(basketstr, "`Currencies.DynamicBasket`")
@test contains(basketstr, "\$3\$-currency")
@test contains(basketstr, " - \$-10\\,\\mathrm{JPY}\$")
end

@testset "string() [print], show()" begin
@test string(1USD) == "1.0USD"
@testset "print & show" begin
@test string(USD) == "1.0USD"
@test string(0.01USD) == "0.01USD"
@test string(20JPY) == "20.0JPY"

# this test is a bit complicated because order is undefined
basketstr = string(StaticBasket([1USD, 20CAD, -10JPY]))
basketstr = string(StaticBasket([USD, 20CAD, -10JPY]))
@test contains(basketstr, "StaticBasket([")
@test contains(basketstr, "-10.0JPY")
@test contains(basketstr, "20.0CAD")
Expand Down
16 changes: 13 additions & 3 deletions test/investment.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
# Investment computations tests

@testset "Future value" begin
@testset "Future Value" begin
@test compoundfv(1000USD, 0.02, 12) == 1268.24USD
@test simplefv(1000USD, 0.04, 12) == 1480USD

# compoundfv edge cases
@test compoundfv(1000USD, 0.02, 1.5) == 1030.15USD
@test compoundfv(1000USD, -0.01, 2) == 980.10USD

# note π doesn't work directly because Irrational has no one()
@test compoundfv(1000USD, π-1, 0) == 1000USD

# generic use cases
@test compoundfv(1, e-1, 10) exp(10)
@test compoundfv(StaticBasket([USD, EUR]), 0.1, 2) ==
StaticBasket([1.21USD, 1.21EUR])
end
62 changes: 41 additions & 21 deletions test/monetary.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Tests for Monetary values

# Basic arithmetic
@testset "Monetary arithmetic" begin
@testset "Monetary — Arithmetic" begin
@test 1USD + 2USD == 3USD
@test 1USD + 2USD + 3USD == 6USD
@test 1.5USD * 3 == 4.5USD
Expand All @@ -10,6 +10,7 @@
@test 1.11USD * 999 == 1108.89USD
@test -1USD == -(1USD)
@test 1USD == one(USD)
@test 1USD == USD
@test 10USD / 1USD == 10.0
@test 10USD ÷ 3USD == 3
@test 10USD % 3USD == 1USD
Expand All @@ -18,7 +19,7 @@
end

# Type safety
@testset "Monetary type safety" begin
@testset "Monetary — Type Safety" begin
@test_throws ArgumentError 1USD + 1CAD
@test_throws ArgumentError 100JPY - 0USD # even when zero!
@test_throws MethodError 5USD * 10CAD
Expand All @@ -30,36 +31,55 @@ end
@test_throws MethodError 10USD % 5 # meaningless
end

# Comparisons
@testset "Monetary comparisons (same type)" begin
@test 1EUR < 2EUR
@test 3JPY > 2JPY
@test 3JPY 3JPY
@test 9USD 9.01USD
@test -1USD 0USD
@test sort([0.5EUR, 0.7EUR, 0.3EUR]) == [0.3EUR, 0.5EUR, 0.7EUR]
end
@testset "Monetary — Comparisons" begin
# Comparisons
@testset "Monetary — Homogenous Comparisons" begin
@test 1EUR < 2EUR
@test 3JPY > 2JPY
@test 3JPY 3JPY
@test 9USD 9.01USD
@test -1USD 0USD
@test sort([0.5EUR, 0.7EUR, 0.3EUR]) == [0.3EUR, 0.5EUR, 0.7EUR]

# Type safety
@testset "Monetary comparisons (different type)" begin
@test 1EUR 1USD
@test 5USD 5
@test 5USD 500
# immutable types should be **equivalent**
@test 10USD 10USD
@test zero(USD) 0USD
@test -one(USD) +(-USD)
end

# Type safety
@testset "Monetary — Heterogenous Comparisons" begin
@test 1EUR 1USD
@test 5USD 5
@test 5USD 500

@test_throws MethodError EUR > USD
@test_throws MethodError GBP USD
@test_throws MethodError JPY < USD
@test_throws MethodError EUR > USD
@test_throws MethodError GBP USD
@test_throws MethodError JPY < USD
end
end

# Big int monetary
BI_USD = Monetary(:USD, BigInt(100))
@testset "BigInt Monetary" begin
BI_USD2 = one(Monetary{:USD, BigInt})
I128_USD = one(Monetary{:USD, Int128})
@testset "Monetary — Representation" begin
@test BigInt(2)^100 * BI_USD + 10BI_USD == (BigInt(2)^100 + 10) * BI_USD

# test **equality** — note equivalence is untrue because BigInt
@test BI_USD == BI_USD2

# wrapping behaviour (strange but documented)
@test typemin(Int128) * I128_USD typemax(Int128) * I128_USD + I128_USD

# don't mix
@test_throws ArgumentError BI_USD + USD
@test_throws ArgumentError BI_USD - I128_USD
@test_throws MethodError BI_USD / I128_USD
end

# Custom decimals
@testset "Custom precision" begin
@testset "Monetary — Precision" begin
flatusd = one(Monetary{:USD, Int, 0})
millusd = one(Monetary{:USD, Int, 3})

Expand Down
6 changes: 4 additions & 2 deletions test/valuation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ rates_d = ExchangeRateTable(
:USD => 1.0,
:CAD => 0.75)

@testset "Valuation with hard-coded rates" begin
@testset "Valuation" begin
@test valuate(rates_a, :CAD, 21USD) == 28CAD
@test valuate(rates_a, :CAD, DynamicBasket([21USD, 10CAD])) == 38CAD
@test valuate(rates_b, :USD, 10USD) == 10USD
Expand All @@ -35,7 +35,7 @@ end
# Valuation — ECB data
rates_e = ecbrates()

@testset "Valuation with ECB data" begin
@testset "ECB data" begin
# test cache
@test rates_e ecbrates()

Expand All @@ -45,4 +45,6 @@ rates_e = ecbrates()
@test isa(rates_e, ExchangeRateTable)
@test valuate(rates_e, :USD, 1USD) == 1USD
@test valuate(rates_e, :EUR, 20EUR) == 20EUR
@test haskey(rates_e, :JPY)
@test length(rates_e) > 10
end

0 comments on commit edbc7b9

Please sign in to comment.