Skip to content

Commit 5f7093b

Browse files
authored
Merge pull request #78 from bachdavi/dba-implement-all-rounding-methods
Implement `round` methods for all `RoundingMode`s
2 parents 1328b9a + e918e51 commit 5f7093b

File tree

2 files changed

+87
-17
lines changed

2 files changed

+87
-17
lines changed

src/FixedPointDecimals.jl

Lines changed: 39 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -125,24 +125,39 @@ end
125125
Base.widemul(x::Integer, y::FD) = widemul(y, x)
126126

127127
"""
128-
_round_to_even(quotient, remainder, divisor)
129-
130-
Round `quotient + remainder / divisor` to the nearest even integer, given that
131-
`0 ≤ remainder < divisor` or `0 ≥ remainder > divisor`. (This assumption is
132-
satisfied by the return value of `fldmod` in all cases, and the return value of
133-
`divrem` in cases where `divisor` is known to be positive.)
128+
_round_to_nearest(quotient, remainder, divisor, ::RoundingMode{M})
129+
130+
Round `quotient + remainder / divisor` to the nearest integer,
131+
given that `0 ≤ remainder < divisor` or `0 ≥ remainder >
132+
divisor`. (This assumption is satisfied by the return value of
133+
`fldmod` in all cases, and the return value of `divrem` in cases where
134+
`divisor` is known to be positive.) The tie is decided depending on
135+
the `RoundingMode`.
134136
"""
135-
function _round_to_even(quotient::T, remainder::T, divisor::T) where {T <: Integer}
137+
function _round_to_nearest(quotient::T,
138+
remainder::T,
139+
divisor::T,
140+
::RoundingMode{M}=RoundNearest) where {T <: Integer, M}
136141
halfdivisor = divisor >> 1
137142
if iseven(divisor) && remainder == halfdivisor
138-
ifelse(iseven(quotient), quotient, quotient + one(quotient))
143+
# `:NearestTiesAway` will tie away from zero, e.g. -8.5 ->
144+
# -9. `:NearestTiesUp` will always ties towards positive
145+
# infinity. `:Nearest` will tie towards the nearest even
146+
# integer.
147+
if M == :NearestTiesAway
148+
ifelse(quotient < zero(quotient), quotient, quotient + one(quotient))
149+
elseif M == :Nearest
150+
ifelse(iseven(quotient), quotient, quotient + one(quotient))
151+
else
152+
quotient + one(quotient)
153+
end
139154
elseif abs(remainder) > abs(halfdivisor)
140155
quotient + one(quotient)
141156
else
142157
quotient
143158
end
144159
end
145-
_round_to_even(q, r, d) = _round_to_even(promote(q, r, d)...)
160+
_round_to_nearest(q, r, d, m=RoundNearest) = _round_to_nearest(promote(q, r, d)..., m)
146161

147162
# In many of our calls to fldmod, `y` is a constant (the coefficient, 10^f). However, since
148163
# `fldmod` is sometimes not being inlined, that constant information is not available to the
@@ -156,7 +171,7 @@ _round_to_even(q, r, d) = _round_to_even(promote(q, r, d)...)
156171
function Base.:*(x::FD{T, f}, y::FD{T, f}) where {T, f}
157172
powt = coefficient(FD{T, f})
158173
quotient, remainder = fldmodinline(widemul(x.i, y.i), powt)
159-
reinterpret(FD{T, f}, _round_to_even(quotient, remainder, powt))
174+
reinterpret(FD{T, f}, _round_to_nearest(quotient, remainder, powt))
160175
end
161176

162177
# these functions are needed to avoid InexactError when converting from the
@@ -167,7 +182,7 @@ Base.:*(x::FD{T, f}, y::Integer) where {T, f} = reinterpret(FD{T, f}, T(x.i * y)
167182
function Base.:/(x::FD{T, f}, y::FD{T, f}) where {T, f}
168183
powt = coefficient(FD{T, f})
169184
quotient, remainder = fldmod(widemul(x.i, powt), y.i)
170-
reinterpret(FD{T, f}, T(_round_to_even(quotient, remainder, y.i)))
185+
reinterpret(FD{T, f}, T(_round_to_nearest(quotient, remainder, y.i)))
171186
end
172187

173188
# These functions allow us to perform division with integers outside of the range of the
@@ -176,23 +191,30 @@ function Base.:/(x::Integer, y::FD{T, f}) where {T, f}
176191
powt = coefficient(FD{T, f})
177192
powtsq = widemul(powt, powt)
178193
quotient, remainder = fldmod(widemul(x, powtsq), y.i)
179-
reinterpret(FD{T, f}, T(_round_to_even(quotient, remainder, y.i)))
194+
reinterpret(FD{T, f}, T(_round_to_nearest(quotient, remainder, y.i)))
180195
end
181196

182197
function Base.:/(x::FD{T, f}, y::Integer) where {T, f}
183198
quotient, remainder = fldmod(x.i, y)
184-
reinterpret(FD{T, f}, T(_round_to_even(quotient, remainder, y)))
199+
reinterpret(FD{T, f}, T(_round_to_nearest(quotient, remainder, y)))
185200
end
186201

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

206+
Base.round(fd::FD, ::RoundingMode{:Up}) = ceil(fd)
207+
Base.round(fd::FD, ::RoundingMode{:Down}) = floor(fd)
208+
Base.round(fd::FD, ::RoundingMode{:ToZero}) = trunc(fd)
209+
191210
# TODO: round with number of digits; should be easy
192-
function Base.round(x::FD{T, f}, ::RoundingMode{:Nearest}=RoundNearest) where {T, f}
211+
function Base.round(x::FD{T, f},
212+
m::Union{RoundingMode{:Nearest},
213+
RoundingMode{:NearestTiesUp},
214+
RoundingMode{:NearestTiesAway}}=RoundNearest) where {T, f}
193215
powt = coefficient(FD{T, f})
194216
quotient, remainder = fldmodinline(x.i, powt)
195-
FD{T, f}(_round_to_even(quotient, remainder, powt))
217+
FD{T, f}(_round_to_nearest(quotient, remainder, powt, m))
196218
end
197219
function Base.ceil(x::FD{T, f}) where {T, f}
198220
powt = coefficient(FD{T, f})
@@ -247,8 +269,8 @@ for fn in [:trunc, :floor, :ceil]
247269
reinterpret(FD{T, f}, val)
248270
end
249271
end
250-
function Base.round(::Type{TI}, x::FD, ::RoundingMode{:Nearest}=RoundNearest) where {TI <: Integer}
251-
convert(TI, round(x))::TI
272+
function Base.round(::Type{TI}, x::FD, m::RoundingMode=RoundNearest) where {TI <: Integer}
273+
convert(TI, round(x,m))::TI
252274
end
253275
function Base.round(::Type{FD{T, f}}, x::Real, ::RoundingMode{:Nearest}=RoundNearest) where {T, f}
254276
reinterpret(FD{T, f}, round(T, x * coefficient(FD{T, f})))

test/runtests.jl

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -605,6 +605,54 @@ end
605605
@test round(Int, FD2(1.50)) === 2
606606
end
607607

608+
# Is alias for `ceil`.
609+
@testset "up" begin
610+
@test round(Int, FD2(-0.51), RoundUp) === 0
611+
@test round(Int, FD2(-0.50), RoundUp) === 0
612+
@test round(Int, FD2(-0.49), RoundUp) === 0
613+
@test round(Int, FD2(0.50), RoundUp) === 1
614+
@test round(Int, FD2(0.51), RoundUp) === 1
615+
@test round(Int, FD2(1.50), RoundUp) === 2
616+
end
617+
618+
# Is alias for `floor`.
619+
@testset "down" begin
620+
@test round(Int, FD2(-0.51), RoundDown) === -1
621+
@test round(Int, FD2(-0.50), RoundDown) === -1
622+
@test round(Int, FD2(-0.49), RoundDown) === -1
623+
@test round(Int, FD2(0.50), RoundDown) === 0
624+
@test round(Int, FD2(0.51), RoundDown) === 0
625+
@test round(Int, FD2(1.50), RoundDown) === 1
626+
end
627+
628+
# Is alias for `trunc`.
629+
@testset "to zero" begin
630+
@test round(Int, FD2(-0.51), RoundToZero) === 0
631+
@test round(Int, FD2(-0.50), RoundToZero) === 0
632+
@test round(Int, FD2(-0.49), RoundToZero) === 0
633+
@test round(Int, FD2(0.50), RoundToZero) === 0
634+
@test round(Int, FD2(0.51), RoundToZero) === 0
635+
@test round(Int, FD2(1.50), RoundToZero) === 1
636+
end
637+
638+
@testset "tie away" begin
639+
@test round(Int, FD2(-0.51), RoundNearestTiesAway) === -1
640+
@test round(Int, FD2(-0.50), RoundNearestTiesAway) === -1
641+
@test round(Int, FD2(-0.49), RoundNearestTiesAway) === 0
642+
@test round(Int, FD2(0.50), RoundNearestTiesAway) === 1
643+
@test round(Int, FD2(0.51), RoundNearestTiesAway) === 1
644+
@test round(Int, FD2(1.50), RoundNearestTiesAway) === 2
645+
end
646+
647+
@testset "tie up" begin
648+
@test round(Int, FD2(-0.51), RoundNearestTiesUp) === -1
649+
@test round(Int, FD2(-0.50), RoundNearestTiesUp) === 0
650+
@test round(Int, FD2(-0.49), RoundNearestTiesUp) === 0
651+
@test round(Int, FD2(0.50), RoundNearestTiesUp) === 1
652+
@test round(Int, FD2(0.51), RoundNearestTiesUp) === 1
653+
@test round(Int, FD2(1.50), RoundNearestTiesUp) === 2
654+
end
655+
608656
@testset "rounding invariant $x" for x in filter(!islarge, keyvalues[FD2])
609657
@test isinteger(round(x))
610658
@test x - FD2(1//2) round(x) x + FD2(1//2)

0 commit comments

Comments
 (0)