diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 8bba0203..a416a686 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -109,4 +109,13 @@ 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))) +@noinline function throw_converterror{T<:FixedPoint}(::Type{T}, x) + n = 2^(8*sizeof(T)) + bitstring = sizeof(T) == 1 ? "an 8-bit" : "a $(8*sizeof(T))-bit" + io = IOBuffer() + showcompact(io, typemin(T)); Tmin = takebuf_string(io) + showcompact(io, typemax(T)); Tmax = takebuf_string(io) + throw(ArgumentError("$T is $bitstring type representing $n values from $Tmin to $Tmax; cannot represent $x")) +end + end # module diff --git a/src/ufixed.jl b/src/ufixed.jl index 613a0586..a50bf9a0 100644 --- a/src/ufixed.jl +++ b/src/ufixed.jl @@ -9,7 +9,9 @@ immutable UFixed{T<:Unsigned,f} <: FixedPoint{T,f} end rawtype{T,f}(::Type{UFixed{T,f}}) = T + rawtype(x::Number) = rawtype(typeof(x)) nbitsfrac{T,f}(::Type{UFixed{T,f}}) = f +nbitsfrac(x::Number) = nbitsfract(typeof(x)) typealias UFixed8 UFixed{UInt8,8} typealias UFixed10 UFixed{UInt16,10} @@ -31,9 +33,8 @@ const uf14 = UFixedConstructor{UInt16,14}() const uf16 = UFixedConstructor{UInt16,16}() zero{T,f}(::Type{UFixed{T,f}}) = UFixed{T,f}(zero(T),0) -@generated function one{T<:UFixed}(::Type{T}) - f = 2^nbitsfrac(T)-1 - :( T($f,0) ) +function one{T<:UFixed}(::Type{T}) + T(typemax(rawtype(T)) >> (8*sizeof(T)-nbitsfrac(T)), 0) end zero(x::UFixed) = zero(typeof(x)) one(x::UFixed) = one(typeof(x)) @@ -41,19 +42,31 @@ rawone(v) = reinterpret(one(v)) # Conversions convert{T<:UFixed}(::Type{T}, x::T) = x -convert{T1<:UFixed}(::Type{T1}, x::UFixed) = reinterpret(T1, round(rawtype(T1), (rawone(T1)/rawone(x))*reinterpret(x))) +function convert{T<:UFixed}(::Type{T}, x::UFixed) + y = round((rawone(T)/rawone(x))*reinterpret(x)) + (0 <= y) & (y <= typemax(rawtype(T))) || throw_converterror(T, x) + reinterpret(T, _unsafe_trunc(rawtype(T), y)) +end convert(::Type{UFixed16}, x::UFixed8) = reinterpret(UFixed16, convert(UInt16, 0x0101*reinterpret(x))) convert{U<:UFixed}(::Type{U}, x::Real) = _convert(U, rawtype(U), x) -_convert{U<:UFixed,T}(::Type{U}, ::Type{T}, x) = U(round(T, widen1(rawone(U))*x), 0) -_convert{U<:UFixed }(::Type{U}, ::Type{UInt128}, x) = U(round(UInt128, rawone(U)*x), 0) +function _convert{U<:UFixed,T}(::Type{U}, ::Type{T}, x) + y = round(widen1(rawone(U))*x) + (0 <= y) & (y <= typemax(T)) || throw_converterror(U, x) + U(_unsafe_trunc(T, y), 0) +end +function _convert{U<:UFixed}(::Type{U}, ::Type{UInt128}, x) + y = round(rawone(U)*x) # for UInt128, we can't widen + (0 <= y) & (y <= typemax(UInt128)) & (x <= Float64(typemax(U))) || throw_converterror(U, x) + U(_unsafe_trunc(UInt128, y), 0) +end rem{T<:UFixed}(x::T, ::Type{T}) = x -rem{T<:UFixed}(x::UFixed, ::Type{T}) = reinterpret(T, unsafe_trunc(rawtype(T), round((rawone(T)/rawone(x))*reinterpret(x)))) -rem{T<:UFixed}(x::Real, ::Type{T}) = reinterpret(T, unsafe_trunc(rawtype(T), round(rawone(T)*x))) +rem{T<:UFixed}(x::UFixed, ::Type{T}) = reinterpret(T, _unsafe_trunc(rawtype(T), round((rawone(T)/rawone(x))*reinterpret(x)))) +rem{T<:UFixed}(x::Real, ::Type{T}) = reinterpret(T, _unsafe_trunc(rawtype(T), round(rawone(T)*x))) convert(::Type{BigFloat}, x::UFixed) = reinterpret(x)*(1/BigFloat(rawone(x))) function convert{T<:AbstractFloat}(::Type{T}, x::UFixed) - y = reinterpret(x)*(1/convert(T, rawone(x))) + y = reinterpret(x)*(one(rawtype(x))/convert(T, rawone(x))) convert(T, y) # needed for types like Float16 which promote arithmetic to Float32 end convert(::Type{Bool}, x::UFixed) = x == zero(x) ? false : true @@ -68,17 +81,21 @@ sizeof{T<:UFixed}(::Type{T}) = sizeof(rawtype(T)) abs(x::UFixed) = x # Arithmetic +@generated function floattype{U<:UFixed}(::Type{U}) + eps(U) < eps(Float32) ? :(Float64) : :(Float32) +end + (-){T<:UFixed}(x::T) = T(-reinterpret(x), 0) (~){T<:UFixed}(x::T) = T(~reinterpret(x), 0) +{T,f}(x::UFixed{T,f}, y::UFixed{T,f}) = UFixed{T,f}(convert(T, x.i+y.i),0) -{T,f}(x::UFixed{T,f}, y::UFixed{T,f}) = UFixed{T,f}(convert(T, x.i-y.i),0) -*{T<:UFixed}(x::T, y::T) = convert(T,convert(Float32, x)*convert(Float32, y)) -/{T<:UFixed}(x::T, y::T) = convert(T,convert(Float32, x)/convert(Float32, y)) +*{T<:UFixed}(x::T, y::T) = convert(T,convert(floattype(T), x)*convert(floattype(T), y)) +/{T<:UFixed}(x::T, y::T) = convert(T,convert(floattype(T), x)/convert(floattype(T), y)) # Comparisons <{T<:UFixed}(x::T, y::T) = reinterpret(x) < reinterpret(y) -<={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) @@ -150,3 +167,18 @@ promote_rule{T<:UFixed, R<:Rational}(::Type{T}, ::Type{R}) = R Tp = eps(convert(Float32, typemax(Ti))) > eps(T) ? Float64 : Float32 :( $Tp ) end +@generated function promote_rule{T1,T2,f1,f2}(::Type{UFixed{T1,f1}}, ::Type{UFixed{T2,f2}}) + f = max(f1, f2) # ensure we have enough precision + T = promote_type(T1, T2) + # make sure we have enough integer bits + i1, i2 = 8*sizeof(T1)-f1, 8*sizeof(T2)-f2 # number of integer bits for each + i = 8*sizeof(T)-f + while i < max(i1, i2) + T = widen1(T) + i = 8*sizeof(T)-f + end + :(UFixed{$T,$f}) +end + +_unsafe_trunc{T}(::Type{T}, x::Integer) = x % T +_unsafe_trunc{T}(::Type{T}, x) = unsafe_trunc(T, x) diff --git a/test/ufixed.jl b/test/ufixed.jl index fa8b5e9f..939cd49e 100644 --- a/test/ufixed.jl +++ b/test/ufixed.jl @@ -21,7 +21,7 @@ v = @compat UFixed12.([2]) @test v == UFixed12[0x1ffeuf12] @test isa(v, Vector{UFixed12}) -UF2 = (UFixed{UInt32,16}, UFixed{UInt64,3}, UFixed{UInt128,7}) +UF2 = (UFixed{UInt32,16}, UFixed{UInt64,3}, UFixed{UInt64,51}, UFixed{UInt128,7}, UFixed{UInt128,51}) for T in (FixedPointNumbers.UF..., UF2...) @test zero(T) == 0 @@ -43,15 +43,19 @@ end @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) - -@test_throws InexactError UFixed8(2) -@test_throws InexactError UFixed8(255) -@test_throws InexactError UFixed8(0xff) -@test_throws InexactError UFixed16(2) -@test_throws InexactError UFixed16(0xff) -@test_throws InexactError UFixed16(0xffff) -@test_throws InexactError convert(UFixed8, typemax(UFixed10)) -@test_throws InexactError convert(UFixed16, typemax(UFixed10)) +@test typemax(UFixed{UInt128,100}) == typemax(UInt128) // (UInt128(2)^100-1) + +# TODO: change back to InexactError when it allows message strings +@test_throws ArgumentError UFixed8(2) +@test_throws ArgumentError UFixed8(255) +@test_throws ArgumentError UFixed8(0xff) +@test_throws ArgumentError UFixed16(2) +@test_throws ArgumentError UFixed16(0xff) +@test_throws ArgumentError UFixed16(0xffff) +@test_throws ArgumentError convert(UFixed8, typemax(UFixed10)) +@test_throws ArgumentError convert(UFixed16, typemax(UFixed10)) +@test_throws ArgumentError convert(UFixed{UInt128,100}, 10^9) +@test_throws ArgumentError convert(UFixed{UInt128,100}, 10.0^9) x = UFixed8(0.5) @test isfinite(x) == true @@ -64,7 +68,7 @@ x = UFixed8(0.5) @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{UInt64,3}, 1.1/typemax(UInt64)*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) @@ -93,6 +97,9 @@ end @test (65.2 % UFixed10).i == round(Int, 65.2*1023) % UInt16 @test (-0.3 % UFixed10).i == round(Int, -0.3*1023) % UInt16 +@test 1 % UFixed8 == 1 +@test 2 % UFixed8 == UFixed8(0.996) + x = UFixed8(0b01010001, 0) @test ~x == UFixed8(0b10101110, 0) @test -x == 0xafuf8 @@ -100,8 +107,8 @@ x = UFixed8(0b01010001, 0) for T in (FixedPointNumbers.UF..., UF2...) x = T(0x10,0) y = T(0x25,0) - fx = convert(Float32, x) - fy = convert(Float32, y) + fx = convert(FixedPointNumbers.floattype(T), x) + fy = convert(FixedPointNumbers.floattype(T), y) @test y > x @test y != x @test typeof(x+y) == T @@ -154,6 +161,9 @@ for T in (FixedPointNumbers.UF..., UF2...) testtrunc(eps(T)) end +@test !(UFixed8(0.5) < UFixed8(0.5)) +@test UFixed8(0.5) <= UFixed8(0.5) + @test div(0x10uf8, 0x02uf8) == fld(0x10uf8, 0x02uf8) == 8 @test div(0x0fuf8, 0x02uf8) == fld(0x0fuf8, 0x02uf8) == 7 @test Base.fld1(0x10uf8, 0x02uf8) == 8 @@ -166,6 +176,14 @@ end r = 1uf8:1uf8:48uf8 @test length(r) == 48 +# Promotion within UFixed +@test @inferred(promote(UFixed8(0.2), UFixed8(0.8))) === + (UFixed8(0.2), UFixed8(0.8)) +@test @inferred(promote(UFixed{UInt16,3}(0.2), UFixed{UInt8,3}(0.86))) === + (UFixed{UInt16,3}(0.2), UFixed{UInt16,3}(0.86)) +@test @inferred(promote(UFixed{UInt8,7}(0.197), UFixed{UInt8,4}(0.8))) === + (UFixed{UInt16,7}(0.197), UFixed{UInt16,7}(0.8)) + # Show x = 0xaauf8 iob = IOBuffer()