Skip to content

Commit 05fd7e7

Browse files
authored
Merge pull request #168 from kimikage/commonize2
Commonize further code between `Fixed` and` Normed` (Fixes #154)
2 parents 0ae5b82 + 497f4ba commit 05fd7e7

File tree

5 files changed

+128
-77
lines changed

5 files changed

+128
-77
lines changed

src/FixedPointNumbers.jl

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,28 @@ nbitsfrac(::Type{X}) where {T, f, X <: FixedPoint{T,f}} = f
4545
rawtype(::Type{X}) where {T, X <: FixedPoint{T}} = T
4646

4747
# construction using the (approximate) intended value, i.e., N0f8
48-
*(x::Real, ::Type{X}) where {X<:FixedPoint} = X(x)
48+
*(x::Real, ::Type{X}) where {X <: FixedPoint} = _convert(X, x)
49+
50+
# constructor-style conversions
51+
(::Type{X})(x::Real) where {X <: FixedPoint} = _convert(X, x)
52+
53+
function (::Type{<:FixedPoint})(x::AbstractChar)
54+
throw(ArgumentError("FixedPoint (Fixed or Normed) cannot be constructed from a Char"))
55+
end
56+
(::Type{X})(x::Complex) where {X <: FixedPoint} = X(convert(real(typeof(x)), x))
57+
function (::Type{X})(x::Base.TwicePrecision) where {X <: FixedPoint}
58+
floattype(X) === BigFloat ? X(big(x)) : X(convert(floattype(X), x))
59+
end
60+
61+
# conversions
62+
function Base.Bool(x::FixedPoint)
63+
x == zero(x) ? false : x == oneunit(x) ? true : throw(InexactError(:Bool, Bool, x))
64+
end
65+
function (::Type{Ti})(x::FixedPoint) where {Ti <: Integer}
66+
isinteger(x) || throw(InexactError(:Integer, typeof(x), x))
67+
floor(Ti, x)
68+
end
69+
Base.Rational{Ti}(x::FixedPoint) where {Ti <: Integer} = Rational{Ti}(Rational(x))
4970

5071
"""
5172
isapprox(x::FixedPoint, y::FixedPoint; rtol=0, atol=max(eps(x), eps(y)))

src/fixed.jl

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,6 @@ struct Fixed{T <: Signed, f} <: FixedPoint{T, f}
2424
end
2525
end
2626

27-
Fixed{T, f}(x::AbstractChar) where {T,f} = throw(ArgumentError("Fixed cannot be constructed from a Char"))
28-
Fixed{T, f}(x::Complex) where {T,f} = Fixed{T, f}(convert(real(typeof(x)), x))
29-
Fixed{T, f}(x::Base.TwicePrecision) where {T,f} = Fixed{T, f}(convert(Float64, x))
30-
Fixed{T,f}(x::Integer) where {T,f} = Fixed{T,f}(round(T, convert(widen1(T),x)<<f),0)
31-
Fixed{T,f}(x::AbstractFloat) where {T,f} = Fixed{T,f}(round(T, trunc(widen1(T),x)<<f + rem(x,1)*(one(widen1(T))<<f)),0)
32-
Fixed{T,f}(x::Rational) where {T,f} = Fixed{T,f}(x.num)/Fixed{T,f}(x.den)
33-
3427
typechar(::Type{X}) where {X <: Fixed} = 'Q'
3528
signbits(::Type{X}) where {X <: Fixed} = 1
3629

@@ -53,6 +46,25 @@ end
5346
intmask(::Fixed{T,f}) where {T, f} = -oneunit(T) << f # Signed
5447
fracmask(x::Fixed{T,f}) where {T, f} = ~intmask(x) # Signed
5548

49+
# constructor-style conversions
50+
function _convert(::Type{F}, x::Fixed{T2,f2}) where {T, T2, f, f2, F <: Fixed{T,f}}
51+
y = round(((1<<f)/(1<<f2))*reinterpret(x)) # FIXME: avoid overflow
52+
(typemin(T) <= y) & (y <= typemax(T)) || throw_converterror(F, x)
53+
reinterpret(F, _unsafe_trunc(T, y))
54+
end
55+
56+
function _convert(::Type{F}, x::Integer) where {T, f, F <: Fixed{T,f}}
57+
reinterpret(F, round(T, convert(widen1(T),x)<<f)) # TODO: optimization and input range checking
58+
end
59+
60+
function _convert(::Type{F}, x::AbstractFloat) where {T, f, F <: Fixed{T,f}}
61+
reinterpret(F, round(T, trunc(widen1(T),x)<<f + rem(x,1)*(one(widen1(T))<<f))) # TODO: optimization and input range checking
62+
end
63+
64+
function _convert(::Type{F}, x::Rational) where {T, f, F <: Fixed{T,f}}
65+
F(x.num)/F(x.den) # TODO: optimization and input range checking
66+
end
67+
5668
# unchecked arithmetic
5769

5870
# with truncation:
@@ -63,15 +75,6 @@ fracmask(x::Fixed{T,f}) where {T, f} = ~intmask(x) # Signed
6375
/(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(div(convert(widen(T), x.i) << f, y.i), 0)
6476

6577

66-
# # conversions and promotions
67-
function Fixed{T,f}(x::Fixed{T2,f2}) where {T <: Integer,T2 <: Integer,f,f2}
68-
# reinterpret(Fixed{T,f},T(reinterpret(x)<<(f-f2)))
69-
U = Fixed{T,f}
70-
y = round(((1<<f)/(1<<f2))*reinterpret(x))
71-
(typemin(T) <= y) & (y <= typemax(T)) || throw_converterror(U, x)
72-
reinterpret(U, _unsafe_trunc(T, y))
73-
end
74-
7578
rem(x::Integer, ::Type{Fixed{T,f}}) where {T,f} = Fixed{T,f}(rem(x,T)<<f,0)
7679
rem(x::Real, ::Type{Fixed{T,f}}) where {T,f} = Fixed{T,f}(rem(Integer(trunc(x)),T)<<f + rem(Integer(round(rem(x,1)*(one(widen1(T))<<f))),T),0)
7780

@@ -81,19 +84,10 @@ Base.BigFloat(x::Fixed{T,f}) where {T,f} =
8184
(::Type{TF})(x::Fixed{T,f}) where {TF <: AbstractFloat,T,f} =
8285
TF(x.i>>f) + TF(x.i&(one(widen1(T))<<f - 1))/TF(one(widen1(T))<<f)
8386

84-
Base.Bool(x::Fixed{T,f}) where {T,f} = x.i!=0
85-
function Base.Integer(x::Fixed{T,f}) where {T,f}
86-
isinteger(x) || throw(InexactError())
87-
Integer(x.i>>f)
88-
end
89-
function (::Type{TI})(x::Fixed{T,f}) where {TI <: Integer,T,f}
90-
isinteger(x) || throw(InexactError())
91-
TI(x.i>>f)
87+
function Base.Rational(x::Fixed{T,f}) where {T, f}
88+
f < bitwidth(T)-1 ? x.i//rawone(x) : x.i//(one(widen1(T))<<f)
9289
end
9390

94-
(::Type{TR})(x::Fixed{T,f}) where {TR <: Rational,T,f} =
95-
TR(x.i>>f + (x.i&(1<<f-1))//(one(widen1(T))<<f))
96-
9791
function trunc(x::Fixed{T,f}) where {T, f}
9892
f == 0 && return x
9993
f == bitwidth(T) && return zero(x) # TODO: remove this line

src/normed.jl

Lines changed: 23 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,6 @@ struct Normed{T <: Unsigned, f} <: FixedPoint{T, f}
1919
end
2020
end
2121

22-
Normed{T, f}(x::AbstractChar) where {T,f} = throw(ArgumentError("Normed cannot be constructed from a Char"))
23-
Normed{T, f}(x::Complex) where {T,f} = Normed{T, f}(convert(real(typeof(x)), x))
24-
Normed{T, f}(x::Base.TwicePrecision) where {T,f} = Normed{T, f}(convert(Float64, x))
25-
Normed{T1,f}(x::Normed{T2,f}) where {T1 <: Unsigned,T2 <: Unsigned,f} = Normed{T1,f}(convert(T1, x.i), 0)
26-
2722
typechar(::Type{X}) where {X <: Normed} = 'N'
2823
signbits(::Type{X}) where {X <: Normed} = 0
2924

@@ -42,34 +37,38 @@ function rawone(::Type{Normed{T,f}}) where {T <: Unsigned, f}
4237
typemax(T) >> (bitwidth(T) - f)
4338
end
4439

45-
# Conversions
46-
function Normed{T,f}(x::Normed{T2}) where {T <: Unsigned,T2 <: Unsigned,f}
47-
U = Normed{T,f}
48-
y = round((rawone(U)/rawone(x))*reinterpret(x))
49-
(0 <= y) & (y <= typemax(T)) || throw_converterror(U, x)
50-
reinterpret(U, _unsafe_trunc(T, y))
51-
end
52-
N0f16(x::N0f8) = reinterpret(N0f16, convert(UInt16, 0x0101*reinterpret(x)))
40+
# constructor-style conversions
41+
function _convert(::Type{N}, x::Normed{T2,f}) where {T, T2, f, N <: Normed{T,f}}
42+
reinterpret(N, convert(T, x.i)) # TODO: input range checking
43+
end
5344

54-
(::Type{U})(x::Real) where {U <: Normed} = _convert(U, x)
45+
function _convert(::Type{N}, x::Normed{T2,f2}) where {T, T2, f, f2, N <: Normed{T,f}}
46+
y = round((rawone(N)/rawone(x))*reinterpret(x))
47+
(0 <= y) & (y <= typemax(T)) || throw_converterror(N, x)
48+
reinterpret(N, _unsafe_trunc(T, y))
49+
end
50+
51+
function _convert(::Type{N}, x::Normed{UInt8,8}) where {N <: Normed{UInt16,16}} # TODO: generalization
52+
reinterpret(N0f16, convert(UInt16, 0x0101*reinterpret(x)))
53+
end
5554

56-
function _convert(::Type{U}, x) where {T, f, U <: Normed{T,f}}
55+
function _convert(::Type{N}, x::Real) where {T, f, N <: Normed{T,f}}
5756
if T == UInt128 # for UInt128, we can't widen
5857
# the upper limit is not exact
59-
(0 <= x) & (x <= (typemax(T)/rawone(U))) || throw_converterror(U, x)
60-
y = round(rawone(U)*x)
58+
(0 <= x) & (x <= (typemax(T)/rawone(N))) || throw_converterror(N, x)
59+
y = round(rawone(N)*x)
6160
else
62-
y = round(widen1(rawone(U))*x)
63-
(0 <= y) & (y <= typemax(T)) || throw_converterror(U, x)
61+
y = round(widen1(rawone(N))*x)
62+
(0 <= y) & (y <= typemax(T)) || throw_converterror(N, x)
6463
end
65-
reinterpret(U, _unsafe_trunc(T, y))
64+
reinterpret(N, _unsafe_trunc(T, y))
6665
end
6766
# Prevent overflow (https://discourse.julialang.org/t/saving-greater-than-8-bit-images/6057)
68-
function _convert(::Type{U}, x::Float16) where {T, f, U <: Normed{T,f}}
69-
if Float16(typemax(T)/rawone(U)) > Float32(typemax(T)/rawone(U))
70-
x == Float16(typemax(T)/rawone(U)) && return typemax(U)
67+
function _convert(::Type{N}, x::Float16) where {T, f, N <: Normed{T,f}}
68+
if Float16(typemax(T)/rawone(N)) > Float32(typemax(T)/rawone(N))
69+
x == Float16(typemax(T)/rawone(N)) && return typemax(N)
7170
end
72-
return _convert(U, Float32(x))
71+
return _convert(N, Float32(x))
7372
end
7473
function _convert(::Type{N}, x::Tf) where {T, f, N <: Normed{T,f}, Tf <: Union{Float32, Float64}}
7574
if T === UInt128 && f == 53
@@ -239,10 +238,6 @@ end
239238

240239
Base.BigFloat(x::Normed) = reinterpret(x)*(1/BigFloat(rawone(x)))
241240

242-
Base.Bool(x::Normed) = x == zero(x) ? false : true
243-
Base.Integer(x::Normed) = convert(Integer, x*1.0)
244-
(::Type{T})(x::Normed) where {T <: Integer} = convert(T, x*(1/oneunit(T)))
245-
Base.Rational{Ti}(x::Normed) where {Ti <: Integer} = convert(Ti, reinterpret(x))//convert(Ti, rawone(x))
246241
Base.Rational(x::Normed) = reinterpret(x)//rawone(x)
247242

248243
abs(x::Normed) = x

test/fixed.jl

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ end
9797
@test_throws InexactError convert(Fixed{Int8, 7}, 1)
9898
@test_throws InexactError convert(Fixed{Int8, 7}, 2)
9999
@test_throws InexactError convert(Fixed{Int8, 7}, 128)
100+
101+
@test convert(Q2f5, -1//2) === -0.5Q2f5
102+
@test_broken convert(Q1f6, Rational{Int8}(-3//4)) === -0.75Q1f6
103+
@test_broken convert(Q0f7, Rational{Int16}(-3//4)) === -0.75Q0f7
104+
@test_broken convert(Q0f7, Rational{UInt8}(3//4)) === 0.75Q0f7
105+
106+
@test convert(Q0f7, Base.TwicePrecision(0.5)) === 0.5Q0f7
107+
@test_throws InexactError convert(Q7f8, Base.TwicePrecision(0x80, 0x01))
108+
tp = Base.TwicePrecision(0xFFFFFFFFp-32, 0xFFFFFFFEp-64)
109+
@test convert(Q0f63, tp) === reinterpret(Q0f63, typemax(Int64))
100110
end
101111

102112
@testset "test_fixed" begin
@@ -231,9 +241,26 @@ end
231241
end
232242
end
233243

244+
@testset "bool conversions" begin
245+
@test convert(Bool, 0.0Q1f6) === false
246+
@test convert(Bool, 1.0Q1f6) === true
247+
@test_throws InexactError convert(Bool, 0.5Q1f6)
248+
@test_throws InexactError convert(Bool, -1Q1f6)
249+
@test_broken convert(Bool, Fixed{Int8,8}(0.2)) # TODO: remove this
250+
end
251+
234252
@testset "Integer conversions" begin
235253
@test convert(Int, Q1f6(1)) === 1
236254
@test convert(Integer, Q1f6(1)) === Int8(1)
255+
@test convert(UInt, 1Q1f6) === UInt(1)
256+
@test_throws InexactError convert(Integer, 0.5Q1f6)
257+
@test_throws InexactError convert(Int8, 256Q9f6)
258+
end
259+
260+
@testset "rational conversions" begin
261+
@test convert(Rational, -0.75Q1f6) === Rational{Int8}(-3//4)
262+
@test convert(Rational, -0.75Q0f7) === Rational{Int16}(-3//4)
263+
@test convert(Rational{Int}, -0.75Q0f7) === Rational(-3//4)
237264
end
238265

239266
@testset "Floating-point conversions" begin

test/normed.jl

Lines changed: 35 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,19 @@ end
9292

9393
@test convert(N0f8, 1.1f0/typemax(UInt8)) == eps(N0f8)
9494

95+
@test_broken convert(N0f8, 1//255) === eps(N0f8)
96+
@test_broken convert(N0f8, Rational{Int8}(3//5)) === N0f8(3/5)
97+
@test_broken convert(N0f8, Rational{UInt8}(3//5)) === N0f8(3/5)
98+
99+
@test convert(N0f8, Base.TwicePrecision(1.0)) === 1N0f8
100+
95101
@test convert(Float64, eps(N0f8)) == 1/typemax(UInt8)
96102
@test convert(Float32, eps(N0f8)) == 1.0f0/typemax(UInt8)
97103
@test convert(BigFloat, eps(N0f8)) == BigFloat(1)/typemax(UInt8)
98104
for T in (FixedPointNumbers.UF..., UF2...)
99105
@test convert(Bool, zero(T)) == false
100106
@test convert(Bool, one(T)) == true
101-
@test convert(Bool, convert(T, 0.2)) == true
107+
@test_throws InexactError convert(Bool, convert(T, 0.2))
102108
@test convert(Int, one(T)) == 1
103109
@test convert(Integer, one(T)) == 1
104110
@test convert(Rational, one(T)) == 1
@@ -109,30 +115,38 @@ end
109115
@test convert(Normed{UInt16,7}, Normed{UInt8,7}(0.504)) === Normed{UInt16,7}(0.504)
110116
end
111117

118+
@testset "integer conversions" begin
119+
@test convert(UInt, 1N1f7) === UInt(1)
120+
@test convert(Integer, 1N1f7) === 0x01
121+
@test convert(Int, 1N1f7) === 1
122+
@test_throws InexactError convert(Integer, 0.5N1f7)
123+
@test_throws InexactError convert(Int8, 256N8f8)
124+
end
125+
112126
@testset "conversion from float" begin
113127
# issue 102
114128
for T in (UInt8, UInt16, UInt32, UInt64, UInt128)
115129
for Tf in (Float16, Float32, Float64)
116130
@testset "Normed{$T,$f}(::$Tf)" for f = 1:bitwidth(T)
117-
U = Normed{T,f}
118-
r = FixedPointNumbers.rawone(U)
131+
N = Normed{T,f}
132+
r = FixedPointNumbers.rawone(N)
119133

120-
@test reinterpret(U(zero(Tf))) == 0x0
134+
@test reinterpret(N(zero(Tf))) == 0x0
121135

122-
input_typemax = Tf(typemax(U))
136+
input_typemax = Tf(typemax(N))
123137
if isinf(input_typemax)
124-
@test reinterpret(U(floatmax(Tf))) >= round(T, floatmax(Tf))
138+
@test reinterpret(N(floatmax(Tf))) >= round(T, floatmax(Tf))
125139
else
126-
@test reinterpret(U(input_typemax)) >= (typemax(T)>>1) # overflow check
140+
@test reinterpret(N(input_typemax)) >= (typemax(T)>>1) # overflow check
127141
end
128142

129143
input_upper = Tf(BigFloat(typemax(T)) / r, RoundDown)
130144
isinf(input_upper) && continue # for Julia v0.7
131-
@test reinterpret(U(input_upper)) == T(min(round(BigFloat(input_upper) * r), typemax(T)))
145+
@test reinterpret(N(input_upper)) == T(min(round(BigFloat(input_upper) * r), typemax(T)))
132146

133147
input_exp2 = Tf(exp2(bitwidth(T) - f))
134148
isinf(input_exp2) && continue
135-
@test reinterpret(U(input_exp2)) == T(input_exp2) * r
149+
@test reinterpret(N(input_exp2)) == T(input_exp2) * r
136150
end
137151
end
138152
end
@@ -149,27 +163,27 @@ end
149163
end
150164

151165
for Tf in (Float16, Float32, Float64)
152-
@testset "$Tf(::Normed{$Ti})" for Ti in (UInt8, UInt16)
153-
@testset "$Tf(::Normed{$Ti,$f})" for f = 1:bitwidth(Ti)
154-
T = Normed{Ti,f}
166+
@testset "$Tf(::Normed{$T})" for T in (UInt8, UInt16)
167+
@testset "$Tf(::Normed{$T,$f})" for f = 1:bitwidth(T)
168+
N = Normed{T,f}
155169
float_err = 0.0
156-
for i = typemin(Ti):typemax(Ti)
157-
f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(T)))
170+
for i = typemin(T):typemax(T)
171+
f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(N)))
158172
isinf(f_expected) && break # for Float16(::Normed{UInt16,1})
159-
f_actual = Tf(reinterpret(T, i))
173+
f_actual = Tf(reinterpret(N, i))
160174
float_err += abs(f_actual - f_expected)
161175
end
162176
@test float_err == 0.0
163177
end
164178
end
165-
@testset "$Tf(::Normed{$Ti})" for Ti in (UInt32, UInt64, UInt128)
166-
@testset "$Tf(::Normed{$Ti,$f})" for f = 1:bitwidth(Ti)
167-
T = Normed{Ti,f}
179+
@testset "$Tf(::Normed{$T})" for T in (UInt32, UInt64, UInt128)
180+
@testset "$Tf(::Normed{$T,$f})" for f = 1:bitwidth(T)
181+
N = Normed{T,f}
168182
error_count = 0
169-
for i in vcat(Ti(0x00):Ti(0xFF), (typemax(Ti)-0xFF):typemax(Ti))
170-
f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(T)))
183+
for i in vcat(T(0x00):T(0xFF), (typemax(T)-0xFF):typemax(T))
184+
f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(N)))
171185
isinf(f_expected) && break # for Float16() and Float32()
172-
f_actual = Tf(reinterpret(T, i))
186+
f_actual = Tf(reinterpret(N, i))
173187
f_actual == f_expected && continue
174188
f_actual == prevfloat(f_expected) && continue
175189
f_actual == nextfloat(f_expected) && continue

0 commit comments

Comments
 (0)