Skip to content

added support for an arbitrary number of fraction bits #44

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 2 commits into from
Aug 24, 2016
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
38 changes: 23 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,29 @@ 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.

# Type hierarchy
This library defines an abstract type `FixedPoint{T <: Integer, f}` as a subtype of `Real`. The parameter `T` is the underlying representation and `f` is the number of fraction bits.

For signed integers, there is a fixed-point type `Fixed{T, f}` and for unsigned integers, there is the `UFixed{T, f}` 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)`, `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`.
This library defines an abstract type `FixedPoint{T <: Integer, f}` as a
subtype of `Real`. The parameter `T` is the underlying representation and `f`
is the number of fraction bits.

For signed integers, there is a fixed-point type `Fixed{T, f}` and for unsigned
integers, there is the `UFixed{T, f}` 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 (aliased to
UFixed{UInt8,8}) is represented internally by a `UInt8`, and makes `0x00`
equivalent to `0.0` and `0xff` to `1.0`. The type aliases `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)`, `ufixed12(1.3)` (a
convenience function), `UFixed{UInt16,12}(1.3)`, or the literal syntax
`0x14ccuf12`. The latter syntax means to construct a `UFixed12` (it ends in
`uf12`) from the `UInt16` value `0x14cc`.

More generally, an arbitrary number of bits from any of the standard unsigned
integer widths can be used for the fractional part. For example:
`UFixed{UInt32,16}`, `UFixed{UInt64,3}`, `UFixed{UInt128,7}`.

There currently is no literal syntax for signed `Fixed` numbers.

Expand Down
11 changes: 11 additions & 0 deletions src/FixedPointNumbers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,4 +94,15 @@ scaledual{T<:FixedPoint}(Tdual::Type, x::Union{T,AbstractArray{T}}) =
scaledual{Tdual<:Number, T<:FixedPoint}(b::Tdual, x::Union{T,AbstractArray{T}}) =
convert(Tdual, b/one(T)), reinterpret(rawtype(T), x)

# printing
function show{T,f}(io::IO, x::FixedPoint{T,f})
shorttype = typeof(x)<:UFixed ? "UFixed" : "Fixed"
print(io, shorttype, "{", T, ",", f, "}")
print(io, "(")
showcompact(io, x)
print(io, ")")
end
const _log2_10 = 3.321928094887362
showcompact{T,f}(io::IO, x::FixedPoint{T,f}) = show(io, round(convert(Float64,x), ceil(Int,f/_log2_10)))

end # module
10 changes: 0 additions & 10 deletions src/fixed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,3 @@ promote_rule{T,f,TR}(::Type{Fixed{T,f}}, ::Type{Rational{TR}}) = Rational{TR}

# TODO: Document and check that it still does the right thing.
decompose{T,f}(x::Fixed{T,f}) = x.i, -f, 1

# printing
function show(io::IO, x::Fixed)
print(io, typeof(x))
print(io, "(")
showcompact(io, x)
print(io, ")")
end
const _log2_10 = 3.321928094887362
showcompact{T,f}(io::IO, x::Fixed{T,f}) = show(io, round(convert(Float64,x), ceil(Int,f/_log2_10)))
69 changes: 22 additions & 47 deletions src/ufixed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,7 @@ typealias UFixed16 UFixed{UInt16,16}

const UF = (UFixed8, UFixed10, UFixed12, UFixed14, UFixed16)

for (uf) in UF
T = rawtype(uf)
f = nbitsfrac(uf)
@eval reinterpret(::Type{UFixed{$T,$f}}, x::$T) = UFixed{$T,$f}(x, 0)
end
reinterpret{T<:Unsigned, f}(::Type{UFixed{T,f}}, x::T) = UFixed{T,f}(x, 0)

# The next lines mimic the floating-point literal syntax "3.2f0"
immutable UFixedConstructor{T,f} end
Expand All @@ -35,13 +31,9 @@ const uf14 = UFixedConstructor{UInt16,14}()
const uf16 = UFixedConstructor{UInt16,16}()

zero{T,f}(::Type{UFixed{T,f}}) = UFixed{T,f}(zero(T),0)
for uf in UF
TT = rawtype(uf)
f = nbitsfrac(uf)
T = UFixed{TT,f}
@eval begin
one(::Type{$T}) = $T($(2^f-1),0)
end
@generated function one{T<:UFixed}(::Type{T})
f = 2^nbitsfrac(T)-1
:( T($f,0) )
end
zero(x::UFixed) = zero(typeof(x))
one(x::UFixed) = one(typeof(x))
Expand Down Expand Up @@ -98,26 +90,24 @@ abs(x::UFixed) = x
# 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 convert(rawtype($T), reinterpret(x)-reinterpret(y))&$roundmask>0 ? $T(y+one($T)) : y)
ceil(x::$T) = (y = trunc(x); return convert(rawtype($T), reinterpret(x)-reinterpret(y))&$ceilmask >0 ? $T(y+one($T)) : y)
end
function round{T,f}(x::UFixed{T,f})
mask = convert(T, 1<<(f-1))
y = trunc(x)
return convert(T, reinterpret(x)-reinterpret(y)) & mask>0 ?
UFixed{T,f}(y+one(UFixed{T,f})) : y
end
function ceil{T,f}(x::UFixed{T,f})
k = 8*sizeof(T)-f
mask = (typemax(T)<<k)>>k
y = trunc(x)
return convert(T, reinterpret(x)-reinterpret(y)) & (mask)>0 ?
UFixed{T,f}(y+one(UFixed{T,f})) : y
end

trunc{T<:Integer}(::Type{T}, x::UFixed) = convert(T, div(reinterpret(x), rawone(x)))
round{T<:Integer}(::Type{T}, x::UFixed) = round(T, reinterpret(x)/rawone(x))
floor{T<:Integer}(::Type{T}, x::UFixed) = trunc(T, x)
ceil{T<:Integer}(::Type{T}, x::UFixed) = ceil(T, reinterpret(x)/rawone(x))
trunc(x::UFixed) = trunc(Int, x)
round(x::UFixed) = round(Int, x)
floor(x::UFixed) = floor(Int, x)
ceil(x::UFixed) = ceil(Int, x)

isfinite(x::UFixed) = true
isnan(x::UFixed) = false
Expand Down Expand Up @@ -155,25 +145,10 @@ function decompose(x::UFixed)
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(convert(Float32, typemax(Ti))) > eps(T) ? Float64 : Float32
@eval begin
promote_rule(::Type{$T}, ::Type{$Ti}) = $Tp
end
end
end

# Show
function show{T,f}(io::IO, x::UFixed{T,f})
print(io, "UFixed", f)
print(io, "(")
showcompact(io, x)
print(io, ")")
promote_rule{T<:UFixed}(::Type{T}, ::Type{Float32}) = Float32
promote_rule{T<:UFixed}(::Type{T}, ::Type{Float64}) = Float64
promote_rule{T<:UFixed, R<:Rational}(::Type{T}, ::Type{R}) = R
@generated function promote_rule{T<:UFixed, Ti<:Union{Signed,Unsigned}}(::Type{T}, ::Type{Ti})
Tp = eps(convert(Float32, typemax(Ti))) > eps(T) ? Float64 : Float32
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one definitely needs to be @generated.

:( $Tp )
end
showcompact{T,f}(io::IO, x::UFixed{T,f}) = show(io, round(convert(Float64,x), ceil(Int,f/_log2_10)))
8 changes: 8 additions & 0 deletions test/fixed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,11 @@ for T in (Float16, Float32, Float64, BigFloat)
y = convert(T, x)
@test isa(y, T)
end

# Show
x = Fixed{Int32,3}(0.25)
iob = IOBuffer()
show(iob, x)
str = takebuf_string(iob)
@test startswith(str, "Fixed{Int32,3}(")
@test eval(parse(str)) == x
26 changes: 19 additions & 7 deletions test/ufixed.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,16 @@ using FixedPointNumbers, Base.Test
@test ufixed14(1.0) == 0x3fffuf14
@test ufixed12([2]) == UFixed12[0x1ffeuf12]

for T in FixedPointNumbers.UF
UF2 = (UFixed{UInt32,16}, UFixed{UInt64,3}, UFixed{UInt128,7})

for T in (FixedPointNumbers.UF..., UF2...)
@test zero(T) == 0
@test one(T) == 1
@test one(T) * one(T) == one(T)
@test typemin(T) == 0
@test realmin(T) == 0
@test eps(zero(T)) == eps(typemax(T))
@test sizeof(T) == 1 + (T != UFixed8)
@test sizeof(T) == sizeof(FixedPointNumbers.rawtype(T))
end
@test typemax(UFixed8) == 1
@test typemax(UFixed10) == typemax(UInt16)//(2^10-1)
Expand All @@ -34,6 +36,9 @@ end
@test typemax(UFixed10) == typemax(UInt16) // (2^10-1)
@test typemax(UFixed12) == typemax(UInt16) // (2^12-1)
@test typemax(UFixed14) == typemax(UInt16) // (2^14-1)
@test typemax(UFixed{UInt32,16}) == typemax(UInt32) // (2^16-1)
@test typemax(UFixed{UInt64,3}) == typemax(UInt64) // (2^3-1)
@test typemax(UFixed{UInt128,7}) == typemax(UInt128) // (2^7-1)

x = UFixed8(0.5)
@test isfinite(x) == true
Expand All @@ -45,13 +50,16 @@ x = UFixed8(0.5)
@test convert(UFixed12, 1.1/typemax(UInt16)*16) == eps(UFixed12)
@test convert(UFixed14, 1.1/typemax(UInt16)*4) == eps(UFixed14)
@test convert(UFixed16, 1.1/typemax(UInt16)) == eps(UFixed16)
@test convert(UFixed{UInt32,16}, 1.1/typemax(UInt32)*2^16) == eps(UFixed{UInt32,16})
@test convert(UFixed{UInt64,3}, 1.1/typemax(UInt64)*2^61) == eps(UFixed{UInt64,3})
@test convert(UFixed{UInt128,7}, 1.1/typemax(UInt128)*UInt128(2)^121) == eps(UFixed{UInt128,7})

@test convert(UFixed8, 1.1f0/typemax(UInt8)) == eps(UFixed8)

@test convert(Float64, eps(UFixed8)) == 1/typemax(UInt8)
@test convert(Float32, eps(UFixed8)) == 1.0f0/typemax(UInt8)
@test convert(BigFloat, eps(UFixed8)) == BigFloat(1)/typemax(UInt8)
for T in FixedPointNumbers.UF
for T in (FixedPointNumbers.UF..., UF2...)
@test convert(Bool, zero(T)) == false
@test convert(Bool, one(T)) == true
@test convert(Bool, convert(T, 0.2)) == true
Expand All @@ -64,7 +72,7 @@ x = UFixed8(0b01010001, 0)
@test ~x == UFixed8(0b10101110, 0)
@test -x == 0xafuf8

for T in FixedPointNumbers.UF
for T in (FixedPointNumbers.UF..., UF2...)
x = T(0x10,0)
y = T(0x25,0)
fx = convert(Float32, x)
Expand All @@ -89,15 +97,19 @@ function testtrunc{T}(inc::T)
incf = convert(Float64, inc)
tm = reinterpret(typemax(T))/reinterpret(one(T))
x = zero(T)
for i = 0:reinterpret(typemax(T))-1
for i = 0 : min(1e6, reinterpret(typemax(T))-1)
xf = incf*i
try
@test typeof(trunc(x)) == T
@test trunc(x) == trunc(xf)
@test typeof(round(x)) == T
@test round(x) == round(xf)
cxf = ceil(xf)
if cxf < tm
@test typeof(ceil(x)) == T
@test ceil(x) == ceil(xf)
end
@test typeof(floor(x)) == T
@test floor(x) == floor(xf)
@test trunc(Int,x) == trunc(Int,xf)
@test round(Int,x) == round(Int,xf)
Expand All @@ -113,7 +125,7 @@ function testtrunc{T}(inc::T)
end
end

for T in FixedPointNumbers.UF
for T in (FixedPointNumbers.UF..., UF2...)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably shouldn't test UF2 here since testtrunc tests every value under the sun and increases the testing times massively.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note the min(1e6, ...

testtrunc(eps(T))
end

Expand All @@ -122,7 +134,7 @@ x = 0xaauf8
iob = IOBuffer()
show(iob, x)
str = takebuf_string(iob)
@test startswith(str, "UFixed8(")
@test startswith(str, "UFixed{UInt8,8}(")
@test eval(parse(str)) == x

# scaledual
Expand Down