Skip to content

Commit 402e637

Browse files
authored
Merge pull request #158 from kimikage/round
Add rounding functions for `Fixed` (Fixes #153)
2 parents ca6e304 + 265d0af commit 402e637

File tree

5 files changed

+198
-50
lines changed

5 files changed

+198
-50
lines changed

src/FixedPointNumbers.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,15 @@ for f in (:+, :-, :rem, :mod, :mod1, :min, :max)
139139
$f(x::X, y::X) where {X <: FixedPoint} = X($f(x.i, y.i), 0)
140140
end
141141
end
142+
for (m, f) in ((:(:Nearest), :round),
143+
(:(:ToZero), :trunc),
144+
(:(:Up), :ceil),
145+
(:(:Down), :floor))
146+
@eval begin
147+
round(x::FixedPoint, ::RoundingMode{$m}) = $f(x)
148+
round(::Type{Ti}, x::FixedPoint, ::RoundingMode{$m}) where {Ti <: Integer} = $f(Ti, x)
149+
end
150+
end
142151

143152
# Printing. These are used to generate type-symbols, so we need them
144153
# before we include any files.

src/fixed.jl

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ function rawone(::Type{Fixed{T,f}}) where {T, f}
5050
oneunit(T) << f
5151
end
5252

53+
intmask(::Fixed{T,f}) where {T, f} = -oneunit(T) << f # Signed
54+
fracmask(x::Fixed{T,f}) where {T, f} = ~intmask(x) # Signed
55+
5356
# unchecked arithmetic
5457

5558
# with truncation:
@@ -91,6 +94,73 @@ end
9194
(::Type{TR})(x::Fixed{T,f}) where {TR <: Rational,T,f} =
9295
TR(x.i>>f + (x.i&(1<<f-1))//(one(widen1(T))<<f))
9396

97+
function trunc(x::Fixed{T,f}) where {T, f}
98+
f == 0 && return x
99+
f == bitwidth(T) && return zero(x) # TODO: remove this line
100+
f == bitwidth(T) - 1 && return x.i == typemin(T) ? x : zero(x)
101+
t = x.i & intmask(x)
102+
r = x.i & fracmask(x)
103+
_rawone = oneunit(T) << f
104+
reinterpret(Fixed{T,f}, (x.i < 0) & (r != 0) ? t + _rawone : t)
105+
end
106+
function floor(x::Fixed{T,f}) where {T, f}
107+
f == bitwidth(T) && x.i < 0 && throw_converterror(Fixed{T,f}, -1) # TODO: remove this line
108+
Fixed{T,f}(x.i & intmask(x), 0)
109+
end
110+
function ceil(x::Fixed{T,f}) where {T, f}
111+
f == 0 && return x
112+
upper = typemax(T) & intmask(x)
113+
x.i > upper && throw_converterror(Fixed{T,f}, ceil(float(x)))
114+
reinterpret(Fixed{T,f}, (x.i + fracmask(x)) & intmask(x))
115+
end
116+
function round(x::Fixed{T,f}) where {T, f}
117+
f == 0 && return x
118+
f == bitwidth(T) && return zero(x) # TODO: remove this line
119+
upper = intmask(x) >>> 0x1
120+
lower = intmask(x) >> 0x1
121+
if f == bitwidth(T) - 1
122+
x.i > upper && throw_converterror(Fixed{T,f}, @exp2(bitwidth(T)-f-1))
123+
return x.i < lower ? typemin(x) : zero(x)
124+
end
125+
x.i >= upper && throw_converterror(Fixed{T,f}, @exp2(bitwidth(T)-f-1))
126+
y = oneunit(T) << UInt8(f - 1) + x.i
127+
m = oneunit(T) << UInt8(f + 1) - oneunit(T)
128+
z = y & intmask(x)
129+
reinterpret(Fixed{T,f}, z - T(y & m == rawone(x)) << f)
130+
end
131+
132+
function trunc(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f}
133+
f == 0 && return convert(Ti, x.i)
134+
f == bitwidth(T) && return zero(Ti) # TODO: remove this line
135+
f == bitwidth(T) - 1 && return x.i == typemin(T) ? convert(Ti, -1) : zero(Ti)
136+
t = x.i >> f
137+
r = x.i & fracmask(x)
138+
convert(Ti, (x.i < 0) & (r != 0) ? t + oneunit(T) : t)
139+
end
140+
function floor(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f}
141+
f == bitwidth(T) && return x.i < 0 ? convert(Ti, -1) : zero(Ti) # TODO: remove this line
142+
convert(Ti, x.i >> f)
143+
end
144+
function ceil(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f}
145+
f == bitwidth(T) && return x.i > 0 ? oneunit(Ti) : zero(Ti) # TODO: remove this line
146+
y = x.i + fracmask(x)
147+
convert(Ti, x.i >= 0 ? y >>> f : y >> f)
148+
end
149+
function round(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f}
150+
f == 0 && return convert(Ti, x.i)
151+
f == bitwidth(T) && return zero(Ti) # TODO: remove this line
152+
upper = intmask(x) >>> 0x1
153+
lower = intmask(x) >> 0x1
154+
if f == bitwidth(T) - 1
155+
x.i < lower && return convert(Ti, -1)
156+
return x.i > upper ? oneunit(Ti) : zero(Ti)
157+
end
158+
y = oneunit(T) << UInt8(f - 1) + x.i
159+
m = oneunit(T) << UInt8(f + 1) - oneunit(T)
160+
z = x.i >= 0 ? y >>> f : y >> f
161+
convert(Ti, z - Ti(y & m == rawone(x)))
162+
end
163+
94164
promote_rule(ft::Type{Fixed{T,f}}, ::Type{TI}) where {T,f,TI <: Integer} = Fixed{T,f}
95165
promote_rule(::Type{Fixed{T,f}}, ::Type{TF}) where {T,f,TF <: AbstractFloat} = TF
96166
promote_rule(::Type{Fixed{T,f}}, ::Type{Rational{TR}}) where {T,f,TR} = Rational{TR}

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/fixed.jl

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,18 @@ end
7070
@test reinterpret(Int8, 0.5Q0f7) === signed(0x40)
7171
end
7272

73+
@testset "masks" begin
74+
@test FixedPointNumbers.intmask(0Q7f0) === signed(0xFF)
75+
@test FixedPointNumbers.intmask(0Q6f1) === signed(0xFE)
76+
@test FixedPointNumbers.intmask(0Q1f6) === signed(0xC0)
77+
@test FixedPointNumbers.intmask(0Q0f7) === signed(0x80)
78+
79+
@test FixedPointNumbers.fracmask(0Q7f0) === signed(0x00)
80+
@test FixedPointNumbers.fracmask(0Q6f1) === signed(0x01)
81+
@test FixedPointNumbers.fracmask(0Q1f6) === signed(0x3F)
82+
@test FixedPointNumbers.fracmask(0Q0f7) === signed(0x7F)
83+
end
84+
7385
@testset "inexactness" begin
7486
@test_throws InexactError Q0f7(-2)
7587
# TODO: change back to InexactError when it allows message strings
@@ -96,6 +108,56 @@ end
96108
end
97109
end
98110

111+
@testset "rounding" begin
112+
for T in (Int8, Int16, Int32, Int64)
113+
rs = vcat([ oneunit(T) << b - oneunit(T) for b = 0:bitwidth(T)-1],
114+
[ oneunit(T) << b for b = 1:bitwidth(T)-2],
115+
[ oneunit(T) << b + oneunit(T) for b = 2:bitwidth(T)-2],
116+
[-oneunit(T) << b - oneunit(T) for b = 2:bitwidth(T)-2],
117+
[-oneunit(T) << b for b = 1:bitwidth(T)-1],
118+
[-oneunit(T) << b + oneunit(T) for b = 1:bitwidth(T)-1])
119+
@testset "rounding Fixed{$T,$f}" for f = 0:bitwidth(T)-1
120+
F = Fixed{T,f}
121+
xs = (reinterpret(F, r) for r in rs)
122+
@test all(x -> trunc(x) == trunc(float(x)), xs)
123+
@test all(x -> floor(float(x)) < typemin(F) || floor(x) == floor(float(x)), xs)
124+
@test all(x -> ceil(float(x)) > typemax(F) || ceil(x) == ceil(float(x)), xs)
125+
@test all(x -> round(float(x)) > typemax(F) || round(x) == round(float(x)), xs)
126+
@test all(x -> trunc(Int64, x) === trunc(Int64, float(x)), xs)
127+
@test all(x -> floor(Int64, x) === floor(Int64, float(x)), xs)
128+
@test all(x -> ceil(Int64, x) === ceil(Int64, float(x)), xs)
129+
@test all(x -> round(Int64, x) === round(Int64, float(x)), xs)
130+
end
131+
end
132+
@testset "rounding Fixed{Int16,$f} with overflow" for f = 1:16 # TODO: drop 16
133+
F = Fixed{Int16,f}
134+
@test_throws ArgumentError ceil(typemax(F))
135+
if f == 16
136+
@test_throws ArgumentError ceil(eps(F))
137+
elseif f == 15
138+
@test_throws ArgumentError ceil(eps(F))
139+
@test_throws ArgumentError round(typemax(F))
140+
@test_throws ArgumentError round(F(0.5) + eps(F))
141+
else
142+
@test_throws ArgumentError ceil(typemin(F) - oneunit(F) + eps(F))
143+
@test_throws ArgumentError round(typemax(F))
144+
@test_throws ArgumentError round(typemax(F) - F(0.5) + eps(F))
145+
end
146+
end
147+
@testset "rounding mode" begin
148+
@test round(-1.5Q1f6, RoundNearest) === -2Q1f6
149+
@test round(-1.5Q1f6, RoundToZero) === -1Q1f6
150+
@test round(-1.5Q1f6, RoundUp) === -1Q1f6
151+
@test round(-1.5Q1f6, RoundDown) === -2Q1f6
152+
@test round(Int, -1.5Q1f6, RoundNearest) === -2
153+
@test round(Int, -1.5Q1f6, RoundToZero) === -1
154+
@test round(Int, -1.5Q1f6, RoundUp) === -1
155+
@test round(Int, -1.5Q1f6, RoundDown) === -2
156+
end
157+
@test_throws InexactError trunc(UInt, typemin(Q0f7))
158+
@test_throws InexactError floor(UInt, -eps(Q0f7))
159+
end
160+
99161
@testset "modulus" begin
100162
T = Fixed{Int8,7}
101163
for i = -1.0:0.1:typemax(T)

test/normed.jl

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -240,41 +240,39 @@ 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))
266+
end
267+
@testset "rounding mode" begin
268+
@test round(1.504N1f7, RoundNearest) === 2N1f7
269+
@test round(1.504N1f7, RoundToZero) === 1N1f7
270+
@test round(1.504N1f7, RoundUp) === 2N1f7
271+
@test round(1.504N1f7, RoundDown) === 1N1f7
272+
@test round(Int, 1.504N1f7, RoundNearest) === 2
273+
@test round(Int, 1.504N1f7, RoundToZero) === 1
274+
@test round(Int, 1.504N1f7, RoundUp) === 2
275+
@test round(Int, 1.504N1f7, RoundDown) === 1
278276
end
279277
end
280278

0 commit comments

Comments
 (0)