Skip to content

Commit b4e5a1b

Browse files
committed
Add rounding functions for Fixed
1 parent 9bcca82 commit b4e5a1b

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
@@ -44,6 +44,9 @@ function rawone(::Type{Fixed{T,f}}) where {T, f}
4444
oneunit(T) << f
4545
end
4646

47+
intmask(::Fixed{T,f}) where {T, f} = -oneunit(T) << f # Signed
48+
fracmask(x::Fixed{T,f}) where {T, f} = ~intmask(x) # Signed
49+
4750
# unchecked arithmetic
4851

4952
# with truncation:
@@ -85,6 +88,73 @@ end
8588
(::Type{TR})(x::Fixed{T,f}) where {TR <: Rational,T,f} =
8689
TR(x.i>>f + (x.i&(1<<f-1))//(one(widen1(T))<<f))
8790

91+
function trunc(x::Fixed{T,f}) where {T, f}
92+
f == 0 && return x
93+
f == bitwidth(T) && return zero(x)
94+
f == bitwidth(T) - 1 && return x.i == typemin(T) ? x : zero(x)
95+
t = x.i & intmask(x)
96+
r = x.i & fracmask(x)
97+
_rawone = oneunit(T) << f
98+
reinterpret(Fixed{T,f}, (x.i < 0) & (r != 0) ? t + _rawone : t)
99+
end
100+
function floor(x::Fixed{T,f}) where {T, f}
101+
f == bitwidth(T) && x.i < 0 && throw_converterror(Fixed{T,f}, -1)
102+
Fixed{T,f}(x.i & intmask(x), 0)
103+
end
104+
function ceil(x::Fixed{T,f}) where {T, f}
105+
f == 0 && return x
106+
upper = typemax(T) & intmask(x)
107+
x.i > upper && throw_converterror(Fixed{T,f}, ceil(float(x)))
108+
reinterpret(Fixed{T,f}, (x.i + fracmask(x)) & intmask(x))
109+
end
110+
function round(x::Fixed{T,f}) where {T, f}
111+
f == 0 && return x
112+
f == bitwidth(T) && return zero(x)
113+
upper = intmask(x) >>> 0x1
114+
lower = intmask(x) >> 0x1
115+
if f == bitwidth(T) - 1
116+
x.i > upper && throw_converterror(Fixed{T,f}, @exp2(bitwidth(T)-f-1))
117+
return x.i < lower ? typemin(x) : zero(x)
118+
end
119+
x.i >= upper && throw_converterror(Fixed{T,f}, @exp2(bitwidth(T)-f-1))
120+
y = oneunit(T) << UInt8(f - 1) + x.i
121+
m = oneunit(T) << UInt8(f + 1) - oneunit(T)
122+
z = y & intmask(x)
123+
reinterpret(Fixed{T,f}, z - T(y & m == rawone(x)) << f)
124+
end
125+
126+
function trunc(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f}
127+
f == 0 && return convert(Ti, x.i)
128+
f == bitwidth(T) && return zero(Ti)
129+
f == bitwidth(T) - 1 && return x.i == typemin(T) ? convert(Ti, -1) : zero(Ti)
130+
t = x.i >> f
131+
r = x.i & fracmask(x)
132+
convert(Ti, (x.i < 0) & (r != 0) ? t + oneunit(T) : t)
133+
end
134+
function floor(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f}
135+
f == bitwidth(T) && return x.i < 0 ? convert(Ti, -1) : zero(Ti)
136+
convert(Ti, x.i >> f)
137+
end
138+
function ceil(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f}
139+
f == bitwidth(T) && return x.i > 0 ? oneunit(Ti) : zero(Ti)
140+
y = x.i + fracmask(x)
141+
convert(Ti, x.i >= 0 ? y >>> f : y >> f)
142+
end
143+
function round(::Type{Ti}, x::Fixed{T,f}) where {Ti <: Integer, T, f}
144+
f == 0 && return convert(Ti, x.i)
145+
f == bitwidth(T) && return zero(Ti)
146+
upper = intmask(x) >>> 0x1
147+
lower = intmask(x) >> 0x1
148+
if f == bitwidth(T) - 1
149+
x.i < lower && return convert(Ti, -1)
150+
return x.i > upper ? oneunit(Ti) : zero(Ti)
151+
end
152+
y = oneunit(T) << UInt8(f - 1) + x.i
153+
m = oneunit(T) << UInt8(f + 1) - oneunit(T)
154+
z = x.i >= 0 ? y >>> f : y >> f
155+
convert(Ti, z - Ti(y & m == rawone(x)))
156+
end
157+
88158
promote_rule(ft::Type{Fixed{T,f}}, ::Type{TI}) where {T,f,TI <: Integer} = Fixed{T,f}
89159
promote_rule(::Type{Fixed{T,f}}, ::Type{TF}) where {T,f,TF <: AbstractFloat} = TF
90160
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
@@ -60,6 +60,18 @@ end
6060
@test reinterpret(Int8, 0.5Q0f7) === signed(0x40)
6161
end
6262

63+
@testset "masks" begin
64+
@test FixedPointNumbers.intmask(0Q7f0) === signed(0xFF)
65+
@test FixedPointNumbers.intmask(0Q6f1) === signed(0xFE)
66+
@test FixedPointNumbers.intmask(0Q1f6) === signed(0xC0)
67+
@test FixedPointNumbers.intmask(0Q0f7) === signed(0x80)
68+
69+
@test FixedPointNumbers.fracmask(0Q7f0) === signed(0x00)
70+
@test FixedPointNumbers.fracmask(0Q6f1) === signed(0x01)
71+
@test FixedPointNumbers.fracmask(0Q1f6) === signed(0x3F)
72+
@test FixedPointNumbers.fracmask(0Q0f7) === signed(0x7F)
73+
end
74+
6375
@testset "inexactness" begin
6476
@test_throws InexactError Q0f7(-2)
6577
# TODO: change back to InexactError when it allows message strings
@@ -86,6 +98,46 @@ end
8698
end
8799
end
88100

101+
@testset "rounding" begin
102+
for T in (Int8, Int16, Int32, Int64)
103+
rs = vcat([ oneunit(T) << b - oneunit(T) for b = 0:bitwidth(T)-1],
104+
[ oneunit(T) << b for b = 1:bitwidth(T)-2],
105+
[ oneunit(T) << b + oneunit(T) for b = 2:bitwidth(T)-2],
106+
[-oneunit(T) << b - oneunit(T) for b = 2:bitwidth(T)-2],
107+
[-oneunit(T) << b for b = 1:bitwidth(T)-1],
108+
[-oneunit(T) << b + oneunit(T) for b = 1:bitwidth(T)-1])
109+
@testset "rounding Fixed{$T,$f}" for f = 0:bitwidth(T)
110+
F = Fixed{T,f}
111+
xs = (reinterpret(F, r) for r in rs)
112+
@test all(x -> trunc(x) == trunc(float(x)), xs)
113+
@test all(x -> floor(float(x)) < typemin(F) || floor(x) == floor(float(x)), xs)
114+
@test all(x -> ceil(float(x)) > typemax(F) || ceil(x) == ceil(float(x)), xs)
115+
@test all(x -> round(float(x)) > typemax(F) || round(x) == round(float(x)), xs)
116+
@test all(x -> trunc(Int64, x) === trunc(Int64, float(x)), xs)
117+
@test all(x -> floor(Int64, x) === floor(Int64, float(x)), xs)
118+
@test all(x -> ceil(Int64, x) === ceil(Int64, float(x)), xs)
119+
@test all(x -> round(Int64, x) === round(Int64, float(x)), xs)
120+
end
121+
end
122+
@testset "rounding Fixed{Int16,$f} with overflow" for f = 1:16
123+
F = Fixed{Int16,f}
124+
@test_throws ArgumentError ceil(typemax(F))
125+
if f == 16
126+
@test_throws ArgumentError ceil(eps(F))
127+
elseif f == 15
128+
@test_throws ArgumentError ceil(eps(F))
129+
@test_throws ArgumentError round(typemax(F))
130+
@test_throws ArgumentError round(F(0.5) + eps(F))
131+
else
132+
@test_throws ArgumentError ceil(typemin(F) - oneunit(F) + eps(F))
133+
@test_throws ArgumentError round(typemax(F))
134+
@test_throws ArgumentError round(typemax(F) - F(0.5) + eps(F))
135+
end
136+
end
137+
@test_throws InexactError trunc(UInt, typemin(Q0f7))
138+
@test_throws InexactError floor(UInt, -eps(Q0f7))
139+
end
140+
89141
@testset "modulus" begin
90142
T = Fixed{Int8,7}
91143
for i = -1.0:0.1:typemax(T)

0 commit comments

Comments
 (0)