Skip to content

Commit 1e0c5f5

Browse files
committed
Fix overflow problems with round and ceil for Normed
This also simplifies the test for rounding functions.
1 parent ca6e304 commit 1e0c5f5

File tree

2 files changed

+47
-50
lines changed

2 files changed

+47
-50
lines changed

src/normed.jl

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -238,26 +238,35 @@ abs(x::Normed) = x
238238
/(x::T, y::T) where {T <: Normed} = convert(T,convert(floattype(T), x)/convert(floattype(T), y))
239239

240240
# Functions
241-
trunc(x::T) where {T <: Normed} = T(div(reinterpret(x), rawone(T))*rawone(T),0)
242-
floor(x::T) where {T <: Normed} = trunc(x)
243-
function round(x::Normed{T,f}) where {T,f}
244-
mask = convert(T, 1<<(f-1))
245-
y = trunc(x)
246-
return convert(T, reinterpret(x)-reinterpret(y)) & mask>0 ?
247-
Normed{T,f}(y+oneunit(Normed{T,f})) : y
241+
trunc(x::N) where {N <: Normed} = floor(x)
242+
floor(x::N) where {N <: Normed} = reinterpret(N, x.i - x.i % rawone(N))
243+
function ceil(x::Normed{T,f}) where {T, f}
244+
f == 1 && return x
245+
if typemax(T) % rawone(x) != 0
246+
upper = typemax(T) - typemax(T) % rawone(x)
247+
x.i > upper && throw_converterror(Normed{T,f}, ceil(T, typemax(x)))
248+
end
249+
r = x.i % rawone(x)
250+
reinterpret(Normed{T,f}, x.i - r + (r > 0 ? rawone(x) : zero(T)))
248251
end
249-
function ceil(x::Normed{T,f}) where {T,f}
250-
k = bitwidth(T)-f
251-
mask = (typemax(T)<<k)>>k
252-
y = trunc(x)
253-
return convert(T, reinterpret(x)-reinterpret(y)) & (mask)>0 ?
254-
Normed{T,f}(y+oneunit(Normed{T,f})) : y
252+
function round(x::Normed{T,f}) where {T, f}
253+
r = x.i % rawone(x)
254+
q = rawone(x) - r
255+
reinterpret(Normed{T,f}, r > q ? x.i + q : x.i - r)
255256
end
256257

257-
trunc(::Type{T}, x::Normed) where {T <: Integer} = convert(T, div(reinterpret(x), rawone(x)))
258-
round(::Type{T}, x::Normed) where {T <: Integer} = round(T, reinterpret(x)/rawone(x))
259-
floor(::Type{T}, x::Normed) where {T <: Integer} = trunc(T, x)
260-
ceil(::Type{T}, x::Normed) where {T <: Integer} = ceil(T, reinterpret(x)/rawone(x))
258+
trunc(::Type{Ti}, x::Normed) where {Ti <: Integer} = floor(Ti, x)
259+
function floor(::Type{Ti}, x::Normed) where {Ti <: Integer}
260+
convert(Ti, reinterpret(x) ÷ rawone(x))
261+
end
262+
function ceil(::Type{Ti}, x::Normed) where {Ti <: Integer}
263+
d, r = divrem(x.i, rawone(x))
264+
convert(Ti, r > 0 ? d + oneunit(rawtype(x)) : d)
265+
end
266+
function round(::Type{Ti}, x::Normed) where {Ti <: Integer}
267+
d, r = divrem(x.i, rawone(x))
268+
convert(Ti, r > (rawone(x) >> 0x1) ? d + oneunit(rawtype(x)) : d)
269+
end
261270

262271
isfinite(x::Normed) = true
263272
isnan(x::Normed) = false

test/normed.jl

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -240,41 +240,29 @@ end
240240
end
241241
end
242242

243-
function testtrunc(inc::T) where {T}
244-
incf = convert(Float64, inc)
245-
tm = reinterpret(typemax(T))/reinterpret(one(T))
246-
local x = zero(T)
247-
for i = 0 : min(1e6, reinterpret(typemax(T))-1)
248-
xf = incf*i
249-
try
250-
@test typeof(trunc(x)) == T
251-
@test trunc(x) == trunc(xf)
252-
@test typeof(round(x)) == T
253-
@test round(x) == round(xf)
254-
cxf = ceil(xf)
255-
if cxf < tm
256-
@test typeof(ceil(x)) == T
257-
@test ceil(x) == ceil(xf)
258-
end
259-
@test typeof(floor(x)) == T
260-
@test floor(x) == floor(xf)
261-
@test trunc(Int,x) == trunc(Int,xf)
262-
@test round(Int,x) == round(Int,xf)
263-
@test floor(Int,x) == floor(Int,xf)
264-
if cxf < tm
265-
@test ceil(Int,x) == ceil(Int,xf)
266-
end
267-
catch err
268-
println("Failed on x = ", x, ", xf = ", xf)
269-
rethrow(err)
243+
@testset "rounding" begin
244+
for T in (UInt8, UInt16, UInt32, UInt64)
245+
rs = vcat([ oneunit(T) << b - oneunit(T) << 1 for b = 1:bitwidth(T)],
246+
[ oneunit(T) << b - oneunit(T) for b = 1:bitwidth(T)],
247+
[ oneunit(T) << b for b = 2:bitwidth(T)-1])
248+
@testset "rounding Normed{$T,$f}" for f = 1:bitwidth(T)
249+
N = Normed{T,f}
250+
xs = (reinterpret(N, r) for r in rs)
251+
@test all(x -> trunc(x) == trunc(float(x)), xs)
252+
@test all(x -> floor(x) == floor(float(x)), xs)
253+
# force `Normed` comparison avoiding rounding errors
254+
@test all(x -> ceil(float(x)) > typemax(N) || ceil(x) == N(ceil(float(x))), xs)
255+
@test all(x -> round(x) == round(float(x)), xs)
256+
@test all(x -> trunc(UInt64, x) === trunc(UInt64, float(x)), xs)
257+
@test all(x -> floor(UInt64, x) === floor(UInt64, float(x)), xs)
258+
@test all(x -> ceil(UInt64, x) === ceil(UInt64, float(x)), xs)
259+
@test all(x -> round(UInt64, x) === round(UInt64, float(x)), xs)
270260
end
271-
x = convert(T, x+inc)
272261
end
273-
end
274-
275-
@testset "trunc" begin
276-
for T in (FixedPointNumbers.UF..., UF2...)
277-
testtrunc(eps(T))
262+
@testset "rounding Normed{Int16,$f} with overflow" for f in filter(x->!ispow2(x), 1:16)
263+
N = Normed{UInt16,f}
264+
@test_throws ArgumentError ceil(typemax(N))
265+
@test_throws ArgumentError ceil(floor(typemax(N)) + eps(N))
278266
end
279267
end
280268

0 commit comments

Comments
 (0)