Skip to content

Commit 08d9ead

Browse files
committed
Add rounding functions for Fixed
The functions support `Fixed{Int8,8}` and so on, for now.
1 parent 1e0c5f5 commit 08d9ead

File tree

2 files changed

+122
-0
lines changed

2 files changed

+122
-0
lines changed

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}

test/fixed.jl

Lines changed: 52 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,46 @@ 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+
@test_throws InexactError trunc(UInt, typemin(Q0f7))
148+
@test_throws InexactError floor(UInt, -eps(Q0f7))
149+
end
150+
99151
@testset "modulus" begin
100152
T = Fixed{Int8,7}
101153
for i = -1.0:0.1:typemax(T)

0 commit comments

Comments
 (0)