Skip to content

Commit 443e6ac

Browse files
committed
Systemize fixed point types to be tested
1 parent 196e410 commit 443e6ac

File tree

4 files changed

+302
-207
lines changed

4 files changed

+302
-207
lines changed

test/common.jl

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
using FixedPointNumbers, Statistics, Test
2+
using FixedPointNumbers: bitwidth, rawtype, nbitsfrac
3+
4+
"""
5+
target(X::Type, Ss...; ex = :default)
6+
7+
Return a generator which enumerates the target types for testing.
8+
9+
# Arguments
10+
- `X`: target base type
11+
- `Ss`: symbols for specifying the target raw types
12+
- `:i*` : a `Signed` type if `X === Fixed`, or an `Unsigned` type if `X === Normed`
13+
- `:s*` : a `Signed` type (not yet supported)
14+
- `:u*` : an `Unsigned` type (not yet supported)
15+
- `ex`: exhaustivity of `f`s (see also the [`target_f`](@ref) function)
16+
- `:heavy`: all supported `f`s
17+
- `:default` : same as `:heavy` for 8-/16-bit types, and same as `:light` otherwise
18+
- `:light` : important `f`s for byte boundaries and floating point types
19+
- `:thin` : maximum and half `f`s per type
20+
21+
# Example
22+
```julia
23+
julia> collect(target(Normed, :i8, :i32; ex = :default))
24+
21-element Array{DataType,1}:
25+
Normed{UInt8,1}
26+
Normed{UInt8,2}
27+
Normed{UInt8,3}
28+
Normed{UInt8,4}
29+
Normed{UInt8,5}
30+
Normed{UInt8,6}
31+
Normed{UInt8,7}
32+
Normed{UInt8,8}
33+
Normed{UInt32,1}
34+
Normed{UInt32,7}
35+
Normed{UInt32,8}
36+
Normed{UInt32,9}
37+
Normed{UInt32,10}
38+
Normed{UInt32,11}
39+
Normed{UInt32,15}
40+
Normed{UInt32,16}
41+
Normed{UInt32,17}
42+
Normed{UInt32,23}
43+
Normed{UInt32,24}
44+
Normed{UInt32,31}
45+
Normed{UInt32,32}
46+
```
47+
"""
48+
function target(X::Type, Ss...; ex = :default)
49+
Ts = symbol_to_inttype.(X, Ss)
50+
(X{T,f} for T in Ts for f in target_f(X, T; ex = ex))
51+
end
52+
target(X::Type; ex = :default) = target(X, :i8, :i16, :i32, :i64, :i128; ex = ex)
53+
54+
"""
55+
target_f(X::Type, T::Type; ex = :default)
56+
57+
Return a tuple or range of the number of fractional bits `f` to be tested.
58+
59+
# Arguments
60+
The `X` specifies the target base type, i.e. `Fixed` or `Normed` and the `T`
61+
specifies the target raw type.
62+
63+
## `ex` keyword
64+
The `ex` specifies the exhaustivity of `f`s.
65+
The following are examples of `target_f(Normed, T)`. The marker `x` means the
66+
target and the marker `-` means not the target.
67+
68+
### `:heavy` -- all supported `f`s
69+
```
70+
| 3 2 1 |
71+
f |2 1 0 9 8 7 6 5:4 3 2 1 0 9 8 7:6 5 4 3 2 1 0 9:8 7 6 5 4 3 2 1|
72+
T == UInt8 | : : :x x x x x x x x|
73+
T == UInt16 | : :x x x x x x x x:x x x x x x x x|
74+
T == UInt32 |x x x x x x x x:x x x x x x x x:x x x x x x x x:x x x x x x x x|
75+
```
76+
## `:default` -- same as `:heavy` for 8-/16-bit types, and same as `:light` otherwise
77+
```
78+
| 3 2 1 |
79+
f |2 1 0 9 8 7 6 5:4 3 2 1 0 9 8 7:6 5 4 3 2 1 0 9:8 7 6 5 4 3 2 1|
80+
T == UInt8 | : : :x x x x x x x x|
81+
T == UInt16 | : :x x x x x x x x:x x x x x x x x|
82+
T == UInt32 |x x - - - - - -:x x - - - - - x:x x - - - x x x:x x - - - - - x|
83+
```
84+
85+
## `:light` -- important `f`s for byte boundaries and floating point types
86+
```
87+
| 3 2 1 |
88+
f |2 1 0 9 8 7 6 5:4 3 2 1 0 9 8 7:6 5 4 3 2 1 0 9:8 7 6 5 4 3 2 1|
89+
T == UInt8 | : : :x x - - - - - x|
90+
T == UInt16 | : :x x - - - x x x:x x - - - - - x|
91+
T == UInt32 |x x - - - - - -:x x - - - - - x:x x - - - x x x:x x - - - - - x|
92+
| |
93+
+--precision(Float32) +--precision(Float16)
94+
```
95+
96+
## `:thin` -- maximum and half `f`s per type
97+
```
98+
| 3 2 1 |
99+
f |2 1 0 9 8 7 6 5:4 3 2 1 0 9 8 7:6 5 4 3 2 1 0 9:8 7 6 5 4 3 2 1|
100+
T == UInt8 | : : :x - - - x - - -|
101+
T == UInt16 | : :x - - - - - - -:x - - - - - - -|
102+
T == UInt32 |x - - - - - - -:- - - - - - - -:x - - - - - - -:- - - - - - - -|
103+
```
104+
"""
105+
function target_f(X::Type, T::Type{<:Integer}; ex = :default)
106+
f_min = X === Fixed ? 0 : 1
107+
f_max = bitwidth(T) - (T <: Signed) - 1 + f_min
108+
ex === :heavy && return f_min:f_max
109+
ex === :default && bitwidth(T) <= 16 && return f_min:f_max
110+
ex === :thin && return ((f_max + 1) ÷ 2, f_max)
111+
if ex === :light || ex === :default
112+
itr = Iterators.filter(x -> x <= f_max, target_f_series(X, T))
113+
return (itr...,)
114+
end
115+
error()
116+
end
117+
118+
target_f_series(::Type{Fixed}, T::Type{<:Integer}) =
119+
(0, 1, 7, 8, 9,
120+
10, 11, 15, 16, 17,
121+
23, 24, 31, 32, 33,
122+
52, 53, 63, 64, 65,
123+
112, 113, 127)
124+
125+
target_f_series(::Type{Normed}, T::Type{<:Integer}) =
126+
(1, 7, 8, 9,
127+
10, 11, 15, 16, 17,
128+
23, 24, 31, 32, 33,
129+
52, 53, 63, 64, 65,
130+
112, 113, 127, 128)

test/fixed.jl

Lines changed: 72 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
using FixedPointNumbers, Statistics, Test
2-
using FixedPointNumbers: bitwidth
1+
include("common.jl")
2+
3+
function symbol_to_inttype(::Type{Fixed}, s::Symbol)
4+
d = Dict(:i8 => Int8, :i16 => Int16, :i32 => Int32, :i64 => Int64, :i128 => Int128)
5+
d[s]
6+
end
37

48
function test_op(fun::F, ::Type{T}, fx, fy, fxf, fyf, tol) where {F,T}
59
# Make sure that the result is representable
@@ -8,9 +12,9 @@ function test_op(fun::F, ::Type{T}, fx, fy, fxf, fyf, tol) where {F,T}
812
@assert abs(convert(Float64, fun(fx, fy)) - fun(fxf, fyf)) <= tol
913
end
1014

11-
function test_fixed(::Type{T}, f) where {T}
15+
function test_fixed(::Type{T}) where {T}
1216
values = [-10:0.01:10; -180:.01:-160; 160:.01:180]
13-
tol = 2.0^-f
17+
tol = Float64(eps(T))
1418
for x in values
1519
# Ignore values outside the representable range
1620
# typemin <, otherwise for -(-0.5) > typemax
@@ -36,7 +40,7 @@ function test_fixed(::Type{T}, f) where {T}
3640
fyf = convert(Float64, fy)
3741

3842
@assert fx==fy || x!=y
39-
@assert fx<fy || x>=y
43+
@assert fx<fy || (x + tol)>=y
4044
@assert fx<=fy || x>y
4145

4246
test_op(+, T, fx, fy, fxf, fyf, tol)
@@ -50,10 +54,8 @@ function test_fixed(::Type{T}, f) where {T}
5054
end
5155

5256
@testset "test_fixed" begin
53-
for (TI, f) in [(Int8, 7), (Int16, 8), (Int16, 10), (Int32, 16)]
54-
T = Fixed{TI,f}
55-
# println(" Testing $T")
56-
test_fixed(T, f)
57+
for F in target(Fixed, :i8, :i16, :i32; ex = :thin)
58+
test_fixed(F)
5759
end
5860
end
5961

@@ -93,10 +95,18 @@ end
9395
end
9496

9597
@testset "limits and identities" begin
96-
# TODO: add tests
97-
98-
# issue #79
99-
@test floatmin(Q11f4) == Q11f4(0.06)
98+
@testset "$F" for F in target(Fixed)
99+
T, f = rawtype(F), nbitsfrac(F)
100+
@test zero(F) == 0
101+
f < bitwidth(T) - 1 && @test one(F) == 1
102+
f < bitwidth(T) - 1 && @test one(F) * oneunit(F) == oneunit(F)
103+
@test typemin(F) == typemin(T) >> f
104+
@test typemax(F) == typemax(T)//big"2"^f
105+
@test floatmin(F) === eps(F) == 2.0^-f # issue #79
106+
@test floatmax(F) === typemax(F)
107+
@test eps(zero(F)) === eps(typemax(F))
108+
@test sizeof(F) == sizeof(T)
109+
end
100110
end
101111

102112
@testset "inexactness" begin
@@ -182,11 +192,15 @@ end
182192
@test big(0.75Q3f4)::BigFloat == big"0.75"
183193
end
184194

185-
@testset "float()" begin
195+
@testset "float/floattype" begin
186196
@test float(0.75Q3f4) === 0.75f0
187197
@test float(0.75Q19f12) === 0.75
188198
@test float(0.75Q7f24) === 0.75
189199
@test float(0.75Q10f53)::BigFloat == big"0.75"
200+
201+
@testset "floattype($F)" for F in target(Fixed, :i8, :i16, :i32, :i64; ex = :heavy)
202+
@test typemax(F) <= maxintfloat(floattype(F))
203+
end
190204
end
191205

192206
@testset "conversions to float" begin
@@ -195,49 +209,40 @@ end
195209
end
196210

197211
for Tf in (Float16, Float32, Float64)
198-
@testset "$Tf(::Fixed{$T})" for T in (Int8, Int16)
199-
@testset "$Tf(::Fixed{$T,$f})" for f = 0:bitwidth(T)-1
200-
F = Fixed{T,f}
201-
float_err = 0.0
202-
for i = typemin(T):typemax(T)
203-
f_expected = Tf(i * BigFloat(2)^-f)
204-
f_actual = Tf(reinterpret(F, i))
205-
float_err += abs(f_actual - f_expected)
206-
end
207-
@test float_err == 0.0
212+
@testset "$Tf(::$F)" for F in target(Fixed, :i8, :i16)
213+
T, f = rawtype(F), nbitsfrac(F)
214+
float_err = 0.0
215+
for i = typemin(T):typemax(T)
216+
f_expected = Tf(i * BigFloat(2)^-f)
217+
f_actual = Tf(reinterpret(F, i))
218+
float_err += abs(f_actual - f_expected)
208219
end
220+
@test float_err == 0.0
209221
end
210-
@testset "$Tf(::Fixed{$T})" for T in (Int32, Int64, Int128)
211-
@testset "$Tf(::Fixed{$T,$f})" for f = 0:bitwidth(T)-1
212-
F = Fixed{T,f}
213-
error_count = 0
214-
for i in vcat(typemin(T):(typemin(T)+0xFF),
215-
-T(0xFF):T(0xFF),
216-
(typemax(T)-0xFF):typemax(T))
217-
f_expected = Tf(i * BigFloat(2)^-f)
218-
isinf(f_expected) && break # for Float16() and Float32()
219-
f_actual = Tf(reinterpret(F, i))
220-
f_actual == f_expected && continue
221-
error_count += 1
222-
end
223-
@test error_count == 0
222+
@testset "$Tf(::$F)" for F in target(Fixed, :i32, :i64, :i128)
223+
T, f = rawtype(F), nbitsfrac(F)
224+
error_count = 0
225+
for i in vcat(typemin(T):(typemin(T)+0xFF),
226+
-T(0xFF):T(0xFF),
227+
(typemax(T)-0xFF):typemax(T))
228+
f_expected = Tf(i * BigFloat(2)^-f)
229+
isinf(f_expected) && break # for Float16() and Float32()
230+
f_actual = Tf(reinterpret(F, i))
231+
f_actual == f_expected && continue
232+
error_count += 1
224233
end
234+
@test error_count == 0
225235
end
226236
end
227237
end
228238

229239
@testset "fractional fixed-point numbers" begin
230240
# test all-fractional fixed-point numbers (issue #104)
231-
for (T, f) in ((Int8, 7),
232-
(Int16, 15),
233-
(Int32, 31),
234-
(Int64, 63))
235-
tmax = typemax(Fixed{T, f})
236-
@test tmax == BigInt(typemax(T)) / BigInt(2)^f
237-
tol = (tmax + BigFloat(1.0)) / bitwidth(T)
238-
for x in range(-1, stop=BigFloat(tmax)-tol, length=50)
239-
@test abs(Fixed{T, f}(x) - x) <= tol
240-
end
241+
for F in (Q0f7, Q0f15, Q0f31, Q0f63)
242+
tmax = typemax(F)
243+
tol = (tmax + BigFloat(1.0)) / bitwidth(F)
244+
r = range(-1, stop=BigFloat(tmax)-tol, length=50)
245+
@test all(x -> abs(F(x) - x) <= tol, r)
241246
end
242247
end
243248

@@ -258,15 +263,15 @@ end
258263
end
259264

260265
@testset "rounding" begin
261-
for T in (Int8, Int16, Int32, Int64)
266+
for sym in (:i8, :i16, :i32, :i64)
267+
T = symbol_to_inttype(Fixed, sym)
262268
rs = vcat([ oneunit(T) << b - oneunit(T) for b = 0:bitwidth(T)-1],
263269
[ oneunit(T) << b for b = 1:bitwidth(T)-2],
264270
[ oneunit(T) << b + oneunit(T) for b = 2:bitwidth(T)-2],
265271
[-oneunit(T) << b - oneunit(T) for b = 2:bitwidth(T)-2],
266272
[-oneunit(T) << b for b = 1:bitwidth(T)-1],
267273
[-oneunit(T) << b + oneunit(T) for b = 1:bitwidth(T)-1])
268-
@testset "rounding Fixed{$T,$f}" for f = 0:bitwidth(T)-1
269-
F = Fixed{T,f}
274+
@testset "rounding $F" for F in target(Fixed, sym)
270275
xs = (reinterpret(F, r) for r in rs)
271276
@test all(x -> trunc(x) == trunc(float(x)), xs)
272277
@test all(x -> floor(float(x)) < typemin(F) || floor(x) == floor(float(x)), xs)
@@ -308,11 +313,11 @@ end
308313
end
309314

310315
@testset "approx" begin
311-
@testset "approx $T" for T in [Fixed{Int8,7}, Fixed{Int16,8}, Fixed{Int16,10}]
312-
xs = typemin(T):eps(T):typemax(T)-eps(T)
313-
@test all(x -> x x + eps(T), xs)
314-
@test all(x -> x + eps(T) x, xs)
315-
@test !any(x -> x - eps(T) x + eps(T), xs)
316+
@testset "approx $F" for F in target(Fixed, :i8, :i16; ex = :light)
317+
xs = typemin(F):eps(F):typemax(F)-eps(F)
318+
@test all(x -> x x + eps(F), xs)
319+
@test all(x -> x + eps(F) x, xs)
320+
@test !any(x -> x - eps(F) x + eps(F), xs)
316321
end
317322
end
318323

@@ -357,22 +362,16 @@ end
357362
@test !isinf(1Q7f8)
358363

359364
@testset "isinteger" begin
360-
for T in (Int8, Int16)
361-
@testset "isinteger(::Fixed{$T,$f})" for f = 0:bitwidth(T)-1
362-
F = Fixed{T,f}
363-
xs = typemin(F):eps(F):typemax(F)
364-
@test all(x -> isinteger(x) == isinteger(float(x)), xs)
365-
end
365+
@testset "isinteger(::$F)" for F in target(Fixed, :i8, :i16)
366+
xs = typemin(F):eps(F):typemax(F)
367+
@test all(x -> isinteger(x) == isinteger(float(x)), xs)
366368
end
367-
for T in (Int32, Int64)
368-
@testset "isinteger(::Fixed{$T,$f})" for f = 0:bitwidth(T)-1
369-
F = Fixed{T,f}
370-
fzero, fmax, fmin = zero(F), typemax(F), typemin(F)
371-
if f == 0
372-
@test isinteger(fzero) & isinteger(fmax) & isinteger(fmin)
373-
else
374-
@test isinteger(fzero) & !isinteger(fmax) & isinteger(fmin)
375-
end
369+
@testset "isinteger(::$F)" for F in target(Fixed, :i32, :i64, :i128)
370+
fzero, fmax, fmin = zero(F), typemax(F), typemin(F)
371+
if nbitsfrac(F) == 0
372+
@test isinteger(fzero) & isinteger(fmax) & isinteger(fmin)
373+
else
374+
@test isinteger(fzero) & !isinteger(fmax) & isinteger(fmin)
376375
end
377376
end
378377
@testset "isinteger(::Fixed{Int8,8})" begin # TODO: remove this testset
@@ -436,10 +435,10 @@ end
436435
end
437436

438437
@testset "rand" begin
439-
for F in (Fixed{Int8,7}, Fixed{Int16,8}, Fixed{Int16,10}, Fixed{Int32,16})
438+
@testset "rand(::$F)" for F in target(Fixed; ex = :thin)
440439
@test isa(rand(F), F)
441440
a = rand(F, (3, 5))
442-
@test ndims(a) == 2 && eltype(a) == F
441+
@test ndims(a) == 2 && eltype(a) === F
443442
@test size(a) == (3,5)
444443
end
445444
end

0 commit comments

Comments
 (0)