diff --git a/.travis.yml b/.travis.yml index e0ea53de..03342a40 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,11 @@ os: - linux - osx julia: - - 0.3 - 0.4 - nightly notifications: email: false +script: + - if [[ -a .git/shallow ]]; then git fetch --unshallow; fi + - julia -e 'Pkg.clone(pwd()); Pkg.build("FixedPointNumbers")' + - julia -e 'cd(Pkg.dir("FixedPointNumbers", "test")); include("runtests.jl")' diff --git a/README.md b/README.md index 25fdad67..e1df5857 100644 --- a/README.md +++ b/README.md @@ -14,28 +14,23 @@ 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) +# 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 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`. +For signed integers, there is a fixed-point type `Fixed{T, f}` and for unsigned integers, there is the `UFixed{T, f}` type. -To use it, convert numbers to a `Fixed32` type, or call `Fixed32(x)`, which will default -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 +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` +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 +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`. +There currently is no literal syntax for signed `Fixed` numbers. + [wikipedia]: http://en.wikipedia.org/wiki/Fixed-point_arithmetic diff --git a/REQUIRE b/REQUIRE index 36e1ded9..dc43abca 100644 --- a/REQUIRE +++ b/REQUIRE @@ -1,2 +1,2 @@ -julia 0.3 +julia 0.4- Compat 0.2.2 diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 0c75d4bf..d771c147 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -11,21 +11,20 @@ import Base: ==, <, <=, -, +, *, /, ~, trunc, round, floor, ceil, bswap, div, fld, rem, mod, mod1, rem1, fld1, min, max, start, next, done - -abstract FixedPoint <: Real -abstract Fixed <: FixedPoint -abstract Ufixed <: FixedPoint # unsigned variant +# T => BaseType +# f => Number of Bytes reserved for fractional part +abstract FixedPoint{T <: Integer, f} <: Real export FixedPoint, Fixed, - Ufixed, - Fixed32, - Ufixed8, - Ufixed10, - Ufixed12, - Ufixed14, - Ufixed16, + UFixed, + Fixed16, + UFixed8, + UFixed10, + UFixed12, + UFixed14, + UFixed16, # constructors ufixed8, ufixed10, @@ -43,10 +42,26 @@ export reinterpret(x::FixedPoint) = x.i -include("fixed32.jl") +# comparison +=={T <: FixedPoint}(x::T, y::T) = x.i == y.i + <{T <: FixedPoint}(x::T, y::T) = x.i < y.i +<={T <: FixedPoint}(x::T, y::T) = x.i <= y.i + +# predicates +isinteger{T,f}(x::FixedPoint{T,f}) = (x.i&(1<>f,0) +# with rounding up: +*{T,f}(x::Fixed{T,f}, y::Fixed{T,f}) = Fixed{T,f}((Base.widemul(x.i,y.i) + (convert(widen(T), 1) << (f-1) ))>>f,0) + +/{T,f}(x::Fixed{T,f}, y::Fixed{T,f}) = Fixed{T,f}(div(convert(widen(T), x.i) << f, y.i), 0) + + +# # conversions and promotions +convert{T,f}(::Type{Fixed{T,f}}, x::Integer) = Fixed{T,f}(convert(T,x)<>f) + convert(BigFloat,x.i&(1<>f) + convert(TF,x.i&(1<>f) +end + +convert{TR<:Rational,T,f}(::Type{TR}, x::Fixed{T,f}) = + convert(TR, x.i>>f + (x.i&(1<>f,0) -# with rounding up: -*{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}((Base.widemul(x.i,y.i)+(convert(Int64, 1)<<(f-1)))>>f,0) - -/{f}(x::Fixed32{f}, y::Fixed32{f}) = Fixed32{f}(div(convert(Int64, x.i)<>f) + convert(BigFloat,x.i&(1<>f) + convert(T,x.i&(1<>f -end - -convert{T<:Rational, f}(::Type{T}, x::Fixed32{f}) = - convert(T, x.i>>f + (x.i&(1<>f,0) +/{T,f}(x::UFixed{T,f}, y::UFixed{T,f}) = UFixed{T,f}(div(convert(widen(T), x.i)< r.len) + 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)) + 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 -function decompose(x::Ufixed) +function decompose(x::UFixed) g = gcd(reinterpret(x), rawone(x)) div(reinterpret(x),g), 0, div(rawone(x),g) end @@ -169,10 +167,10 @@ for T in UF end # Show -function show{T,f}(io::IO, x::UfixedBase{T,f}) - print(io, "Ufixed", f) +function show{T,f}(io::IO, x::UFixed{T,f}) + print(io, "UFixed", f) print(io, "(") showcompact(io, x) print(io, ")") end -showcompact{T,f}(io::IO, x::UfixedBase{T,f}) = show(io, round(convert(Float64,x), ceil(Int,f/_log2_10))) +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 new file mode 100644 index 00000000..5cb4f124 --- /dev/null +++ b/test/fixed.jl @@ -0,0 +1,55 @@ +using Base.Test +using FixedPointNumbers + +function test_fixed{T}(::Type{T}, f) + values = [-10:0.01:10; -180:.01:-160; 160:.01:180] + tol = 2.0^-f + for x in values + # Ignore values outside the representable range + # typemin <, otherwise for -(-0.5) > typemax + if !(typemin(T) < x <= typemax(T)) + continue + end + isinteger(x) && @show x + fx = convert(T,x) + @test convert(T,convert(Float64, fx)) == fx + @test convert(T,convert(Float64, -fx)) == -fx + @test convert(Float64, -fx) == -convert(Float64, fx) + + fxf = convert(Float64, fx) + + rx = convert(Rational{BigInt},fx) + @assert isequal(fx,rx) == isequal(hash(fx),hash(rx)) + + for y in values + if !(typemin(T) < y <= typemax(T)) + continue + end + + fy = convert(T,y) + fyf = convert(Float64, fy) + + @test fx==fy || x!=y + @test fx=y + @test fx<=fy || x>y + + for fun in [+, -, *, /] + # Make sure that the result is representable + if !(typemin(T) <= fun(fxf, fyf) <= typemax(T)) + continue + elseif (fun == /) && fy != 0 + @test abs(fun(fx, fy) - convert(T, fun(fxf, fyf))) <= tol + @test abs(convert(Float64, fun(fx, fy)) - fun(fxf, fyf)) <= tol + end + end + + @test isequal(fx,fy) == isequal(hash(fx),hash(fy)) + end + end +end + +for (TI, f) in [(Int8, 8), (Int16, 8), (Int16, 10), (Int32, 16)] + T = Fixed{TI,f} + println(" Testing $T") + test_fixed(T, f) +end diff --git a/test/fixed32.jl b/test/fixed32.jl deleted file mode 100644 index b90fa624..00000000 --- a/test/fixed32.jl +++ /dev/null @@ -1,51 +0,0 @@ -using Base.Test -using FixedPointNumbers - -function test_fixed{T}(::Type{T}, f) - values = [-10:0.01:10; -180:.01:-160; 160:.01:180] - tol = 2.0^-f - - for x in values - isinteger(x) && @show x - fx = convert(T,x) - @test convert(T,convert(Float64, fx)) == fx - @test convert(T,convert(Float64, -fx)) == -fx - @test convert(Float64, -fx) == -convert(Float64, fx) - - fxf = convert(Float64, fx) - - rx = convert(Rational{BigInt},fx) - @assert isequal(fx,rx) == isequal(hash(fx),hash(rx)) - - for y in values - fy = convert(T,y) - fyf = convert(Float64, fy) - - @assert fx==fy || x!=y - @assert fx=y - @assert fx<=fy || x>y - - @assert abs((fx+fy)-convert(T,fxf+fyf)) <= tol - @assert abs((fx-fy)-convert(T,fxf-fyf)) <= tol - @assert abs((fx*fy)-convert(T,fxf*fyf)) <= tol - if fy != 0 - @assert abs((fx/fy)-convert(T,fxf/fyf)) <= tol - end - - @assert abs(convert(Float64, fx+fy)-(fxf+fyf)) <= tol - @assert abs(convert(Float64, fx-fy)-(fxf-fyf)) <= tol - @assert abs(convert(Float64, fx*fy)-(fxf*fyf)) <= tol - if fy != 0 - @assert abs(convert(Float64, fx/fy)-(fxf/fyf)) <= tol - end - - @assert isequal(fx,fy) == isequal(hash(fx),hash(fy)) - end - end -end - -for f in [8, 10, 16] - T = Fixed32{f} - println(" Testing $T") - test_fixed(T, f) -end diff --git a/test/runtests.jl b/test/runtests.jl index ea09c13b..f45cc3b4 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,4 +1,4 @@ -for f in ["ufixed.jl", "fixed32.jl"] +for f in ["fixed.jl", "ufixed.jl"] println("Testing $f") include(f) end diff --git a/test/ufixed.jl b/test/ufixed.jl index 9293175f..d7b46eda 100644 --- a/test/ufixed.jl +++ b/test/ufixed.jl @@ -6,16 +6,16 @@ using FixedPointNumbers, Compat, Base.Test @test reinterpret(0xa2uf14) == 0xa2 @test reinterpret(0xa2uf16) == 0xa2 -@test reinterpret(Ufixed8, 0xa2) == 0xa2uf8 -@test reinterpret(Ufixed10, 0x1fa2) == 0x1fa2uf10 -@test reinterpret(Ufixed12, 0x1fa2) == 0x1fa2uf12 -@test reinterpret(Ufixed14, 0x1fa2) == 0x1fa2uf14 -@test reinterpret(Ufixed16, 0x1fa2) == 0x1fa2uf16 +@test reinterpret(UFixed8, 0xa2) == 0xa2uf8 +@test reinterpret(UFixed10, 0x1fa2) == 0x1fa2uf10 +@test reinterpret(UFixed12, 0x1fa2) == 0x1fa2uf12 +@test reinterpret(UFixed14, 0x1fa2) == 0x1fa2uf14 +@test reinterpret(UFixed16, 0x1fa2) == 0x1fa2uf16 @test ufixed8(1.0) == 0xffuf8 @test ufixed8(0.5) == 0x80uf8 @test ufixed14(1.0) == 0x3fffuf14 -@test ufixed12([2]) == Ufixed12[0x1ffeuf12] +@test ufixed12([2]) == UFixed12[0x1ffeuf12] for T in FixedPointNumbers.UF @test zero(T) == 0 @@ -23,33 +23,33 @@ for T in FixedPointNumbers.UF @test typemin(T) == 0 @test realmin(T) == 0 @test eps(zero(T)) == eps(typemax(T)) - @test sizeof(T) == 1 + (T != Ufixed8) + @test sizeof(T) == 1 + (T != UFixed8) end -@test typemax(Ufixed8) == 1 -@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(Ufixed16) == 1 -@test typemax(Ufixed10) == typemax(UInt16) // (2^10-1) -@test typemax(Ufixed12) == typemax(UInt16) // (2^12-1) -@test typemax(Ufixed14) == typemax(UInt16) // (2^14-1) - -x = Ufixed8(0.5) +@test typemax(UFixed8) == 1 +@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(UFixed16) == 1 +@test typemax(UFixed10) == typemax(UInt16) // (2^10-1) +@test typemax(UFixed12) == typemax(UInt16) // (2^12-1) +@test typemax(UFixed14) == typemax(UInt16) // (2^14-1) + +x = UFixed8(0.5) @test isfinite(x) == true @test isnan(x) == false @test isinf(x) == false -@test convert(Ufixed8, 1.1/typemax(UInt8)) == eps(Ufixed8) -@test convert(Ufixed10, 1.1/typemax(UInt16)*64) == eps(Ufixed10) -@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(UFixed8, 1.1/typemax(UInt8)) == eps(UFixed8) +@test convert(UFixed10, 1.1/typemax(UInt16)*64) == eps(UFixed10) +@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(Ufixed8, 1.1f0/typemax(UInt8)) == eps(Ufixed8) +@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) +@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 @test convert(Bool, zero(T)) == false @test convert(Bool, one(T)) == true @@ -57,24 +57,34 @@ for T in FixedPointNumbers.UF @test convert(Int, one(T)) == 1 @test convert(Rational, one(T)) == 1 end -@test convert(Rational, convert(Ufixed8, 0.5)) == 0x80//0xff +@test convert(Rational, convert(UFixed8, 0.5)) == 0x80//0xff -x = Ufixed8(0b01010001, 0) -@test ~x == Ufixed8(0b10101110, 0) +x = UFixed8(0b01010001, 0) +@test ~x == UFixed8(0b10101110, 0) @test -x == 0xafuf8 for T in FixedPointNumbers.UF x = T(0x10,0) y = T(0x25,0) + fx = convert(Float32, x) + fy = convert(Float32, y) @test y > x @test y != x + @test typeof(x+y) == T + @test typeof((x+y)-y) == T + @test typeof(x*y) == T + @test typeof(x/y) == T @test_approx_eq(x+y, T(0x35,0)) - @test_approx_eq((x+y)-x, convert(Float32, y)) - @test_approx_eq((x-y)+y, convert(Float32, x)) - @test_approx_eq(x*y, convert(Float32, x)*convert(Float32, y)) - @test_approx_eq(x/y, convert(Float32, x)/convert(Float32, y)) - @test_approx_eq(x^2, convert(Float32, x)^2) - @test_approx_eq(x^2.1f0, convert(Float32, x)^2.1f0) + @test_approx_eq((x+y)-x, fy) + @test_approx_eq((x-y)+y, fx) + @test_approx_eq(x*y, convert(T, fx*fy)) + if T == UFixed14 + @test convert(T, fx/fy) - x/y == eps(T) + else + @test_approx_eq(x/y, convert(T, fx/fy)) + end + @test_approx_eq(x^2, convert(T, fx^2)) + @test_approx_eq(x^2.1f0, fx^2.1f0) @test_approx_eq(x^2.1, convert(Float64, x)^2.1) end @@ -115,7 +125,7 @@ x = 0xaauf8 iob = IOBuffer() show(iob, x) str = takebuf_string(iob) -@test startswith(str, "Ufixed8(") +@test startswith(str, "UFixed8(") @test eval(parse(str)) == x # scaledual @@ -130,7 +140,7 @@ end a = rand(UInt8, 10) rfloat = similar(a, Float32) rfixed = similar(rfloat) -af8 = reinterpret(Ufixed8, a) +af8 = reinterpret(UFixed8, a) b = 0.5 bd, eld = scaledual(b, af8[1])