Skip to content

Add unsigned fixed-point numbers #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 4, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@ before_install:
- sudo apt-get install libpcre3-dev julia -y
script:
- julia -e 'Pkg.init(); run(`ln -s $(pwd()) $(Pkg.dir("FixedPoint"))`); Pkg.pin("FixedPoint"); Pkg.resolve()'
- julia test/test.jl
- julia test/runtests.jl
37 changes: 35 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,41 @@
# FixedPoint

This library exports a 32-bit fixed-point type `Fixed32{f}`.
This library exports fixed-point number types.
A [fixed-point number][wikipedia] represents a fractional, or non-integral, number.
In contrast with the more widely known floating-point numbers, fixed-point
numbers have a fixed number of digits (bits) after the decimal (radix) point.
They are effectively integers scaled by a constant factor.

Fixed-point numbers can be used to perform arithmetic. Another practical
application is to implicitly rescale integers without modifying the
underlying representation.

This library exports two categories of fixed-point types. Fixed-point types are
used like any other number: they can be added, multiplied, raised to a power,
etc. In many cases these operations result in conversion to floating-point types.

## Fixed32 (signed fixed-point numbers)

For signed integers, there is a 32-bit fixed-point type `Fixed32{f}`.
The parameter `f` is the number of fraction bits. There is also an abstract subtype of
`Real` called `Fixed`.

To use it, convert numbers to a `Fixed32` type, or call `Fixed32(x)`, which will default
to constructing a `Fixed32{16}`. Then use ordinary arithmetic.
to constructing a `Fixed32{16}`.

## Ufixed (unsigned fixed-point numbers)

For unsigned integers, there is a family of subtypes of the abstract `Ufixed` type.
These types, built with `f` fraction bits, map the closed interval [0.0,1.0]
to the span of numbers with `f` bits.
For example, the `Ufixed8` type is represented internally by a `Uint8`, and makes
`0x00` equivalent to `0.0` and `0xff` to `1.0`.
The types `Ufixed10`, `Ufixed12`, `Ufixed14`, and `Ufixed16` are all based on `Uint16`
and reach the value `1.0` at 10, 12, 14, and 16 bits, respectively (`0x03ff`, `0x0fff`,
`0x3fff`, and `0xffff`).

To construct such a number, use `convert(Ufixed12, 1.3)` or the literal syntax `0x14ccuf12`.
The latter syntax means to construct a `Ufixed12` (it ends in `uf12`) from the `Uint16` value
`0x14cc`.

[wikipedia]: http://en.wikipedia.org/wiki/Fixed-point_arithmetic
117 changes: 47 additions & 70 deletions src/FixedPoint.jl
Original file line number Diff line number Diff line change
@@ -1,76 +1,53 @@
module FixedPoint

import Base: convert, promote_rule, show, showcompact, isinteger, abs

export Fixed, Fixed32

abstract Fixed <: Real

# 32-bit fixed point; parameter `f` is the number of fraction bits
immutable Fixed32{f} <: Fixed
i::Int32

# constructor for manipulating the representation;
# selected by passing an extra dummy argument
Fixed32(i::Integer,_) = new(i)

Fixed32(x) = convert(Fixed32{f}, x)
import Base: convert, promote_rule, show, showcompact, isinteger, abs,
zero, one, typemin, typemax, realmin, realmax, eps, sizeof, reinterpret,
trunc, round, floor, ceil, itrunc, iround, ifloor, iceil, bswap,
div, fld, rem, mod, mod1, rem1, fld1, min, max,
start, next, done

abstract AbstractFixed <: Real
abstract Fixed <: AbstractFixed
abstract Ufixed <: AbstractFixed # unsigned variant

export
AbstractFixed,
Fixed,
Ufixed,
Fixed32,
Ufixed8,
Ufixed10,
Ufixed12,
Ufixed14,
Ufixed16,
# literal constructor constants
uf8,
uf10,
uf12,
uf14,
uf16,
# Functions
scaledual

reinterpret(x::AbstractFixed) = x.i

include("fixed32.jl")
include("ufixed.jl")

for T in tuple(Fixed32, UF...)
R = rawtype(T)
@eval begin
reinterpret(::Type{$R}, x::$T) = x.i
end
end

Fixed32(x::Real) = convert(Fixed32{16}, x)

# comparisons
=={f}(x::Fixed32{f}, y::Fixed32{f}) = x.i == y.i
< {f}(x::Fixed32{f}, y::Fixed32{f}) = x.i < y.i
<={f}(x::Fixed32{f}, y::Fixed32{f}) = x.i <= y.i

# predicates
isinteger{f}(x::Fixed32{f}) = (x.i&(1<<f-1)) == 0

# basic operators
-{f}(x::Fixed32{f}) = Fixed32{f}(-x.i,0)
abs{f}(x::Fixed32{f}) = Fixed32{f}(abs(x.i),0)

+{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(x.i+y.i,0)
-{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(x.i-y.i,0)

# with truncation:
#*{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(Base.widemul(x.i,y.i)>>f,0)
# with rounding up:
*{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}((Base.widemul(x.i,y.i)+(int64(1)<<(f-1)))>>f,0)

/{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(div(int64(x.i)<<f, y.i),0)

# conversions and promotions
convert{f}(::Type{Fixed32{f}}, x::Integer) = Fixed32{f}(x<<f,0)
convert{f}(::Type{Fixed32{f}}, x::FloatingPoint) = Fixed32{f}(itrunc(x)<<f + int32(rem(x,1)*(1<<f)),0)
convert{f}(::Type{Fixed32{f}}, x::Rational) = Fixed32{f}(x.num)/Fixed32{f}(x.den)

convert{f}(::Type{BigFloat}, x::Fixed32{f}) =
convert(BigFloat,x.i>>f) + convert(BigFloat,x.i&(1<<f - 1))/convert(BigFloat,1<<f)
convert{T<:FloatingPoint, f}(::Type{T}, x::Fixed32{f}) =
convert(T,x.i>>f) + convert(T,x.i&(1<<f - 1))/convert(T,1<<f)

convert(::Type{Bool}, x::Fixed32) = x.i!=0
function convert{T<:Integer, f}(::Type{T}, x::Fixed32{f})
isinteger(x) || throw(InexactError())
x.i>>f
end

convert{T<:Rational, f}(::Type{T}, x::Fixed32{f}) =
convert(T, x.i>>f + (x.i&(1<<f-1))//(1<<f))

promote_rule{f,T<:Integer}(ft::Type{Fixed32{f}}, ::Type{T}) = ft
promote_rule{f,T<:FloatingPoint}(::Type{Fixed32{f}}, ::Type{T}) = T

# printing
function show(io::IO, x::Fixed32)
print(io, typeof(x))
print(io, "(")
showcompact(io, x)
print(io, ")")
end
const _log2_10 = 3.321928094887362
showcompact{f}(io::IO, x::Fixed32{f}) = show(io, round(convert(Float64,x), iceil(f/_log2_10)))
# When multiplying by a float, reduce two multiplies to one.
# Particularly useful for arrays.
scaledual(Tdual::Type, x) = one(Tdual), x
scaledual{Tdual<:Number}(b::Tdual, x) = b, x
scaledual{T<:AbstractFixed}(Tdual::Type, x::Union(T, AbstractArray{T})) =
convert(Tdual, 1/one(T)), reinterpret(rawtype(T), x)
scaledual{Tdual<:Number, T<:AbstractFixed}(b::Tdual, x::Union(T, AbstractArray{T})) =
convert(Tdual, b/one(T)), reinterpret(rawtype(T), x)

end # module
70 changes: 70 additions & 0 deletions src/fixed32.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# 32-bit fixed point; parameter `f` is the number of fraction bits
immutable Fixed32{f} <: Fixed
i::Int32

# constructor for manipulating the representation;
# selected by passing an extra dummy argument
Fixed32(i::Integer,_) = new(i)

Fixed32(x) = convert(Fixed32{f}, x)
end

Fixed32(x::Real) = convert(Fixed32{16}, x)

rawtype(::Type{Fixed32}) = Int32
rawtype{f}(::Type{Fixed32{f}}) = Int32
nbitsfrac{f}(::Type{Fixed32{f}}) = f

# comparisons
=={f}(x::Fixed32{f}, y::Fixed32{f}) = x.i == y.i
< {f}(x::Fixed32{f}, y::Fixed32{f}) = x.i < y.i
<={f}(x::Fixed32{f}, y::Fixed32{f}) = x.i <= y.i

# predicates
isinteger{f}(x::Fixed32{f}) = (x.i&(1<<f-1)) == 0

# basic operators
-{f}(x::Fixed32{f}) = Fixed32{f}(-x.i,0)
abs{f}(x::Fixed32{f}) = Fixed32{f}(abs(x.i),0)

+{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(x.i+y.i,0)
-{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(x.i-y.i,0)

# with truncation:
#*{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(Base.widemul(x.i,y.i)>>f,0)
# with rounding up:
*{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}((Base.widemul(x.i,y.i)+(int64(1)<<(f-1)))>>f,0)

/{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(div(int64(x.i)<<f, y.i),0)

# conversions and promotions
convert{f}(::Type{Fixed32{f}}, x::Integer) = Fixed32{f}(x<<f,0)
convert{f}(::Type{Fixed32{f}}, x::FloatingPoint) = Fixed32{f}(itrunc(x)<<f + int32(rem(x,1)*(1<<f)),0)
convert{f}(::Type{Fixed32{f}}, x::Rational) = Fixed32{f}(x.num)/Fixed32{f}(x.den)

convert{f}(::Type{BigFloat}, x::Fixed32{f}) =
convert(BigFloat,x.i>>f) + convert(BigFloat,x.i&(1<<f - 1))/convert(BigFloat,1<<f)
convert{T<:FloatingPoint, f}(::Type{T}, x::Fixed32{f}) =
convert(T,x.i>>f) + convert(T,x.i&(1<<f - 1))/convert(T,1<<f)

convert(::Type{Bool}, x::Fixed32) = x.i!=0
function convert{T<:Integer, f}(::Type{T}, x::Fixed32{f})
isinteger(x) || throw(InexactError())
x.i>>f
end

convert{T<:Rational, f}(::Type{T}, x::Fixed32{f}) =
convert(T, x.i>>f + (x.i&(1<<f-1))//(1<<f))

promote_rule{f,T<:Integer}(ft::Type{Fixed32{f}}, ::Type{T}) = ft
promote_rule{f,T<:FloatingPoint}(::Type{Fixed32{f}}, ::Type{T}) = T

# printing
function show(io::IO, x::Fixed32)
print(io, typeof(x))
print(io, "(")
showcompact(io, x)
print(io, ")")
end
const _log2_10 = 3.321928094887362
showcompact{f}(io::IO, x::Fixed32{f}) = show(io, round(convert(Float64,x), iceil(f/_log2_10)))
144 changes: 144 additions & 0 deletions src/ufixed.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# UfixedBase{T,f} maps Uints from 0 to 2^f-1 to the range [0.0, 1.0]
# For example, a Ufixed8 maps 0x00 to 0.0 and 0xff to 1.0

immutable UfixedBase{T<:Unsigned,f} <: Ufixed
i::T

UfixedBase(i::Integer,_) = new(i) # for setting by raw representation
UfixedBase(x) = convert(UfixedBase{T,f}, x)
end

typealias Ufixed8 UfixedBase{Uint8,8}
typealias Ufixed10 UfixedBase{Uint16,10}
typealias Ufixed12 UfixedBase{Uint16,12}
typealias Ufixed14 UfixedBase{Uint16,14}
typealias Ufixed16 UfixedBase{Uint16,16}

const UF = (Ufixed8, Ufixed10, Ufixed12, Ufixed14, Ufixed16)

rawtype{T,f}(::Type{UfixedBase{T,f}}) = T
nbitsfrac{T,f}(::Type{UfixedBase{T,f}}) = f

# The next lines mimic the floating-point literal syntax "3.2f0"
immutable UfixedConstructor{T,f} end
*{T,f}(n::Integer, ::UfixedConstructor{T,f}) = UfixedBase{T,f}(n,0)
const uf8 = UfixedConstructor{Uint8,8}()
const uf10 = UfixedConstructor{Uint16,10}()
const uf12 = UfixedConstructor{Uint16,12}()
const uf14 = UfixedConstructor{Uint16,14}()
const uf16 = UfixedConstructor{Uint16,16}()

zero{T,f}(::Type{UfixedBase{T,f}}) = UfixedBase{T,f}(zero(T),0)
for T in UF
f = nbitsfrac(T)
@eval begin
one(::Type{$T}) = UfixedBase{$(rawtype(T)),$f}($(2^f-1),0)
end
end
zero(x::Ufixed) = zero(typeof(x))
one(x::Ufixed) = one(typeof(x))
rawone(v) = reinterpret(one(v))

# Conversions
convert{T<:Ufixed}(::Type{T}, x::Real) = T(iround(rawtype(T), rawone(T)*x),0)

convert(::Type{BigFloat}, x::Ufixed) = reinterpret(x)*(1/BigFloat(rawone(x)))
convert{T<:FloatingPoint}(::Type{T}, x::Ufixed) = reinterpret(x)*(1/convert(T, rawone(x)))
convert(::Type{Bool}, x::Ufixed) = x == zero(x) ? false : true
convert{T<:Integer}(::Type{T}, x::Ufixed) = convert(T, x*(1/one(T)))
convert{Ti<:Integer}(::Type{Rational{Ti}}, x::Ufixed) = convert(Ti, reinterpret(x))//convert(Ti, rawone(x))
convert(::Type{Rational}, x::Ufixed) = reinterpret(x)//rawone(x)

# Traits
typemin{T<:Ufixed}(::Type{T}) = zero(T)
typemax{T<:Ufixed}(::Type{T}) = T(typemax(rawtype(T)),0)
realmin{T<:Ufixed}(::Type{T}) = typemin(T)
realmax{T<:Ufixed}(::Type{T}) = typemax(T)
eps{T<:Ufixed}(::Type{T}) = T(one(rawtype(T)),0)
eps{T<:Ufixed}(::T) = eps(T)
sizeof{T<:Ufixed}(::Type{T}) = sizeof(rawtype(T))

# Arithmetic
# Ufixed types are closed under addition and subtraction
+{T,f}(x::UfixedBase{T,f}, y::UfixedBase{T,f}) = UfixedBase{T,f}(convert(T, reinterpret(x)+reinterpret(y)),0)
-{T,f}(x::UfixedBase{T,f}, y::UfixedBase{T,f}) = UfixedBase{T,f}(convert(T, reinterpret(x)-reinterpret(y)),0)
*{T,f}(x::UfixedBase{T,f}, y::UfixedBase{T,f}) = float32(x)*float32(y)
/(x::Ufixed, y::Ufixed) = float32(x)/float32(y)

# Comparisons
< {T<:Ufixed}(x::T, y::T) = reinterpret(x) < reinterpret(y)
<={T<:Ufixed}(x::T, y::T) = reinterpret(x) < reinterpret(y)

# Functions
trunc{T<:Ufixed}(x::T) = T(div(reinterpret(x), rawone(T))*rawone(T),0)
floor{T<:Ufixed}(x::T) = trunc(x)
for T in UF
f = nbitsfrac(T)
R = rawtype(T)
roundmask = convert(R, 1<<(f-1))
k = 8*sizeof(R)-f
ceilmask = (typemax(R)<<k)>>k
@eval begin
round(x::$T) = (y = trunc(x); return reinterpret(x-y)&$roundmask>0 ? y+one($T) : y)
ceil(x::$T) = (y = trunc(x); return reinterpret(x-y)&$ceilmask >0 ? y+one($T) : y)
end
end

itrunc{T<:Integer}(::Type{T}, x::Ufixed) = convert(T, div(reinterpret(x), rawone(x)))
iround{T<:Integer}(::Type{T}, x::Ufixed) = iround(T, reinterpret(x)/rawone(x))
ifloor{T<:Integer}(::Type{T}, x::Ufixed) = itrunc(T, x)
iceil{T<:Integer}(::Type{T}, x::Ufixed) = iceil(T, reinterpret(x)/rawone(x))
itrunc(x::Ufixed) = itrunc(Int, x)
iround(x::Ufixed) = iround(Int, x)
ifloor(x::Ufixed) = ifloor(Int, x)
iceil(x::Ufixed) = iceil(Int, x)

bswap{f}(x::UfixedBase{Uint8,f}) = x
bswap(x::Ufixed) = typeof(x)(bswap(reinterpret(x)),0)

for f in (:div, :fld, :rem, :mod, :mod1, :rem1, :fld1, :min, :max)
@eval begin
$f{T<:Ufixed}(x::T, y::T) = T($f(reinterpret(x),reinterpret(y)),0)
end
end
function minmax{T<:Ufixed}(x::T, y::T)
a, b = minmax(reinterpret(x), reinterpret(y))
T(a,0), T(b,0)
end

# Iteration
# The main subtlety here is that iterating over 0x00uf8:0xffuf8 will wrap around
# unless we iterate using a wider type
if VERSION < v"0.3-"
start{T<:Ufixed}(r::Range{T}) = convert(typeof(reinterpret(r.start)+reinterpret(r.step)), reinterpret(r.start))
next{T<:Ufixed}(r::Range{T}, i::Integer) = (T(i,0), i+reinterpret(r.step))
done{T<:Ufixed}(r::Range{T}, i::Integer) = isempty(r) || (i > r.len)
else
start{T<:Ufixed}(r::StepRange{T}) = convert(typeof(reinterpret(r.start)+reinterpret(r.step)), reinterpret(r.start))
next{T<:Ufixed}(r::StepRange{T}, i::Integer) = (T(i,0), i+reinterpret(r.step))
done{T<:Ufixed}(r::StepRange{T}, i::Integer) = isempty(r) || (i > reinterpret(r.stop))
end

# Promotions
for T in UF
@eval begin
promote_rule(::Type{$T}, ::Type{Float32}) = Float32
promote_rule(::Type{$T}, ::Type{Float64}) = Float64
promote_rule{TR<:Rational}(::Type{$T}, ::Type{TR}) = TR
end
for Ti in (Int8, Uint8, Int16, Uint16, Int32, Uint32, Int64, Uint64)
Tp = eps(float32(typemax(Ti))) > eps(T) ? Float64 : Float32
@eval begin
promote_rule(::Type{$T}, ::Type{$Ti}) = $Tp
end
end
end

# Show
function show(io::IO, x::Ufixed)
print(io, "Ufixed", nbitsfrac(typeof(x)))
print(io, "(")
showcompact(io, x)
print(io, ")")
end
showcompact(io::IO, x::Ufixed) = show(io, round(convert(Float64,x), iceil(nbitsfrac(typeof(x))/_log2_10)))
File renamed without changes.
2 changes: 2 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
include("fixed32.jl")
include("ufixed.jl")
Loading