Skip to content

Commit 4f00cc0

Browse files
committed
Merge pull request #2 from timholy/pull-request/42c3d8bf
Add unsigned fixed-point numbers
2 parents 0f8c1e5 + 477fb63 commit 4f00cc0

File tree

8 files changed

+416
-73
lines changed

8 files changed

+416
-73
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@ before_install:
1414
- sudo apt-get install libpcre3-dev julia -y
1515
script:
1616
- julia -e 'Pkg.init(); run(`ln -s $(pwd()) $(Pkg.dir("FixedPoint"))`); Pkg.pin("FixedPoint"); Pkg.resolve()'
17-
- julia test/test.jl
17+
- julia test/runtests.jl

README.md

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,41 @@
11
# FixedPoint
22

3-
This library exports a 32-bit fixed-point type `Fixed32{f}`.
3+
This library exports fixed-point number types.
4+
A [fixed-point number][wikipedia] represents a fractional, or non-integral, number.
5+
In contrast with the more widely known floating-point numbers, fixed-point
6+
numbers have a fixed number of digits (bits) after the decimal (radix) point.
7+
They are effectively integers scaled by a constant factor.
8+
9+
Fixed-point numbers can be used to perform arithmetic. Another practical
10+
application is to implicitly rescale integers without modifying the
11+
underlying representation.
12+
13+
This library exports two categories of fixed-point types. Fixed-point types are
14+
used like any other number: they can be added, multiplied, raised to a power,
15+
etc. In many cases these operations result in conversion to floating-point types.
16+
17+
## Fixed32 (signed fixed-point numbers)
18+
19+
For signed integers, there is a 32-bit fixed-point type `Fixed32{f}`.
420
The parameter `f` is the number of fraction bits. There is also an abstract subtype of
521
`Real` called `Fixed`.
622

723
To use it, convert numbers to a `Fixed32` type, or call `Fixed32(x)`, which will default
8-
to constructing a `Fixed32{16}`. Then use ordinary arithmetic.
24+
to constructing a `Fixed32{16}`.
25+
26+
## Ufixed (unsigned fixed-point numbers)
27+
28+
For unsigned integers, there is a family of subtypes of the abstract `Ufixed` type.
29+
These types, built with `f` fraction bits, map the closed interval [0.0,1.0]
30+
to the span of numbers with `f` bits.
31+
For example, the `Ufixed8` type is represented internally by a `Uint8`, and makes
32+
`0x00` equivalent to `0.0` and `0xff` to `1.0`.
33+
The types `Ufixed10`, `Ufixed12`, `Ufixed14`, and `Ufixed16` are all based on `Uint16`
34+
and reach the value `1.0` at 10, 12, 14, and 16 bits, respectively (`0x03ff`, `0x0fff`,
35+
`0x3fff`, and `0xffff`).
36+
37+
To construct such a number, use `convert(Ufixed12, 1.3)` or the literal syntax `0x14ccuf12`.
38+
The latter syntax means to construct a `Ufixed12` (it ends in `uf12`) from the `Uint16` value
39+
`0x14cc`.
40+
41+
[wikipedia]: http://en.wikipedia.org/wiki/Fixed-point_arithmetic

src/FixedPoint.jl

Lines changed: 47 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -1,76 +1,53 @@
11
module FixedPoint
22

3-
import Base: convert, promote_rule, show, showcompact, isinteger, abs
4-
5-
export Fixed, Fixed32
6-
7-
abstract Fixed <: Real
8-
9-
# 32-bit fixed point; parameter `f` is the number of fraction bits
10-
immutable Fixed32{f} <: Fixed
11-
i::Int32
12-
13-
# constructor for manipulating the representation;
14-
# selected by passing an extra dummy argument
15-
Fixed32(i::Integer,_) = new(i)
16-
17-
Fixed32(x) = convert(Fixed32{f}, x)
3+
import Base: convert, promote_rule, show, showcompact, isinteger, abs,
4+
zero, one, typemin, typemax, realmin, realmax, eps, sizeof, reinterpret,
5+
trunc, round, floor, ceil, itrunc, iround, ifloor, iceil, bswap,
6+
div, fld, rem, mod, mod1, rem1, fld1, min, max,
7+
start, next, done
8+
9+
abstract AbstractFixed <: Real
10+
abstract Fixed <: AbstractFixed
11+
abstract Ufixed <: AbstractFixed # unsigned variant
12+
13+
export
14+
AbstractFixed,
15+
Fixed,
16+
Ufixed,
17+
Fixed32,
18+
Ufixed8,
19+
Ufixed10,
20+
Ufixed12,
21+
Ufixed14,
22+
Ufixed16,
23+
# literal constructor constants
24+
uf8,
25+
uf10,
26+
uf12,
27+
uf14,
28+
uf16,
29+
# Functions
30+
scaledual
31+
32+
reinterpret(x::AbstractFixed) = x.i
33+
34+
include("fixed32.jl")
35+
include("ufixed.jl")
36+
37+
for T in tuple(Fixed32, UF...)
38+
R = rawtype(T)
39+
@eval begin
40+
reinterpret(::Type{$R}, x::$T) = x.i
41+
end
1842
end
1943

20-
Fixed32(x::Real) = convert(Fixed32{16}, x)
21-
22-
# comparisons
23-
=={f}(x::Fixed32{f}, y::Fixed32{f}) = x.i == y.i
24-
< {f}(x::Fixed32{f}, y::Fixed32{f}) = x.i < y.i
25-
<={f}(x::Fixed32{f}, y::Fixed32{f}) = x.i <= y.i
26-
27-
# predicates
28-
isinteger{f}(x::Fixed32{f}) = (x.i&(1<<f-1)) == 0
29-
30-
# basic operators
31-
-{f}(x::Fixed32{f}) = Fixed32{f}(-x.i,0)
32-
abs{f}(x::Fixed32{f}) = Fixed32{f}(abs(x.i),0)
33-
34-
+{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(x.i+y.i,0)
35-
-{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(x.i-y.i,0)
36-
37-
# with truncation:
38-
#*{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(Base.widemul(x.i,y.i)>>f,0)
39-
# with rounding up:
40-
*{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}((Base.widemul(x.i,y.i)+(int64(1)<<(f-1)))>>f,0)
41-
42-
/{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(div(int64(x.i)<<f, y.i),0)
43-
44-
# conversions and promotions
45-
convert{f}(::Type{Fixed32{f}}, x::Integer) = Fixed32{f}(x<<f,0)
46-
convert{f}(::Type{Fixed32{f}}, x::FloatingPoint) = Fixed32{f}(itrunc(x)<<f + int32(rem(x,1)*(1<<f)),0)
47-
convert{f}(::Type{Fixed32{f}}, x::Rational) = Fixed32{f}(x.num)/Fixed32{f}(x.den)
48-
49-
convert{f}(::Type{BigFloat}, x::Fixed32{f}) =
50-
convert(BigFloat,x.i>>f) + convert(BigFloat,x.i&(1<<f - 1))/convert(BigFloat,1<<f)
51-
convert{T<:FloatingPoint, f}(::Type{T}, x::Fixed32{f}) =
52-
convert(T,x.i>>f) + convert(T,x.i&(1<<f - 1))/convert(T,1<<f)
53-
54-
convert(::Type{Bool}, x::Fixed32) = x.i!=0
55-
function convert{T<:Integer, f}(::Type{T}, x::Fixed32{f})
56-
isinteger(x) || throw(InexactError())
57-
x.i>>f
58-
end
59-
60-
convert{T<:Rational, f}(::Type{T}, x::Fixed32{f}) =
61-
convert(T, x.i>>f + (x.i&(1<<f-1))//(1<<f))
62-
63-
promote_rule{f,T<:Integer}(ft::Type{Fixed32{f}}, ::Type{T}) = ft
64-
promote_rule{f,T<:FloatingPoint}(::Type{Fixed32{f}}, ::Type{T}) = T
65-
66-
# printing
67-
function show(io::IO, x::Fixed32)
68-
print(io, typeof(x))
69-
print(io, "(")
70-
showcompact(io, x)
71-
print(io, ")")
72-
end
73-
const _log2_10 = 3.321928094887362
74-
showcompact{f}(io::IO, x::Fixed32{f}) = show(io, round(convert(Float64,x), iceil(f/_log2_10)))
44+
# When multiplying by a float, reduce two multiplies to one.
45+
# Particularly useful for arrays.
46+
scaledual(Tdual::Type, x) = one(Tdual), x
47+
scaledual{Tdual<:Number}(b::Tdual, x) = b, x
48+
scaledual{T<:AbstractFixed}(Tdual::Type, x::Union(T, AbstractArray{T})) =
49+
convert(Tdual, 1/one(T)), reinterpret(rawtype(T), x)
50+
scaledual{Tdual<:Number, T<:AbstractFixed}(b::Tdual, x::Union(T, AbstractArray{T})) =
51+
convert(Tdual, b/one(T)), reinterpret(rawtype(T), x)
7552

7653
end # module

src/fixed32.jl

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# 32-bit fixed point; parameter `f` is the number of fraction bits
2+
immutable Fixed32{f} <: Fixed
3+
i::Int32
4+
5+
# constructor for manipulating the representation;
6+
# selected by passing an extra dummy argument
7+
Fixed32(i::Integer,_) = new(i)
8+
9+
Fixed32(x) = convert(Fixed32{f}, x)
10+
end
11+
12+
Fixed32(x::Real) = convert(Fixed32{16}, x)
13+
14+
rawtype(::Type{Fixed32}) = Int32
15+
rawtype{f}(::Type{Fixed32{f}}) = Int32
16+
nbitsfrac{f}(::Type{Fixed32{f}}) = f
17+
18+
# comparisons
19+
=={f}(x::Fixed32{f}, y::Fixed32{f}) = x.i == y.i
20+
< {f}(x::Fixed32{f}, y::Fixed32{f}) = x.i < y.i
21+
<={f}(x::Fixed32{f}, y::Fixed32{f}) = x.i <= y.i
22+
23+
# predicates
24+
isinteger{f}(x::Fixed32{f}) = (x.i&(1<<f-1)) == 0
25+
26+
# basic operators
27+
-{f}(x::Fixed32{f}) = Fixed32{f}(-x.i,0)
28+
abs{f}(x::Fixed32{f}) = Fixed32{f}(abs(x.i),0)
29+
30+
+{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(x.i+y.i,0)
31+
-{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(x.i-y.i,0)
32+
33+
# with truncation:
34+
#*{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(Base.widemul(x.i,y.i)>>f,0)
35+
# with rounding up:
36+
*{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}((Base.widemul(x.i,y.i)+(int64(1)<<(f-1)))>>f,0)
37+
38+
/{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(div(int64(x.i)<<f, y.i),0)
39+
40+
# conversions and promotions
41+
convert{f}(::Type{Fixed32{f}}, x::Integer) = Fixed32{f}(x<<f,0)
42+
convert{f}(::Type{Fixed32{f}}, x::FloatingPoint) = Fixed32{f}(itrunc(x)<<f + int32(rem(x,1)*(1<<f)),0)
43+
convert{f}(::Type{Fixed32{f}}, x::Rational) = Fixed32{f}(x.num)/Fixed32{f}(x.den)
44+
45+
convert{f}(::Type{BigFloat}, x::Fixed32{f}) =
46+
convert(BigFloat,x.i>>f) + convert(BigFloat,x.i&(1<<f - 1))/convert(BigFloat,1<<f)
47+
convert{T<:FloatingPoint, f}(::Type{T}, x::Fixed32{f}) =
48+
convert(T,x.i>>f) + convert(T,x.i&(1<<f - 1))/convert(T,1<<f)
49+
50+
convert(::Type{Bool}, x::Fixed32) = x.i!=0
51+
function convert{T<:Integer, f}(::Type{T}, x::Fixed32{f})
52+
isinteger(x) || throw(InexactError())
53+
x.i>>f
54+
end
55+
56+
convert{T<:Rational, f}(::Type{T}, x::Fixed32{f}) =
57+
convert(T, x.i>>f + (x.i&(1<<f-1))//(1<<f))
58+
59+
promote_rule{f,T<:Integer}(ft::Type{Fixed32{f}}, ::Type{T}) = ft
60+
promote_rule{f,T<:FloatingPoint}(::Type{Fixed32{f}}, ::Type{T}) = T
61+
62+
# printing
63+
function show(io::IO, x::Fixed32)
64+
print(io, typeof(x))
65+
print(io, "(")
66+
showcompact(io, x)
67+
print(io, ")")
68+
end
69+
const _log2_10 = 3.321928094887362
70+
showcompact{f}(io::IO, x::Fixed32{f}) = show(io, round(convert(Float64,x), iceil(f/_log2_10)))

src/ufixed.jl

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
# UfixedBase{T,f} maps Uints from 0 to 2^f-1 to the range [0.0, 1.0]
2+
# For example, a Ufixed8 maps 0x00 to 0.0 and 0xff to 1.0
3+
4+
immutable UfixedBase{T<:Unsigned,f} <: Ufixed
5+
i::T
6+
7+
UfixedBase(i::Integer,_) = new(i) # for setting by raw representation
8+
UfixedBase(x) = convert(UfixedBase{T,f}, x)
9+
end
10+
11+
typealias Ufixed8 UfixedBase{Uint8,8}
12+
typealias Ufixed10 UfixedBase{Uint16,10}
13+
typealias Ufixed12 UfixedBase{Uint16,12}
14+
typealias Ufixed14 UfixedBase{Uint16,14}
15+
typealias Ufixed16 UfixedBase{Uint16,16}
16+
17+
const UF = (Ufixed8, Ufixed10, Ufixed12, Ufixed14, Ufixed16)
18+
19+
rawtype{T,f}(::Type{UfixedBase{T,f}}) = T
20+
nbitsfrac{T,f}(::Type{UfixedBase{T,f}}) = f
21+
22+
# The next lines mimic the floating-point literal syntax "3.2f0"
23+
immutable UfixedConstructor{T,f} end
24+
*{T,f}(n::Integer, ::UfixedConstructor{T,f}) = UfixedBase{T,f}(n,0)
25+
const uf8 = UfixedConstructor{Uint8,8}()
26+
const uf10 = UfixedConstructor{Uint16,10}()
27+
const uf12 = UfixedConstructor{Uint16,12}()
28+
const uf14 = UfixedConstructor{Uint16,14}()
29+
const uf16 = UfixedConstructor{Uint16,16}()
30+
31+
zero{T,f}(::Type{UfixedBase{T,f}}) = UfixedBase{T,f}(zero(T),0)
32+
for T in UF
33+
f = nbitsfrac(T)
34+
@eval begin
35+
one(::Type{$T}) = UfixedBase{$(rawtype(T)),$f}($(2^f-1),0)
36+
end
37+
end
38+
zero(x::Ufixed) = zero(typeof(x))
39+
one(x::Ufixed) = one(typeof(x))
40+
rawone(v) = reinterpret(one(v))
41+
42+
# Conversions
43+
convert{T<:Ufixed}(::Type{T}, x::Real) = T(iround(rawtype(T), rawone(T)*x),0)
44+
45+
convert(::Type{BigFloat}, x::Ufixed) = reinterpret(x)*(1/BigFloat(rawone(x)))
46+
convert{T<:FloatingPoint}(::Type{T}, x::Ufixed) = reinterpret(x)*(1/convert(T, rawone(x)))
47+
convert(::Type{Bool}, x::Ufixed) = x == zero(x) ? false : true
48+
convert{T<:Integer}(::Type{T}, x::Ufixed) = convert(T, x*(1/one(T)))
49+
convert{Ti<:Integer}(::Type{Rational{Ti}}, x::Ufixed) = convert(Ti, reinterpret(x))//convert(Ti, rawone(x))
50+
convert(::Type{Rational}, x::Ufixed) = reinterpret(x)//rawone(x)
51+
52+
# Traits
53+
typemin{T<:Ufixed}(::Type{T}) = zero(T)
54+
typemax{T<:Ufixed}(::Type{T}) = T(typemax(rawtype(T)),0)
55+
realmin{T<:Ufixed}(::Type{T}) = typemin(T)
56+
realmax{T<:Ufixed}(::Type{T}) = typemax(T)
57+
eps{T<:Ufixed}(::Type{T}) = T(one(rawtype(T)),0)
58+
eps{T<:Ufixed}(::T) = eps(T)
59+
sizeof{T<:Ufixed}(::Type{T}) = sizeof(rawtype(T))
60+
61+
# Arithmetic
62+
# Ufixed types are closed under addition and subtraction
63+
+{T,f}(x::UfixedBase{T,f}, y::UfixedBase{T,f}) = UfixedBase{T,f}(convert(T, reinterpret(x)+reinterpret(y)),0)
64+
-{T,f}(x::UfixedBase{T,f}, y::UfixedBase{T,f}) = UfixedBase{T,f}(convert(T, reinterpret(x)-reinterpret(y)),0)
65+
*{T,f}(x::UfixedBase{T,f}, y::UfixedBase{T,f}) = float32(x)*float32(y)
66+
/(x::Ufixed, y::Ufixed) = float32(x)/float32(y)
67+
68+
# Comparisons
69+
< {T<:Ufixed}(x::T, y::T) = reinterpret(x) < reinterpret(y)
70+
<={T<:Ufixed}(x::T, y::T) = reinterpret(x) < reinterpret(y)
71+
72+
# Functions
73+
trunc{T<:Ufixed}(x::T) = T(div(reinterpret(x), rawone(T))*rawone(T),0)
74+
floor{T<:Ufixed}(x::T) = trunc(x)
75+
for T in UF
76+
f = nbitsfrac(T)
77+
R = rawtype(T)
78+
roundmask = convert(R, 1<<(f-1))
79+
k = 8*sizeof(R)-f
80+
ceilmask = (typemax(R)<<k)>>k
81+
@eval begin
82+
round(x::$T) = (y = trunc(x); return reinterpret(x-y)&$roundmask>0 ? y+one($T) : y)
83+
ceil(x::$T) = (y = trunc(x); return reinterpret(x-y)&$ceilmask >0 ? y+one($T) : y)
84+
end
85+
end
86+
87+
itrunc{T<:Integer}(::Type{T}, x::Ufixed) = convert(T, div(reinterpret(x), rawone(x)))
88+
iround{T<:Integer}(::Type{T}, x::Ufixed) = iround(T, reinterpret(x)/rawone(x))
89+
ifloor{T<:Integer}(::Type{T}, x::Ufixed) = itrunc(T, x)
90+
iceil{T<:Integer}(::Type{T}, x::Ufixed) = iceil(T, reinterpret(x)/rawone(x))
91+
itrunc(x::Ufixed) = itrunc(Int, x)
92+
iround(x::Ufixed) = iround(Int, x)
93+
ifloor(x::Ufixed) = ifloor(Int, x)
94+
iceil(x::Ufixed) = iceil(Int, x)
95+
96+
bswap{f}(x::UfixedBase{Uint8,f}) = x
97+
bswap(x::Ufixed) = typeof(x)(bswap(reinterpret(x)),0)
98+
99+
for f in (:div, :fld, :rem, :mod, :mod1, :rem1, :fld1, :min, :max)
100+
@eval begin
101+
$f{T<:Ufixed}(x::T, y::T) = T($f(reinterpret(x),reinterpret(y)),0)
102+
end
103+
end
104+
function minmax{T<:Ufixed}(x::T, y::T)
105+
a, b = minmax(reinterpret(x), reinterpret(y))
106+
T(a,0), T(b,0)
107+
end
108+
109+
# Iteration
110+
# The main subtlety here is that iterating over 0x00uf8:0xffuf8 will wrap around
111+
# unless we iterate using a wider type
112+
if VERSION < v"0.3-"
113+
start{T<:Ufixed}(r::Range{T}) = convert(typeof(reinterpret(r.start)+reinterpret(r.step)), reinterpret(r.start))
114+
next{T<:Ufixed}(r::Range{T}, i::Integer) = (T(i,0), i+reinterpret(r.step))
115+
done{T<:Ufixed}(r::Range{T}, i::Integer) = isempty(r) || (i > r.len)
116+
else
117+
start{T<:Ufixed}(r::StepRange{T}) = convert(typeof(reinterpret(r.start)+reinterpret(r.step)), reinterpret(r.start))
118+
next{T<:Ufixed}(r::StepRange{T}, i::Integer) = (T(i,0), i+reinterpret(r.step))
119+
done{T<:Ufixed}(r::StepRange{T}, i::Integer) = isempty(r) || (i > reinterpret(r.stop))
120+
end
121+
122+
# Promotions
123+
for T in UF
124+
@eval begin
125+
promote_rule(::Type{$T}, ::Type{Float32}) = Float32
126+
promote_rule(::Type{$T}, ::Type{Float64}) = Float64
127+
promote_rule{TR<:Rational}(::Type{$T}, ::Type{TR}) = TR
128+
end
129+
for Ti in (Int8, Uint8, Int16, Uint16, Int32, Uint32, Int64, Uint64)
130+
Tp = eps(float32(typemax(Ti))) > eps(T) ? Float64 : Float32
131+
@eval begin
132+
promote_rule(::Type{$T}, ::Type{$Ti}) = $Tp
133+
end
134+
end
135+
end
136+
137+
# Show
138+
function show(io::IO, x::Ufixed)
139+
print(io, "Ufixed", nbitsfrac(typeof(x)))
140+
print(io, "(")
141+
showcompact(io, x)
142+
print(io, ")")
143+
end
144+
showcompact(io::IO, x::Ufixed) = show(io, round(convert(Float64,x), iceil(nbitsfrac(typeof(x))/_log2_10)))
File renamed without changes.

test/runtests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include("fixed32.jl")
2+
include("ufixed.jl")

0 commit comments

Comments
 (0)