Skip to content

Commit

Permalink
Merge 5877a58 into 8e80d39
Browse files Browse the repository at this point in the history
  • Loading branch information
NHDaly committed Feb 1, 2020
2 parents 8e80d39 + 5877a58 commit fd1402b
Showing 1 changed file with 53 additions and 56 deletions.
109 changes: 53 additions & 56 deletions src/FixedPointDecimals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ module FixedPointDecimals

export FixedDecimal, RoundThrows

import Base: reinterpret, zero, one, abs, sign, ==, <, <=, +, -, /, *, div, rem, divrem,
fld, mod, fldmod, fld1, mod1, fldmod1, isinteger, typemin, typemax,
print, show, string, convert, parse, promote_rule, min, max,
floatmin, floatmax, trunc, round, floor, ceil, eps, float, widemul, decompose
using Base: decompose

const BitInteger = Union{Int8, UInt8, Int16, UInt16, Int32, UInt32, Int64,
UInt64, Int128, UInt128}
Expand Down Expand Up @@ -114,22 +111,22 @@ floattype(::Type{<:FD{T}}) where {T<:Integer} = Float64
floattype(::Type{<:FD{BigInt}}) = BigFloat

# basic operators
-(x::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, -x.i)
abs(x::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, abs(x.i))
Base.:-(x::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, -x.i)
Base.abs(x::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, abs(x.i))

+(x::FD{T, f}, y::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, x.i+y.i)
-(x::FD{T, f}, y::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, x.i-y.i)
Base.:+(x::FD{T, f}, y::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, x.i+y.i)
Base.:-(x::FD{T, f}, y::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, x.i-y.i)

# wide multiplication
Base.@pure function widemul(x::FD{<:Any, f}, y::FD{<:Any, g}) where {f, g}
Base.@pure function Base.widemul(x::FD{<:Any, f}, y::FD{<:Any, g}) where {f, g}
i = widemul(x.i, y.i)
reinterpret(FD{typeof(i), f + g}, i)
end
Base.@pure function widemul(x::FD{T, f}, y::Integer) where {T, f}
Base.@pure function Base.widemul(x::FD{T, f}, y::Integer) where {T, f}
i = widemul(x.i, y)
reinterpret(FD{typeof(i), f}, i)
end
Base.@pure widemul(x::Integer, y::FD) = widemul(y, x)
Base.@pure Base.widemul(x::Integer, y::FD) = widemul(y, x)

"""
_round_to_even(quotient, remainder, divisor)
Expand Down Expand Up @@ -160,48 +157,48 @@ _round_to_even(q, r, d) = _round_to_even(promote(q, r, d)...)
# multiplication rounds to nearest even representation
# TODO: can we use floating point to speed this up? after we build a
# correctness test suite.
function *(x::FD{T, f}, y::FD{T, f}) where {T, f}
function Base.:*(x::FD{T, f}, y::FD{T, f}) where {T, f}
powt = coefficient(FD{T, f})
quotient, remainder = fldmodinline(widemul(x.i, y.i), powt)
reinterpret(FD{T, f}, _round_to_even(quotient, remainder, powt))
end

# these functions are needed to avoid InexactError when converting from the
# integer type
*(x::Integer, y::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, T(x * y.i))
*(x::FD{T, f}, y::Integer) where {T, f} = reinterpret(FD{T, f}, T(x.i * y))
Base.:*(x::Integer, y::FD{T, f}) where {T, f} = reinterpret(FD{T, f}, T(x * y.i))
Base.:*(x::FD{T, f}, y::Integer) where {T, f} = reinterpret(FD{T, f}, T(x.i * y))

function /(x::FD{T, f}, y::FD{T, f}) where {T, f}
function Base.:/(x::FD{T, f}, y::FD{T, f}) where {T, f}
powt = coefficient(FD{T, f})
quotient, remainder = fldmod(widemul(x.i, powt), y.i)
reinterpret(FD{T, f}, T(_round_to_even(quotient, remainder, y.i)))
end

# These functions allow us to perform division with integers outside of the range of the
# FixedDecimal.
function /(x::Integer, y::FD{T, f}) where {T, f}
function Base.:/(x::Integer, y::FD{T, f}) where {T, f}
powt = coefficient(FD{T, f})
powtsq = widemul(powt, powt)
quotient, remainder = fldmod(widemul(x, powtsq), y.i)
reinterpret(FD{T, f}, T(_round_to_even(quotient, remainder, y.i)))
end

function /(x::FD{T, f}, y::Integer) where {T, f}
function Base.:/(x::FD{T, f}, y::Integer) where {T, f}
quotient, remainder = fldmod(x.i, y)
reinterpret(FD{T, f}, T(_round_to_even(quotient, remainder, y)))
end

# integerification
trunc(x::FD{T, f}) where {T, f} = FD{T, f}(div(x.i, coefficient(FD{T, f})))
floor(x::FD{T, f}) where {T, f} = FD{T, f}(fld(x.i, coefficient(FD{T, f})))
Base.trunc(x::FD{T, f}) where {T, f} = FD{T, f}(div(x.i, coefficient(FD{T, f})))
Base.floor(x::FD{T, f}) where {T, f} = FD{T, f}(fld(x.i, coefficient(FD{T, f})))

# TODO: round with number of digits; should be easy
function round(x::FD{T, f}, ::RoundingMode{:Nearest}=RoundNearest) where {T, f}
function Base.round(x::FD{T, f}, ::RoundingMode{:Nearest}=RoundNearest) where {T, f}
powt = coefficient(FD{T, f})
quotient, remainder = fldmodinline(x.i, powt)
FD{T, f}(_round_to_even(quotient, remainder, powt))
end
function ceil(x::FD{T, f}) where {T, f}
function Base.ceil(x::FD{T, f}) where {T, f}
powt = coefficient(FD{T, f})
quotient, remainder = fldmodinline(x.i, powt)
if remainder > 0
Expand Down Expand Up @@ -243,44 +240,44 @@ end
_apply_exact_float(f, ::Type{T}, x::Real, i::Integer) where T = f(T, x, i)

for fn in [:trunc, :floor, :ceil]
@eval ($fn(::Type{TI}, x::FD)::TI) where {TI <: Integer} = $fn(x)
@eval (Base.$fn(::Type{TI}, x::FD)::TI) where {TI <: Integer} = $fn(x)

# round/trunc/ceil/flooring to FD; generic
@eval function $fn(::Type{FD{T, f}}, x::Real) where {T, f}
@eval function Base.$fn(::Type{FD{T, f}}, x::Real) where {T, f}
powt = coefficient(FD{T, f})
# Use machine Float64 if possible, but fall back to BigFloat if we need
# more precision. 4f bits suffices.
val = _apply_exact_float($(Symbol(fn, "mul")), T, x, powt)
reinterpret(FD{T, f}, val)
end
end
function round(::Type{TI}, x::FD, ::RoundingMode{:Nearest}=RoundNearest) where {TI <: Integer}
function Base.round(::Type{TI}, x::FD, ::RoundingMode{:Nearest}=RoundNearest) where {TI <: Integer}
convert(TI, round(x))::TI
end
function round(::Type{FD{T, f}}, x::Real, ::RoundingMode{:Nearest}=RoundNearest) where {T, f}
function Base.round(::Type{FD{T, f}}, x::Real, ::RoundingMode{:Nearest}=RoundNearest) where {T, f}
reinterpret(FD{T, f}, round(T, x * coefficient(FD{T, f})))
end

# needed to avoid ambiguity
function round(::Type{FD{T, f}}, x::Rational, ::RoundingMode{:Nearest}=RoundNearest) where {T, f}
function Base.round(::Type{FD{T, f}}, x::Rational, ::RoundingMode{:Nearest}=RoundNearest) where {T, f}
reinterpret(FD{T, f}, round(T, x * coefficient(FD{T, f})))
end

# conversions and promotions
convert(::Type{FD{T, f}}, x::FD{T, f}) where {T, f} = x # Converting an FD to itself is a no-op
Base.convert(::Type{FD{T, f}}, x::FD{T, f}) where {T, f} = x # Converting an FD to itself is a no-op

function convert(::Type{FD{T, f}}, x::Integer) where {T, f}
function Base.convert(::Type{FD{T, f}}, x::Integer) where {T, f}
reinterpret(FD{T, f}, T(widemul(x, coefficient(FD{T, f}))))
end

convert(::Type{T}, x::AbstractFloat) where {T <: FD} = round(T, x)
Base.convert(::Type{T}, x::AbstractFloat) where {T <: FD} = round(T, x)

function convert(::Type{FD{T, f}}, x::Rational) where {T, f}
function Base.convert(::Type{FD{T, f}}, x::Rational) where {T, f}
powt = coefficient(FD{T, f})
reinterpret(FD{T, f}, T(x * powt))::FD{T, f}
end

function convert(::Type{FD{T, f}}, x::FD{U, g}) where {T, f, U, g}
function Base.convert(::Type{FD{T, f}}, x::FD{U, g}) where {T, f, U, g}
if f g
# Compute `10^(f - g)` without overflow
powt = div(coefficient(FD{T, f}), coefficient(FD{U, g}))
Expand All @@ -298,7 +295,7 @@ function convert(::Type{FD{T, f}}, x::FD{U, g}) where {T, f, U, g}
end

for remfn in [:rem, :mod, :mod1, :min, :max]
@eval $remfn(x::T, y::T) where {T <: FD} = reinterpret(T, $remfn(x.i, y.i))
@eval Base.$remfn(x::T, y::T) where {T <: FD} = reinterpret(T, $remfn(x.i, y.i))
end
# TODO: When we upgrade to a min julia version >=1.4 (i.e Julia 2.0), this block can be
# dropped in favor of three-argument `div`, below.
Expand All @@ -313,33 +310,33 @@ if VERSION >= v"1.4.0-"
Base.div(x::T, y::T, r::RoundingMode) where {T <: FD} = T(div(x.i, y.i, r))
end

convert(::Type{AbstractFloat}, x::FD) = convert(floattype(typeof(x)), x)
function convert(::Type{TF}, x::FD{T, f}) where {TF <: AbstractFloat, T, f}
Base.convert(::Type{AbstractFloat}, x::FD) = convert(floattype(typeof(x)), x)
function Base.convert(::Type{TF}, x::FD{T, f}) where {TF <: AbstractFloat, T, f}
convert(TF, x.i / coefficient(FD{T, f}))::TF
end

function convert(::Type{TF}, x::FD{T, f}) where {TF <: BigFloat, T, f}
function Base.convert(::Type{TF}, x::FD{T, f}) where {TF <: BigFloat, T, f}
convert(TF, BigInt(x.i) / BigInt(coefficient(FD{T, f})))::TF
end

function convert(::Type{TI}, x::FD{T, f}) where {TI <: Integer, T, f}
function Base.convert(::Type{TI}, x::FD{T, f}) where {TI <: Integer, T, f}
isinteger(x) || throw(InexactError(:convert, TI, x))
convert(TI, div(x.i, coefficient(FD{T, f})))::TI
end

function convert(::Type{TR}, x::FD{T, f}) where {TR <: Rational, T, f}
function Base.convert(::Type{TR}, x::FD{T, f}) where {TR <: Rational, T, f}
convert(TR, x.i // coefficient(FD{T, f}))::TR
end

(::Type{T})(x::FD) where {T<:Union{AbstractFloat,Integer,Rational}} = convert(T, x)

promote_rule(::Type{FD{T, f}}, ::Type{<:Integer}) where {T, f} = FD{T, f}
promote_rule(::Type{<:FD}, ::Type{TF}) where {TF <: AbstractFloat} = TF
promote_rule(::Type{<:FD}, ::Type{Rational{TR}}) where {TR} = Rational{TR}
Base.promote_rule(::Type{FD{T, f}}, ::Type{<:Integer}) where {T, f} = FD{T, f}
Base.promote_rule(::Type{<:FD}, ::Type{TF}) where {TF <: AbstractFloat} = TF
Base.promote_rule(::Type{<:FD}, ::Type{Rational{TR}}) where {TR} = Rational{TR}

# TODO: decide if these are the right semantics;
# right now we pick the bigger int type and the bigger decimal point
Base.@pure function promote_rule(::Type{FD{T, f}}, ::Type{FD{U, g}}) where {T, f, U, g}
Base.@pure function Base.promote_rule(::Type{FD{T, f}}, ::Type{FD{U, g}}) where {T, f, U, g}
FD{promote_type(T, U), max(f, g)}
end

Expand All @@ -348,24 +345,24 @@ Base.zero(::Type{FD{T, f}}) where {T, f} = reinterpret(FD{T, f}, zero(T))
Base.one(::Type{FD{T, f}}) where {T, f} = reinterpret(FD{T, f}, coefficient(FD{T, f}))

# comparison
==(x::T, y::T) where {T <: FD} = x.i == y.i
<(x::T, y::T) where {T <: FD} = x.i < y.i
<=(x::T, y::T) where {T <: FD} = x.i <= y.i
Base.:(==)(x::T, y::T) where {T <: FD} = x.i == y.i
Base.:( <)(x::T, y::T) where {T <: FD} = x.i < y.i
Base.:(<=)(x::T, y::T) where {T <: FD} = x.i <= y.i

# predicates and traits
isinteger(x::FD{T, f}) where {T, f} = rem(x.i, coefficient(FD{T, f})) == 0
typemin(::Type{FD{T, f}}) where {T, f} = reinterpret(FD{T, f}, typemin(T))
typemax(::Type{FD{T, f}}) where {T, f}= reinterpret(FD{T, f}, typemax(T))
eps(::Type{T}) where {T <: FD} = reinterpret(T, 1)
eps(x::FD) = eps(typeof(x))
floatmin(::Type{T}) where {T <: FD} = eps(T)
floatmax(::Type{T}) where {T <: FD} = typemax(T)
Base.isinteger(x::FD{T, f}) where {T, f} = rem(x.i, coefficient(FD{T, f})) == 0
Base.typemin(::Type{FD{T, f}}) where {T, f} = reinterpret(FD{T, f}, typemin(T))
Base.typemax(::Type{FD{T, f}}) where {T, f}= reinterpret(FD{T, f}, typemax(T))
Base.eps(::Type{T}) where {T <: FD} = reinterpret(T, 1)
Base.eps(x::FD) = eps(typeof(x))
Base.floatmin(::Type{T}) where {T <: FD} = eps(T)
Base.floatmax(::Type{T}) where {T <: FD} = typemax(T)

# printing
function print(io::IO, x::FD{T, 0}) where T
function Base.print(io::IO, x::FD{T, 0}) where T
print(io, x.i)
end
function print(io::IO, x::FD{T, f}) where {T, f}
function Base.print(io::IO, x::FD{T, f}) where {T, f}
iscompact = get(io, :compact, false)

# note: a is negative if x.i == typemin(x.i)
Expand All @@ -387,7 +384,7 @@ function print(io::IO, x::FD{T, f}) where {T, f}
print(io, integer, '.', fractionchars)
end

function show(io::IO, x::FD{T, f}) where {T, f}
function Base.show(io::IO, x::FD{T, f}) where {T, f}
iscompact = get(io, :compact, false)
if !iscompact
print(io, "FixedDecimal{$T,$f}(")
Expand All @@ -407,7 +404,7 @@ Raises an `InexactError` if any rounding is necessary.
"""
const RoundThrows = RoundingMode{:Throw}()

function parse(::Type{FD{T, f}}, str::AbstractString, mode::RoundingMode=RoundNearest) where {T, f}
function Base.parse(::Type{FD{T, f}}, str::AbstractString, mode::RoundingMode=RoundNearest) where {T, f}
if !(mode in [RoundThrows, RoundNearest, RoundToZero])
throw(ArgumentError("Unhandled rounding mode $mode"))
end
Expand Down Expand Up @@ -514,6 +511,6 @@ Base.@pure coefficient(fd::FD{T, f}) where {T, f} = coefficient(FD{T, f})
value(fd::FD) = fd.i

# for generic hashing
decompose(fd::FD) = decompose(Rational(fd))
Base.decompose(fd::FD) = decompose(Rational(fd))

end

0 comments on commit fd1402b

Please sign in to comment.