Skip to content

Commit 31251fd

Browse files
TotalVerbomus
authored andcommitted
Introduce FixedPointDecimals (#69)
Introduce FixedPointDecimals and have Monetary use it
1 parent 8aa1e75 commit 31251fd

File tree

2 files changed

+447
-4
lines changed

2 files changed

+447
-4
lines changed

src/FixedPointDecimals.jl

Lines changed: 224 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,227 @@
1+
# This file partially based off JuliaMath/FixedPointNumbers.jl
2+
#
3+
# Copyright (c) 2014: Jeff Bezanson and other contributors.
4+
#
5+
# Permission is hereby granted, free of charge, to any person obtaining
6+
# a copy of this software and associated documentation files (the
7+
# "Software"), to deal in the Software without restriction, including
8+
# without limitation the rights to use, copy, modify, merge, publish,
9+
# distribute, sublicense, and/or sell copies of the Software, and to
10+
# permit persons to whom the Software is furnished to do so, subject to
11+
# the following conditions:
12+
#
13+
# The above copyright notice and this permission notice shall be
14+
# included in all copies or substantial portions of the Software.
15+
#
16+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
20+
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
21+
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
22+
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23+
124
module FixedPointDecimals
225

3-
# package code goes here
26+
export FixedDecimal
27+
28+
using Compat
29+
30+
import Base: reinterpret, zero, one, abs, sign, ==, <, <=, +, -, /, *, div,
31+
rem, divrem, fld, mod, fldmod, fld1, mod1, fldmod1, isinteger,
32+
typemin, typemax, realmin, realmax, show, convert, promote_rule,
33+
min, max, trunc, round, floor, ceil, eps, float
34+
35+
"""
36+
FixedDecimal{I <: Integer, f::Int}
37+
38+
A fixed-point decimal type backed by integral type `I`, with `f` digits after
39+
the decimal point stored.
40+
"""
41+
immutable FixedDecimal{T <: Integer, f} <: Real
42+
i::T
43+
44+
# internal constructor
45+
Base.reinterpret{T, f}(::Type{FixedDecimal{T, f}}, i::Integer) =
46+
new{T, f}(i % T)
47+
end
48+
49+
const FD = FixedDecimal
50+
51+
floattype{T<:Union{Int8, UInt8, Int16, UInt16}, f}(::Type{FD{T, f}}) = Float32
52+
floattype{T<:Integer, f}(::Type{FD{T, f}}) = Float64
53+
floattype{f}(::Type{FD{BigInt, f}}) = BigFloat
54+
55+
# basic operators
56+
-{T, f}(x::FD{T, f}) = reinterpret(FD{T, f}, -x.i)
57+
abs{T, f}(x::FD{T, f}) = reinterpret(FD{T, f}, abs(x.i))
58+
59+
+{T, f}(x::FD{T, f}, y::FD{T, f}) = reinterpret(FD{T, f}, x.i+y.i)
60+
-{T, f}(x::FD{T, f}, y::FD{T, f}) = reinterpret(FD{T, f}, x.i-y.i)
61+
62+
function _round_to_even(quotient, remainder, powt)
63+
if powt == 1
64+
quotient
65+
else
66+
halfpowt = powt >> 1
67+
if remainder == halfpowt
68+
ifelse(iseven(quotient), quotient, quotient + one(quotient))
69+
elseif remainder < halfpowt
70+
quotient
71+
else
72+
quotient + one(quotient)
73+
end
74+
end
75+
end
76+
77+
# multiplication rounds to nearest even representation
78+
# TODO: can we use floating point to speed this up? after we build a
79+
# correctness test suite.
80+
function *{T, f}(x::FD{T, f}, y::FD{T, f})
81+
powt = T(10)^f
82+
quotient, remainder = fldmod(Base.widemul(x.i, y.i), powt)
83+
reinterpret(FD{T, f}, _round_to_even(quotient, remainder, powt))
84+
end
85+
86+
# these functions are needed to avoid InexactError when converting from the
87+
# integer type
88+
*{T, f}(x::Integer, y::FD{T, f}) = reinterpret(FD{T, f}, T(x * y.i))
89+
*{T, f}(x::FD{T, f}, y::Integer) = reinterpret(FD{T, f}, T(x.i * y))
90+
91+
# TODO. this is probably wrong sometimes.
92+
function /{T, f}(x::FD{T, f}, y::FD{T, f})
93+
powt = T(10)^f
94+
quotient, remainder = divrem(x.i, y.i)
95+
reinterpret(FD{T, f}, quotient * powt + round(T, remainder / y.i * powt))
96+
end
97+
98+
# integerification
99+
trunc{T, f}(x::FD{T, f}) = FD{T, f}(div(x.i, T(10)^f))
100+
floor{T, f}(x::FD{T, f}) = FD{T, f}(fld(x.i, T(10)^f))
101+
# TODO: round with number of digits; should be easy
102+
function round{T, f}(x::FD{T, f})
103+
powt = T(10)^f
104+
quotient, remainder = fldmod(x.i, powt)
105+
FD{T, f}(_round_to_even(quotient, remainder, powt))
106+
end
107+
function ceil{T, f}(x::FD{T, f})
108+
powt = T(10)^f
109+
quotient, remainder = fldmod(x.i, powt)
110+
if remainder > 0
111+
FD{T, f}(quotient + one(quotient))
112+
else
113+
FD{T, f}(quotient)
114+
end
115+
end
116+
117+
for truncfn in [:trunc, :round, :floor, :ceil]
118+
@eval $truncfn{TI}(::Type{TI}, x::FD)::TI = $truncfn(x)
119+
end
120+
121+
# conversions and promotions
122+
convert{T, f}(::Type{FD{T, f}}, x::Integer) =
123+
reinterpret(FD{T, f}, round(T, Base.widemul(T(x), T(10)^f)))
124+
125+
# TODO. this is very, very incorrect.
126+
convert{T, f}(::Type{FD{T, f}}, x::AbstractFloat) =
127+
reinterpret(FD{T, f}, round(T, T(10)^f * x))
128+
function convert{T, f}(::Type{FD{T, f}}, x::Rational)::FD{T, f}
129+
powt = T(10)^f
130+
num::T, den::T = numerator(x), denominator(x)
131+
g = gcd(powt, den)
132+
powt = div(powt, g)
133+
den = div(den, g)
134+
reinterpret(FD{T, f}, powt * num) / FD{T, f}(den)
135+
end
136+
137+
function convert{T, f, U, g}(::Type{FD{T, f}}, x::FD{U, g})
138+
if f g
139+
reinterpret(FD{T, f}, convert(T, Base.widemul(T(10)^(f-g), x.i)))
140+
else
141+
sf = T(10)^(g - f)
142+
q, r = divrem(x.i, sf)
143+
if r 0
144+
throw(InexactError())
145+
else
146+
reinterpret(FD{T, f}, convert(T, q))
147+
end
148+
end
149+
end
150+
151+
for remfn in [:rem, :mod, :mod1, :min, :max]
152+
@eval $remfn{T <: FD}(x::T, y::T) = reinterpret(T, $remfn(x.i, y.i))
153+
end
154+
for divfn in [:div, :fld, :fld1]
155+
@eval $divfn{T <: FD}(x::T, y::T) = $divfn(x.i, y.i)
156+
end
157+
158+
convert(::Type{AbstractFloat}, x::FD) = convert(floattype(typeof(x)), x)
159+
convert{TF <: AbstractFloat, T, f}(::Type{TF}, x::FD{T, f})::TF =
160+
x.i / TF(10)^f
161+
162+
function convert{TI <: Integer, T, f}(::Type{TI}, x::FD{T, f})::TI
163+
isinteger(x) || throw(InexactError())
164+
div(x.i, T(10)^f)
165+
end
166+
167+
convert{TR<:Rational,T,f}(::Type{TR}, x::FD{T, f})::TR =
168+
x.i // T(10)^f
169+
170+
promote_rule{T, f, TI <: Integer}(::Type{FD{T, f}}, ::Type{TI}) = FD{T, f}
171+
promote_rule{T, f, TF <: AbstractFloat}(::Type{FD{T, f}}, ::Type{TF}) = TF
172+
promote_rule{T, f, TR}(::Type{FD{T, f}}, ::Type{Rational{TR}}) = Rational{TR}
173+
174+
# comparison
175+
=={T <: FD}(x::T, y::T) = x.i == y.i
176+
<{T <: FD}(x::T, y::T) = x.i < y.i
177+
<={T <: FD}(x::T, y::T) = x.i <= y.i
178+
179+
# predicates and traits
180+
isinteger{T, f}(x::FD{T, f}) = rem(x.i, T(10)^f) == 0
181+
typemin{T, f}(::Type{FD{T, f}}) = reinterpret(FD{T, f}, typemin(T))
182+
typemax{T, f}(::Type{FD{T, f}}) = reinterpret(FD{T, f}, typemax(T))
183+
eps{T <: FD}(::Type{T}) = reinterpret(T, 1)
184+
eps(x::FD) = eps(typeof(x))
185+
realmin{T <: FD}(::Type{T}) = eps(T)
186+
realmax{T <: FD}(::Type{T}) = typemax(T)
187+
188+
# printing
189+
function show{T}(io::IO, x::FD{T, 0})
190+
iscompact = get(io, :compact, false)
191+
if !iscompact
192+
print(io, "FixedDecimal{$T,0}(")
193+
end
194+
print(io, x.i)
195+
if !iscompact
196+
print(io, ')')
197+
end
198+
end
199+
function show{T, f}(io::IO, x::FD{T, f})
200+
iscompact = get(io, :compact, false)
201+
202+
# note: a is negative if x.i == typemin(x.i)
203+
s, a = sign(x.i), abs(x.i)
204+
integer, fractional = divrem(a, T(10)^f)
205+
integer = abs(integer) # ...but since f > 0, this is positive
206+
fractional = abs(fractional)
207+
208+
if !iscompact
209+
print(io, "FixedDecimal{$T,$f}(")
210+
end
211+
if s == -1
212+
print(io, "-")
213+
end
214+
fractionchars = lpad(abs(fractional), f, "0")
215+
if iscompact
216+
fractionchars = rstrip(fractionchars, '0')
217+
if isempty(fractionchars)
218+
fractionchars = "0"
219+
end
220+
end
221+
print(io, integer, '.', fractionchars)
222+
if !iscompact
223+
print(io, ')')
224+
end
225+
end
4226

5-
end # module
227+
end

0 commit comments

Comments
 (0)