|
| 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 | + |
1 | 24 | module FixedPointDecimals
|
2 | 25 |
|
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 |
4 | 226 |
|
5 |
| -end # module |
| 227 | +end |
0 commit comments