From cd7558c1595f9d11aeff1bfaadbc44483a5cbd27 Mon Sep 17 00:00:00 2001 From: Ben Arthur Date: Fri, 12 Aug 2016 16:58:18 -0400 Subject: [PATCH 1/2] added support for an arbitrary number of fraction bits --- README.md | 38 +++++++++++++---------- src/FixedPointNumbers.jl | 11 +++++++ src/fixed.jl | 10 ------- src/ufixed.jl | 65 ++++++++++++++-------------------------- test/fixed.jl | 8 +++++ test/ufixed.jl | 22 +++++++++----- 6 files changed, 79 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index e1df5857..08304b10 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 9e1d8424..6e226841 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -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 diff --git a/src/fixed.jl b/src/fixed.jl index f76970c8..e4b63cdd 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -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))) diff --git a/src/ufixed.jl b/src/ufixed.jl index 4cc369d2..379c8dc7 100644 --- a/src/ufixed.jl +++ b/src/ufixed.jl @@ -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 @@ -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)) @@ -98,16 +90,18 @@ 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 - @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 + 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))) @@ -155,25 +149,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 + :( $Tp ) end -showcompact{T,f}(io::IO, x::UFixed{T,f}) = show(io, round(convert(Float64,x), ceil(Int,f/_log2_10))) diff --git a/test/fixed.jl b/test/fixed.jl index e541c201..8f948d35 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -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 diff --git a/test/ufixed.jl b/test/ufixed.jl index 839e1542..6853cafc 100644 --- a/test/ufixed.jl +++ b/test/ufixed.jl @@ -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) @@ -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 @@ -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 @@ -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) @@ -89,7 +97,7 @@ 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 trunc(x) == trunc(xf) @@ -113,7 +121,7 @@ function testtrunc{T}(inc::T) end end -for T in FixedPointNumbers.UF +for T in (FixedPointNumbers.UF..., UF2...) testtrunc(eps(T)) end @@ -122,7 +130,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 From 994d96e8040dbe6cccbd9fd7b779d2685f2d6960 Mon Sep 17 00:00:00 2001 From: Ben Arthur Date: Sat, 13 Aug 2016 10:17:12 -0400 Subject: [PATCH 2/2] round and ceil now return FixedPoint --- src/ufixed.jl | 4 ---- test/ufixed.jl | 4 ++++ 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ufixed.jl b/src/ufixed.jl index 379c8dc7..e1d9cd20 100644 --- a/src/ufixed.jl +++ b/src/ufixed.jl @@ -108,10 +108,6 @@ trunc{T<:Integer}(::Type{T}, x::UFixed) = convert(T, div(reinterpret(x), rawone( 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 diff --git a/test/ufixed.jl b/test/ufixed.jl index 6853cafc..135f3c8c 100644 --- a/test/ufixed.jl +++ b/test/ufixed.jl @@ -100,12 +100,16 @@ function testtrunc{T}(inc::T) 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)