Skip to content

Commit 9bcca82

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

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
@@ -235,26 +235,35 @@ abs(x::Normed) = x
235235
/(x::T, y::T) where {T <: Normed} = convert(T,convert(floattype(T), x)/convert(floattype(T), y))
236236

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

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

259268
isfinite(x::Normed) = true
260269
isnan(x::Normed) = false

test/normed.jl

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -233,41 +233,29 @@ end
233233
end
234234
end
235235

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

0 commit comments

Comments
 (0)