Skip to content

Commit 61fcdb6

Browse files
committed
Merge branch 'cv/limits' (#8)
2 parents 57a1cca + 859f054 commit 61fcdb6

File tree

2 files changed

+432
-60
lines changed

2 files changed

+432
-60
lines changed

src/FixedPointDecimals.jl

Lines changed: 96 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,10 @@ export FixedDecimal, RoundThrows
2929

3030
using Compat
3131

32-
import Base: reinterpret, zero, one, abs, sign, ==, <, <=, +, -, /, *, div,
33-
rem, divrem, fld, mod, fldmod, fld1, mod1, fldmod1, isinteger,
34-
typemin, typemax, realmin, realmax, print, show, string, convert, parse,
35-
promote_rule, min, max, trunc, round, floor, ceil, eps, float, widemul
32+
import Base: reinterpret, zero, one, abs, sign, ==, <, <=, +, -, /, *, div, rem, divrem,
33+
fld, mod, fldmod, fld1, mod1, fldmod1, isinteger, typemin, typemax,
34+
realmin, realmax, print, show, string, convert, parse, promote_rule, min, max,
35+
trunc, round, floor, ceil, eps, float, widemul
3636

3737
const IEEEFloat = Union{Float16, Float32, Float64}
3838

@@ -70,17 +70,26 @@ for fn in [:trunc, :floor, :ceil]
7070
end
7171

7272
"""
73-
FixedDecimal{I <: Integer, f::Int}
73+
FixedDecimal{T <: Integer, f::Int}
7474
75-
A fixed-point decimal type backed by integral type `I`, with `f` digits after
75+
A fixed-point decimal type backed by integral type `T`, with `f` digits after
7676
the decimal point stored.
7777
"""
7878
immutable FixedDecimal{T <: Integer, f} <: Real
7979
i::T
8080

81-
# internal constructor
82-
Base.reinterpret{T, f}(::Type{FixedDecimal{T, f}}, i::Integer) =
83-
new{T, f}(i % T)
81+
# inner constructor
82+
function Base.reinterpret{T, f}(::Type{FixedDecimal{T, f}}, i::Integer)
83+
n = max_exp10(T)
84+
if f >= 0 && (n < 0 || f <= n)
85+
new{T, f}(i % T)
86+
else
87+
throw(ArgumentError(
88+
"Requested number of decimal places $f exceeds the max allowed for the " *
89+
"storage type $T: [0, $(max_exp10(T))]"
90+
))
91+
end
92+
end
8493
end
8594

8695
const FD = FixedDecimal
@@ -131,8 +140,8 @@ _round_to_even(q, r, d) = _round_to_even(promote(q, r, d)...)
131140
# TODO: can we use floating point to speed this up? after we build a
132141
# correctness test suite.
133142
function *{T, f}(x::FD{T, f}, y::FD{T, f})
134-
powt = T(10)^f
135-
quotient, remainder = fldmod(Base.widemul(x.i, y.i), powt)
143+
powt = coefficient(FD{T, f})
144+
quotient, remainder = fldmod(widemul(x.i, y.i), powt)
136145
reinterpret(FD{T, f}, _round_to_even(quotient, remainder, powt))
137146
end
138147

@@ -142,14 +151,15 @@ end
142151
*{T, f}(x::FD{T, f}, y::Integer) = reinterpret(FD{T, f}, T(x.i * y))
143152

144153
function /{T, f}(x::FD{T, f}, y::FD{T, f})
145-
powt = T(10)^f
154+
powt = coefficient(FD{T, f})
146155
quotient, remainder = fldmod(widemul(x.i, powt), y.i)
147156
reinterpret(FD{T, f}, T(_round_to_even(quotient, remainder, y.i)))
148157
end
149158

150-
# these functions are needed to avoid InexactError when converting from the integer type
159+
# These functions allow us to perform division with integers outside of the range of the
160+
# FixedDecimal.
151161
function /{T, f}(x::Integer, y::FD{T, f})
152-
powt = T(10)^f
162+
powt = coefficient(FD{T, f})
153163
powtsq = widemul(powt, powt)
154164
quotient, remainder = fldmod(widemul(x, powtsq), y.i)
155165
reinterpret(FD{T, f}, T(_round_to_even(quotient, remainder, y.i)))
@@ -161,16 +171,17 @@ function /{T, f}(x::FD{T, f}, y::Integer)
161171
end
162172

163173
# integerification
164-
trunc{T, f}(x::FD{T, f}) = FD{T, f}(div(x.i, T(10)^f))
165-
floor{T, f}(x::FD{T, f}) = FD{T, f}(fld(x.i, T(10)^f))
174+
trunc{T, f}(x::FD{T, f}) = FD{T, f}(div(x.i, coefficient(FD{T, f})))
175+
floor{T, f}(x::FD{T, f}) = FD{T, f}(fld(x.i, coefficient(FD{T, f})))
176+
166177
# TODO: round with number of digits; should be easy
167178
function round{T, f}(x::FD{T, f}, ::RoundingMode{:Nearest}=RoundNearest)
168-
powt = T(10)^f
179+
powt = coefficient(FD{T, f})
169180
quotient, remainder = fldmod(x.i, powt)
170181
FD{T, f}(_round_to_even(quotient, remainder, powt))
171182
end
172183
function ceil{T, f}(x::FD{T, f})
173-
powt = T(10)^f
184+
powt = coefficient(FD{T, f})
174185
quotient, remainder = fldmod(x.i, powt)
175186
if remainder > 0
176187
FD{T, f}(quotient + one(quotient))
@@ -185,49 +196,48 @@ for fn in [:trunc, :floor, :ceil]
185196
# round/trunc/ceil/flooring to FD; generic
186197
# TODO. we may need to check overflow and boundary conditions here.
187198
@eval function $fn{T, f}(::Type{FD{T, f}}, x::Real)
188-
powt = T(10)^f
199+
powt = coefficient(FD{T, f})
189200
val = trunc(T, $(Symbol(fn, "mul"))(x, powt))
190201
reinterpret(FD{T, f}, val)
191202
end
192203
end
193-
round{TI <: Integer}(::Type{TI}, x::FD,
194-
::RoundingMode{:Nearest}=RoundNearest)::TI = round(x)
195-
function round{T, f}(::Type{FD{T, f}}, x::Real,
196-
::RoundingMode{:Nearest}=RoundNearest)
197-
reinterpret(FD{T, f}, round(T, T(10)^f * x))
204+
function round{TI <: Integer}(::Type{TI}, x::FD, ::RoundingMode{:Nearest}=RoundNearest)::TI
205+
round(x)
206+
end
207+
function round{T, f}(::Type{FD{T, f}}, x::Real, ::RoundingMode{:Nearest}=RoundNearest)
208+
reinterpret(FD{T, f}, round(T, x * coefficient(FD{T, f})))
198209
end
199210

200211
# needed to avoid ambiguity
201-
function round{T, f}(::Type{FD{T, f}}, x::Rational,
202-
::RoundingMode{:Nearest}=RoundNearest)
203-
reinterpret(FD{T, f}, round(T, T(10)^f * x))
212+
function round{T, f}(::Type{FD{T, f}}, x::Rational, ::RoundingMode{:Nearest}=RoundNearest)
213+
reinterpret(FD{T, f}, round(T, x * coefficient(FD{T, f})))
204214
end
205215

206216
# conversions and promotions
207-
convert{T, f}(::Type{FD{T, f}}, x::Integer) =
208-
reinterpret(FD{T, f}, round(T, Base.widemul(T(x), T(10)^f)))
217+
function convert{T, f}(::Type{FD{T, f}}, x::Integer)
218+
reinterpret(FD{T, f}, T(widemul(x, coefficient(FD{T, f}))))
219+
end
209220

210221
convert{T <: FD}(::Type{T}, x::AbstractFloat) = round(T, x)
211222

212223
function convert{T, f}(::Type{FD{T, f}}, x::Rational)::FD{T, f}
213-
powt = T(10)^f
214-
num::T, den::T = numerator(x), denominator(x)
215-
g = gcd(powt, den)
216-
powt = div(powt, g)
217-
den = div(den, g)
218-
reinterpret(FD{T, f}, powt * num) / FD{T, f}(den)
224+
powt = coefficient(FD{T, f})
225+
reinterpret(FD{T, f}, T(x * powt))
219226
end
220227

221228
function convert{T, f, U, g}(::Type{FD{T, f}}, x::FD{U, g})
222229
if f g
223-
reinterpret(FD{T, f}, convert(T, Base.widemul(T(10)^(f-g), x.i)))
230+
# Compute `10^(f - g)` without overflow
231+
powt = div(coefficient(FD{T, f}), coefficient(FD{U, g}))
232+
reinterpret(FD{T, f}, T(widemul(x.i, powt)))
224233
else
225-
sf = T(10)^(g - f)
226-
q, r = divrem(x.i, sf)
227-
if r 0
228-
throw(InexactError())
234+
# Compute `10^(g - f)` without overflow
235+
powt = div(coefficient(FD{U, g}), coefficient(FD{T, f}))
236+
q, r = divrem(x.i, powt)
237+
if r == 0
238+
reinterpret(FD{T, f}, T(q))
229239
else
230-
reinterpret(FD{T, f}, convert(T, q))
240+
throw(InexactError())
231241
end
232242
end
233243
end
@@ -240,16 +250,20 @@ for divfn in [:div, :fld, :fld1]
240250
end
241251

242252
convert(::Type{AbstractFloat}, x::FD) = convert(floattype(typeof(x)), x)
243-
convert{TF <: AbstractFloat, T, f}(::Type{TF}, x::FD{T, f})::TF =
244-
x.i / TF(10)^f
253+
function convert{TF <: AbstractFloat, T, f}(::Type{TF}, x::FD{T, f})::TF
254+
x.i / coefficient(FD{T, f})
255+
end
256+
257+
function convert{TF <: BigFloat, T, f}(::Type{TF}, x::FD{T, f})::TF
258+
BigInt(x.i) / BigInt(coefficient(FD{T, f}))
259+
end
245260

246261
function convert{TI <: Integer, T, f}(::Type{TI}, x::FD{T, f})::TI
247262
isinteger(x) || throw(InexactError())
248-
div(x.i, T(10)^f)
263+
div(x.i, coefficient(FD{T, f}))
249264
end
250265

251-
convert{TR<:Rational,T,f}(::Type{TR}, x::FD{T, f})::TR =
252-
x.i // T(10)^f
266+
convert{TR <: Rational, T, f}(::Type{TR}, x::FD{T, f})::TR = x.i // coefficient(FD{T, f})
253267

254268
promote_rule{T, f, TI <: Integer}(::Type{FD{T, f}}, ::Type{TI}) = FD{T, f}
255269
promote_rule{T, f, TF <: AbstractFloat}(::Type{FD{T, f}}, ::Type{TF}) = TF
@@ -266,7 +280,7 @@ Base.@pure promote_rule{T, f, U, g}(::Type{FD{T, f}}, ::Type{FD{U, g}}) =
266280
<={T <: FD}(x::T, y::T) = x.i <= y.i
267281

268282
# predicates and traits
269-
isinteger{T, f}(x::FD{T, f}) = rem(x.i, T(10)^f) == 0
283+
isinteger{T, f}(x::FD{T, f}) = rem(x.i, coefficient(FD{T, f})) == 0
270284
typemin{T, f}(::Type{FD{T, f}}) = reinterpret(FD{T, f}, typemin(T))
271285
typemax{T, f}(::Type{FD{T, f}}) = reinterpret(FD{T, f}, typemax(T))
272286
eps{T <: FD}(::Type{T}) = reinterpret(T, 1)
@@ -283,7 +297,7 @@ function print{T, f}(io::IO, x::FD{T, f})
283297

284298
# note: a is negative if x.i == typemin(x.i)
285299
s, a = sign(x.i), abs(x.i)
286-
integer, fractional = divrem(a, T(10)^f)
300+
integer, fractional = divrem(a, coefficient(x))
287301
integer = abs(integer) # ...but since f > 0, this is positive
288302
fractional = abs(fractional)
289303

@@ -382,4 +396,38 @@ function parse_round{T}(::Type{T}, fractional::AbstractString, ::RoundingMode{:N
382396
return T(0)
383397
end
384398

399+
400+
"""
401+
max_exp10(T)
402+
403+
The highest value of `x` which does not result in an overflow when evaluating `T(10)^x`. For
404+
types of `T` that do not overflow -1 will be returned.
405+
"""
406+
function max_exp10{T <: Integer}(::Type{T})
407+
applicable(typemax, T) || return -1
408+
W = widen(T)
409+
type_max = W(typemax(T))
410+
411+
powt = one(W)
412+
ten = W(10)
413+
exponent = 0
414+
415+
while type_max > powt
416+
powt *= ten
417+
exponent += 1
418+
end
419+
420+
exponent - 1
421+
end
422+
423+
"""
424+
coefficient(::Type{FD{T, f}}) -> T
425+
426+
Compute `10^f` as an Integer without overflow. Note that overflow will not occur for any
427+
constructable `FD{T, f}`.
428+
"""
429+
coefficient{T, f}(::Type{FD{T, f}}) = T(10)^f
430+
coefficient{T, f}(fd::FD{T, f}) = coefficient(FD{T, f})
431+
value(fd::FD) = fd.i
432+
385433
end

0 commit comments

Comments
 (0)