Skip to content

Commit 81ed5dd

Browse files
TotalVerbomus
authored andcommitted
Fix overflow issues with division
1 parent 4dd5492 commit 81ed5dd

File tree

2 files changed

+46
-30
lines changed

2 files changed

+46
-30
lines changed

src/FixedPointDecimals.jl

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,6 @@ import Base: reinterpret, zero, one, abs, sign, ==, <, <=, +, -, /, *, div,
3434
typemin, typemax, realmin, realmax, print, show, string, convert, parse,
3535
promote_rule, min, max, trunc, round, floor, ceil, eps, float, widemul
3636

37-
import Base.Checked: checked_mul
38-
3937
const IEEEFloat = Union{Float16, Float32, Float64}
4038

4139
for fn in [:trunc, :floor, :ceil]
@@ -109,18 +107,22 @@ Base.@pure function widemul{T, f}(x::FD{T, f}, y::Integer)
109107
end
110108
Base.@pure widemul(x::Integer, y::FD) = widemul(y, x)
111109

112-
function _round_to_even(quotient, remainder, powt)
113-
if powt == 1
114-
quotient
110+
"""
111+
_round_to_even(quotient, remainder, divisor)
112+
113+
Round `quotient + remainder / divisor` to the nearest even integer, given that
114+
`0 ≤ remainder < divisor` or `0 ≥ remainder > divisor`. (This assumption is
115+
satisfied by the return value of `fldmod` in all cases, and the return value of
116+
`divrem` in cases where `divisor` is known to be positive.)
117+
"""
118+
function _round_to_even(quotient, remainder, divisor)
119+
halfdivisor = divisor >> 1
120+
if iseven(divisor) && remainder == halfdivisor
121+
ifelse(iseven(quotient), quotient, quotient + one(quotient))
122+
elseif abs(remainder) > abs(halfdivisor)
123+
quotient + one(quotient)
115124
else
116-
halfpowt = powt >> 1
117-
if remainder == halfpowt
118-
ifelse(iseven(quotient), quotient, quotient + one(quotient))
119-
elseif remainder < halfpowt
120-
quotient
121-
else
122-
quotient + one(quotient)
123-
end
125+
quotient
124126
end
125127
end
126128

@@ -138,26 +140,33 @@ end
138140
*{T, f}(x::Integer, y::FD{T, f}) = reinterpret(FD{T, f}, T(x * y.i))
139141
*{T, f}(x::FD{T, f}, y::Integer) = reinterpret(FD{T, f}, T(x.i * y))
140142

141-
# TODO. this is probably wrong sometimes.
142143
function /{T, f}(x::FD{T, f}, y::FD{T, f})
143144
powt = T(10)^f
144-
quotient, remainder = divrem(x.i, y.i)
145-
reinterpret(FD{T, f}, quotient * powt + round(T, remainder / y.i * powt))
145+
quotient, remainder = fldmod(widemul(x.i, powt), widen(y.i))
146+
reinterpret(FD{T, f}, T(_round_to_even(quotient, remainder, widen(y.i))))
146147
end
147148

148149
# these functions are needed to avoid InexactError when converting from the integer type
149150
function /{T, f}(x::Integer, y::FD{T, f})
150-
powt = T(10)^f
151-
xi, yi = checked_mul(x, powt), y.i
152-
quotient, remainder = divrem(xi, yi)
153-
reinterpret(FD{T, f}, quotient * powt + round(T, remainder / yi * powt))
151+
S = promote_type(typeof(x), T)
152+
xi, yi = promote(x, y.i)
153+
154+
# The integer part of our result is x.i * 10^2f / y.i, so we need to
155+
# double-widen to get a precise result.
156+
powt = S(10)^f
157+
powtsq = widemul(powt, powt)
158+
quotient, remainder = fldmod(widemul(widen(xi), powtsq), widen(widen(yi)))
159+
160+
reinterpret(FD{T, f},
161+
T(_round_to_even(quotient, remainder, widen(widen(yi)))))
154162
end
155163

156164
function /{T, f}(x::FD{T, f}, y::Integer)
157-
powt = T(10)^f
158-
xi, yi = x.i, checked_mul(y, powt)
159-
quotient, remainder = divrem(xi, yi)
160-
reinterpret(FD{T, f}, quotient * powt + round(T, remainder / yi * powt))
165+
S = promote_type(T, typeof(y))
166+
xi, yi = promote(x.i, y)
167+
168+
quotient, remainder = fldmod(xi, yi)
169+
reinterpret(FD{T, f}, T(_round_to_even(quotient, remainder, yi)))
161170
end
162171

163172
# integerification

test/runtests.jl

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,13 @@ end
198198
@testset "division by 1" begin
199199
@testset for x in keyvalues[FD2]
200200
@test x / one(x) == x
201-
@test x / -one(x) == -x
201+
202+
# signed integers using two's complement have one additional negative value
203+
if x < 0 && x == typemin(x)
204+
@test_throws InexactError x / -one(x)
205+
else
206+
@test x / -one(x) == -x
207+
end
202208
end
203209
end
204210

@@ -262,17 +268,18 @@ end
262268
@test 129 / FD2(200) == FD2(0.64)
263269
@test -129 / FD2(200) == FD2(-0.64)
264270

265-
# Use of Float or BigFloat internally can change the calculated result
271+
# Use of Float or BigFloat internally should not change the calculated
272+
# result
266273
@test round(Int, 109 / 200 * 100) == 55
267274
@test round(Int, BigInt(109) / 200 * 100) == 54 # Correct
268275

269276
x = FD{Int128,2}(1.09)
270-
@test x / Int128(2) != x / BigInt(2)
271-
@test x / FD{Int128,2}(2) == x / Int128(2)
277+
@test x / Int128(2) == x / BigInt(2) == FD2(0.54)
278+
@test x / FD{Int128,2}(2) == x / Int128(2) == FD2(0.54)
272279

273280
y = FD{Int128,2}(200)
274-
@test Int128(109) / y != BigInt(109) / y
275-
@test FD{Int128,2}(109) / y == Int128(109) / y
281+
@test Int128(109) / y == BigInt(109) / y == FD2(0.54)
282+
@test FD{Int128,2}(109) / y == Int128(109) / y == FD2(0.54)
276283
end
277284

278285
@testset "without promotion" begin

0 commit comments

Comments
 (0)