Skip to content

Commit e18c350

Browse files
authored
Merge pull request #151 from kimikage/commonize1
Commonize code between `Fixed` and `Normed` (the first part of #139)
2 parents 7ad0f0c + 763783e commit e18c350

File tree

6 files changed

+196
-149
lines changed

6 files changed

+196
-149
lines changed

src/FixedPointNumbers.jl

Lines changed: 65 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@ import Base: ==, <, <=, -, +, *, /, ~, isapprox,
1010

1111
using Base: @pure
1212

13-
# T => BaseType
14-
# f => Number of bits reserved for fractional part
13+
"""
14+
FixedPoint{T <: Integer, f} <: Real
15+
16+
Supertype of the two fixed-point number types: `Fixed{T, f}` and `Normed{T, f}`.
17+
18+
The parameter `T` is the underlying machine representation and `f` is the number
19+
of fraction bits.
20+
"""
1521
abstract type FixedPoint{T <: Integer, f} <: Real end
1622

1723

@@ -25,16 +31,20 @@ export
2531
# Functions
2632
scaledual
2733

34+
include("utilities.jl")
35+
36+
# reinterpretation
2837
reinterpret(x::FixedPoint) = x.i
2938
reinterpret(::Type{T}, x::FixedPoint{T,f}) where {T,f} = x.i
39+
reinterpret(::Type{X}, x::T) where {T <: Integer, X <: FixedPoint{T}} = X(x, 0)
40+
41+
# static parameters
42+
nbitsfrac(::Type{X}) where {T, f, X <: FixedPoint{T,f}} = f
43+
rawtype(::Type{X}) where {T, X <: FixedPoint{T}} = T
3044

3145
# construction using the (approximate) intended value, i.e., N0f8
3246
*(x::Real, ::Type{X}) where {X<:FixedPoint} = X(x)
3347

34-
# comparison
35-
==(x::T, y::T) where {T <: FixedPoint} = x.i == y.i
36-
<(x::T, y::T) where {T <: FixedPoint} = x.i < y.i
37-
<=(x::T, y::T) where {T <: FixedPoint} = x.i <= y.i
3848
"""
3949
isapprox(x::FixedPoint, y::FixedPoint; rtol=0, atol=max(eps(x), eps(y)))
4050
@@ -52,26 +62,22 @@ end
5262
# predicates
5363
isinteger(x::FixedPoint{T,f}) where {T,f} = (x.i&(1<<f-1)) == 0
5464

65+
# identities
66+
zero(::Type{X}) where {X <: FixedPoint} = X(zero(rawtype(X)), 0)
67+
oneunit(::Type{X}) where {X <: FixedPoint} = X(rawone(X), 0)
68+
one(::Type{X}) where {X <: FixedPoint} = oneunit(X)
69+
70+
# for Julia v1.0, which does not fold `div_float` before inlining
71+
inv_rawone(x) = (@generated) ? (y = 1.0 / rawone(x); :($y)) : 1.0 / rawone(x)
72+
5573
# traits
74+
sizeof(::Type{X}) where {X <: FixedPoint} = sizeof(rawtype(X))
75+
eps(::Type{X}) where {X <: FixedPoint} = X(oneunit(rawtype(X)), 0)
5676
typemax(::Type{T}) where {T <: FixedPoint} = T(typemax(rawtype(T)), 0)
5777
typemin(::Type{T}) where {T <: FixedPoint} = T(typemin(rawtype(T)), 0)
5878
floatmin(::Type{T}) where {T <: FixedPoint} = eps(T)
5979
floatmax(::Type{T}) where {T <: FixedPoint} = typemax(T)
6080

61-
widen1(::Type{Int8}) = Int16
62-
widen1(::Type{UInt8}) = UInt16
63-
widen1(::Type{Int16}) = Int32
64-
widen1(::Type{UInt16}) = UInt32
65-
widen1(::Type{Int32}) = Int64
66-
widen1(::Type{UInt32}) = UInt64
67-
widen1(::Type{Int64}) = Int128
68-
widen1(::Type{UInt64}) = UInt128
69-
widen1(::Type{Int128}) = Int128
70-
widen1(::Type{UInt128}) = UInt128
71-
widen1(x::Integer) = x % widen1(typeof(x))
72-
73-
const ShortInts = Union{Int8,UInt8,Int16,UInt16}
74-
const LongInts = Union{UInt64, UInt128, Int64, Int128, BigInt}
7581

7682
"""
7783
floattype(::Type{T})
@@ -100,36 +106,54 @@ floattype(::Type{T}) where {T <: Real} = T # fallback
100106
floattype(::Type{T}) where {T <: Union{ShortInts, Bool}} = Float32
101107
floattype(::Type{T}) where {T <: Integer} = Float64
102108
floattype(::Type{T}) where {T <: LongInts} = BigFloat
103-
floattype(::Type{FixedPoint{T,f}}) where {T <: ShortInts,f} = Float32
104-
floattype(::Type{FixedPoint{T,f}}) where {T <: Integer,f} = Float64
105-
floattype(::Type{FixedPoint{T,f}}) where {T <: LongInts,f} = BigFloat
106-
floattype(::Type{F}) where {F <: FixedPoint} = floattype(supertype(F))
107-
floattype(x::FixedPoint) = floattype(typeof(x))
109+
floattype(::Type{X}) where {T <: ShortInts, X <: FixedPoint{T}} = Float32
110+
floattype(::Type{X}) where {T <: Integer, X <: FixedPoint{T}} = Float64
111+
floattype(::Type{X}) where {T <: LongInts, X <: FixedPoint{T}} = BigFloat
108112

109-
nbitsfrac(::Type{FixedPoint{T,f}}) where {T <: Integer,f} = f
110-
nbitsfrac(::Type{F}) where {F <: FixedPoint} = nbitsfrac(supertype(F))
113+
float(x::FixedPoint) = convert(floattype(x), x)
111114

112-
rawtype(::Type{FixedPoint{T,f}}) where {T <: Integer,f} = T
113-
rawtype(::Type{F}) where {F <: FixedPoint} = rawtype(supertype(F))
114-
rawtype(x::FixedPoint) = rawtype(typeof(x))
115+
function minmax(x::X, y::X) where {X <: FixedPoint}
116+
a, b = minmax(reinterpret(x), reinterpret(y))
117+
X(a,0), X(b,0)
118+
end
119+
120+
bswap(x::X) where {X <: FixedPoint} = sizeof(X) == 1 ? x : X(bswap(x.i), 0)
115121

116-
# This IOBuffer is used during module definition to generate typealias names
117-
_iotypealias = IOBuffer()
122+
for f in (:zero, :oneunit, :one, :eps, :rawone, :rawtype, :floattype)
123+
@eval begin
124+
$f(x::FixedPoint) = $f(typeof(x))
125+
end
126+
end
127+
for f in (:(==), :<, :<=, :div, :fld, :fld1)
128+
@eval begin
129+
$f(x::X, y::X) where {X <: FixedPoint} = $f(x.i, y.i)
130+
end
131+
end
132+
for f in (:-, :~, :abs)
133+
@eval begin
134+
$f(x::X) where {X <: FixedPoint} = X($f(x.i), 0)
135+
end
136+
end
137+
for f in (:+, :-, :rem, :mod, :mod1, :min, :max)
138+
@eval begin
139+
$f(x::X, y::X) where {X <: FixedPoint} = X($f(x.i, y.i), 0)
140+
end
141+
end
118142

119143
# Printing. These are used to generate type-symbols, so we need them
120144
# before we include any files.
121145
function showtype(io::IO, ::Type{X}) where {X <: FixedPoint}
122146
print(io, typechar(X))
123147
f = nbitsfrac(X)
124-
m = sizeof(X)*8-f-signbits(X)
148+
m = bitwidth(X)-f-signbits(X)
125149
print(io, m, 'f', f)
126150
io
127151
end
128152
function show(io::IO, x::FixedPoint{T,f}) where {T,f}
129-
show(io, round(convert(Float64,x), digits=ceil(Int,f/_log2_10)))
153+
log10_2 = 0.3010299956639812
154+
show(io, round(convert(Float64,x), digits=ceil(Int, f * log10_2)))
130155
get(io, :compact, false) || showtype(io, typeof(x))
131156
end
132-
const _log2_10 = 3.321928094887362
133157

134158
function Base.showarg(io::IO, a::Array{T}, toplevel) where {T<:FixedPoint}
135159
toplevel || print(io, "::")
@@ -144,10 +168,6 @@ include("normed.jl")
144168
include("deprecations.jl")
145169
const UF = (N0f8, N6f10, N4f12, N2f14, N0f16)
146170

147-
eps(::Type{T}) where {T <: FixedPoint} = T(oneunit(rawtype(T)),0)
148-
eps(::T) where {T <: FixedPoint} = eps(T)
149-
sizeof(::Type{T}) where {T <: FixedPoint} = sizeof(rawtype(T))
150-
151171
# Promotions for reductions
152172
const Treduce = Float64
153173
Base.add_sum(x::FixedPoint, y::FixedPoint) = Treduce(x) + Treduce(y)
@@ -158,17 +178,6 @@ Base.reduce_empty(::typeof(Base.mul_prod), ::Type{F}) where {F<:FixedPoint} = on
158178
Base.reduce_first(::typeof(Base.mul_prod), x::FixedPoint) = Treduce(x)
159179

160180

161-
for f in (:div, :fld, :fld1)
162-
@eval begin
163-
$f(x::T, y::T) where {T <: FixedPoint} = $f(reinterpret(x),reinterpret(y))
164-
end
165-
end
166-
for f in (:rem, :mod, :mod1, :min, :max)
167-
@eval begin
168-
$f(x::T, y::T) where {T <: FixedPoint} = T($f(reinterpret(x),reinterpret(y)),0)
169-
end
170-
end
171-
172181
"""
173182
sd, ad = scaledual(s::Number, a)
174183
@@ -185,13 +194,13 @@ scaledual(::Type{Tdual}, x::FixedPoint) where Tdual = convert(Tdual, 1/rawone(x)
185194
scaledual(::Type{Tdual}, x::AbstractArray{T}) where {Tdual, T <: FixedPoint} =
186195
convert(Tdual, 1/rawone(T)), reinterpret(rawtype(T), x)
187196

188-
@noinline function throw_converterror(::Type{T}, x) where {T <: FixedPoint}
189-
n = 2^(8*sizeof(T))
190-
bitstring = sizeof(T) == 1 ? "an 8-bit" : "a $(8*sizeof(T))-bit"
197+
@noinline function throw_converterror(::Type{X}, x) where {X <: FixedPoint}
198+
n = 2^bitwidth(X)
199+
bitstring = bitwidth(X) == 8 ? "an 8-bit" : "a $(bitwidth(X))-bit"
191200
io = IOBuffer()
192-
show(IOContext(io, :compact=>true), typemin(T)); Tmin = String(take!(io))
193-
show(IOContext(io, :compact=>true), typemax(T)); Tmax = String(take!(io))
194-
throw(ArgumentError("$T is $bitstring type representing $n values from $Tmin to $Tmax; cannot represent $x"))
201+
show(IOContext(io, :compact=>true), typemin(X)); Xmin = String(take!(io))
202+
show(IOContext(io, :compact=>true), typemax(X)); Xmax = String(take!(io))
203+
throw(ArgumentError("$X is $bitstring type representing $n values from $Xmin to $Xmax; cannot represent $x"))
195204
end
196205

197206
rand(::Type{T}) where {T <: FixedPoint} = reinterpret(T, rand(rawtype(T)))

src/fixed.jl

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1-
# 32-bit fixed point; parameter `f` is the number of fraction bits
2-
struct Fixed{T <: Signed,f} <: FixedPoint{T, f}
1+
"""
2+
Fixed{T <: Signed, f} <: FixedPoint{T, f}
3+
4+
`Fixed{T,f}` maps `Signed` integers from `-2^f` to `2^f` to the range
5+
[-1.0, 1.0]. For example, `Fixed{Int8,7}` maps `-128` to `-1.0` and `127` to
6+
`127/128 ≈ 0.992`.
7+
8+
There are the typealiases for `Fixed` in the `QXfY` notation, where `Y` is
9+
the number of fractional bits (i.e. `f`), and `X+Y+1` equals the number of
10+
underlying bits used (`+1` means the sign bit). For example, `Q0f7` is aliased
11+
to `Fixed{Int8,7}` and `Q3f12` is aliased to `Fixed{Int16,12}`.
12+
"""
13+
struct Fixed{T <: Signed, f} <: FixedPoint{T, f}
314
i::T
415

516
# constructor for manipulating the representation;
@@ -14,30 +25,29 @@ Fixed{T,f}(x::Integer) where {T,f} = Fixed{T,f}(round(T, convert(widen1(T),x)<<f
1425
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)
1526
Fixed{T,f}(x::Rational) where {T,f} = Fixed{T,f}(x.num)/Fixed{T,f}(x.den)
1627

17-
reinterpret(::Type{Fixed{T,f}}, x::T) where {T <: Signed,f} = Fixed{T,f}(x, 0)
18-
1928
typechar(::Type{X}) where {X <: Fixed} = 'Q'
2029
signbits(::Type{X}) where {X <: Fixed} = 1
2130

2231
for T in (Int8, Int16, Int32, Int64)
23-
for f in 0:sizeof(T)*8-1
24-
sym = Symbol(String(take!(showtype(_iotypealias, Fixed{T,f}))))
32+
io = IOBuffer()
33+
for f in 0:bitwidth(T)-1
34+
sym = Symbol(String(take!(showtype(io, Fixed{T,f}))))
2535
@eval begin
2636
const $sym = Fixed{$T,$f}
2737
export $sym
2838
end
2939
end
3040
end
3141

32-
# basic operators
33-
-(x::Fixed{T,f}) where {T,f} = Fixed{T,f}(-x.i,0)
34-
abs(x::Fixed{T,f}) where {T,f} = Fixed{T,f}(abs(x.i),0)
42+
function rawone(::Type{Fixed{T,f}}) where {T, f}
43+
f >= bitwidth(T)-1 && throw_converterror(Fixed{T,f}, 1)
44+
oneunit(T) << f
45+
end
3546

36-
+(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(x.i+y.i,0)
37-
-(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}(x.i-y.i,0)
47+
# unchecked arithmetic
3848

3949
# with truncation:
40-
#*{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(Base.widemul(x.i,y.i)>>f,0)
50+
#*(x::Fixed{T,f}, y::Fixed{T,f}) = Fixed{T,f}(Base.widemul(x.i,y.i)>>f,0)
4151
# with rounding up:
4252
*(x::Fixed{T,f}, y::Fixed{T,f}) where {T,f} = Fixed{T,f}((Base.widemul(x.i,y.i) + (one(widen(T)) << (f-1)))>>f,0)
4353

@@ -56,7 +66,6 @@ end
5666
rem(x::Integer, ::Type{Fixed{T,f}}) where {T,f} = Fixed{T,f}(rem(x,T)<<f,0)
5767
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)
5868

59-
float(x::Fixed) = convert(floattype(x), x)
6069

6170
Base.BigFloat(x::Fixed{T,f}) where {T,f} =
6271
BigFloat(x.i>>f) + BigFloat(x.i&(one(widen1(T))<<f - 1))/BigFloat(one(widen1(T))<<f)
@@ -84,13 +93,13 @@ promote_rule(::Type{Fixed{T,f}}, ::Type{Rational{TR}}) where {T,f,TR} = Rational
8493
f = max(f1, f2) # ensure we have enough precision
8594
T = promote_type(T1, T2)
8695
# make sure we have enough integer bits
87-
i1, i2 = 8*sizeof(T1)-f1, 8*sizeof(T2)-f2 # number of integer bits for each
88-
i = 8*sizeof(T)-f
96+
i1, i2 = bitwidth(T1)-f1, bitwidth(T2)-f2 # number of integer bits for each
97+
i = bitwidth(T)-f
8998
while i < max(i1, i2)
9099
Tw = widen1(T)
91100
T == Tw && break
92101
T = Tw
93-
i = 8*sizeof(T)-f
102+
i = bitwidth(T)-f
94103
end
95104
:(Fixed{$T,$f})
96105
end

0 commit comments

Comments
 (0)