diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index ffb9962f..160c1331 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -45,7 +45,28 @@ nbitsfrac(::Type{X}) where {T, f, X <: FixedPoint{T,f}} = f rawtype(::Type{X}) where {T, X <: FixedPoint{T}} = T # construction using the (approximate) intended value, i.e., N0f8 -*(x::Real, ::Type{X}) where {X<:FixedPoint} = X(x) +*(x::Real, ::Type{X}) where {X <: FixedPoint} = _convert(X, x) + +# constructor-style conversions +(::Type{X})(x::Real) where {X <: FixedPoint} = _convert(X, x) + +function (::Type{<:FixedPoint})(x::AbstractChar) + throw(ArgumentError("FixedPoint (Fixed or Normed) cannot be constructed from a Char")) +end +(::Type{X})(x::Complex) where {X <: FixedPoint} = X(convert(real(typeof(x)), x)) +function (::Type{X})(x::Base.TwicePrecision) where {X <: FixedPoint} + floattype(X) === BigFloat ? X(big(x)) : X(convert(floattype(X), x)) +end + +# conversions +function Base.Bool(x::FixedPoint) + x == zero(x) ? false : x == oneunit(x) ? true : throw(InexactError(:Bool, Bool, x)) +end +function (::Type{Ti})(x::FixedPoint) where {Ti <: Integer} + isinteger(x) || throw(InexactError(:Integer, typeof(x), x)) + floor(Ti, x) +end +Base.Rational{Ti}(x::FixedPoint) where {Ti <: Integer} = Rational{Ti}(Rational(x)) """ isapprox(x::FixedPoint, y::FixedPoint; rtol=0, atol=max(eps(x), eps(y))) diff --git a/src/fixed.jl b/src/fixed.jl index a4c9b65f..24c1ac55 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -24,13 +24,6 @@ struct Fixed{T <: Signed, f} <: FixedPoint{T, f} end end -Fixed{T, f}(x::AbstractChar) where {T,f} = throw(ArgumentError("Fixed cannot be constructed from a Char")) -Fixed{T, f}(x::Complex) where {T,f} = Fixed{T, f}(convert(real(typeof(x)), x)) -Fixed{T, f}(x::Base.TwicePrecision) where {T,f} = Fixed{T, f}(convert(Float64, x)) -Fixed{T,f}(x::Integer) where {T,f} = Fixed{T,f}(round(T, convert(widen1(T),x)<>f) + TF(x.i&(one(widen1(T))<>f) -end -function (::Type{TI})(x::Fixed{T,f}) where {TI <: Integer,T,f} - isinteger(x) || throw(InexactError()) - TI(x.i>>f) +function Base.Rational(x::Fixed{T,f}) where {T, f} + f < bitwidth(T)-1 ? x.i//rawone(x) : x.i//(one(widen1(T))<>f + (x.i&(1<> (bitwidth(T) - f) end -# Conversions -function Normed{T,f}(x::Normed{T2}) where {T <: Unsigned,T2 <: Unsigned,f} - U = Normed{T,f} - y = round((rawone(U)/rawone(x))*reinterpret(x)) - (0 <= y) & (y <= typemax(T)) || throw_converterror(U, x) - reinterpret(U, _unsafe_trunc(T, y)) -end -N0f16(x::N0f8) = reinterpret(N0f16, convert(UInt16, 0x0101*reinterpret(x))) +# constructor-style conversions +function _convert(::Type{N}, x::Normed{T2,f}) where {T, T2, f, N <: Normed{T,f}} + reinterpret(N, convert(T, x.i)) # TODO: input range checking +end -(::Type{U})(x::Real) where {U <: Normed} = _convert(U, x) +function _convert(::Type{N}, x::Normed{T2,f2}) where {T, T2, f, f2, N <: Normed{T,f}} + y = round((rawone(N)/rawone(x))*reinterpret(x)) + (0 <= y) & (y <= typemax(T)) || throw_converterror(N, x) + reinterpret(N, _unsafe_trunc(T, y)) +end + +function _convert(::Type{N}, x::Normed{UInt8,8}) where {N <: Normed{UInt16,16}} # TODO: generalization + reinterpret(N0f16, convert(UInt16, 0x0101*reinterpret(x))) +end -function _convert(::Type{U}, x) where {T, f, U <: Normed{T,f}} +function _convert(::Type{N}, x::Real) where {T, f, N <: Normed{T,f}} if T == UInt128 # for UInt128, we can't widen # the upper limit is not exact - (0 <= x) & (x <= (typemax(T)/rawone(U))) || throw_converterror(U, x) - y = round(rawone(U)*x) + (0 <= x) & (x <= (typemax(T)/rawone(N))) || throw_converterror(N, x) + y = round(rawone(N)*x) else - y = round(widen1(rawone(U))*x) - (0 <= y) & (y <= typemax(T)) || throw_converterror(U, x) + y = round(widen1(rawone(N))*x) + (0 <= y) & (y <= typemax(T)) || throw_converterror(N, x) end - reinterpret(U, _unsafe_trunc(T, y)) + reinterpret(N, _unsafe_trunc(T, y)) end # Prevent overflow (https://discourse.julialang.org/t/saving-greater-than-8-bit-images/6057) -function _convert(::Type{U}, x::Float16) where {T, f, U <: Normed{T,f}} - if Float16(typemax(T)/rawone(U)) > Float32(typemax(T)/rawone(U)) - x == Float16(typemax(T)/rawone(U)) && return typemax(U) +function _convert(::Type{N}, x::Float16) where {T, f, N <: Normed{T,f}} + if Float16(typemax(T)/rawone(N)) > Float32(typemax(T)/rawone(N)) + x == Float16(typemax(T)/rawone(N)) && return typemax(N) end - return _convert(U, Float32(x)) + return _convert(N, Float32(x)) end function _convert(::Type{N}, x::Tf) where {T, f, N <: Normed{T,f}, Tf <: Union{Float32, Float64}} if T === UInt128 && f == 53 @@ -239,10 +238,6 @@ end Base.BigFloat(x::Normed) = reinterpret(x)*(1/BigFloat(rawone(x))) -Base.Bool(x::Normed) = x == zero(x) ? false : true -Base.Integer(x::Normed) = convert(Integer, x*1.0) -(::Type{T})(x::Normed) where {T <: Integer} = convert(T, x*(1/oneunit(T))) -Base.Rational{Ti}(x::Normed) where {Ti <: Integer} = convert(Ti, reinterpret(x))//convert(Ti, rawone(x)) Base.Rational(x::Normed) = reinterpret(x)//rawone(x) abs(x::Normed) = x diff --git a/test/fixed.jl b/test/fixed.jl index 63fb60cb..033909e4 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -97,6 +97,16 @@ end @test_throws InexactError convert(Fixed{Int8, 7}, 1) @test_throws InexactError convert(Fixed{Int8, 7}, 2) @test_throws InexactError convert(Fixed{Int8, 7}, 128) + + @test convert(Q2f5, -1//2) === -0.5Q2f5 + @test_broken convert(Q1f6, Rational{Int8}(-3//4)) === -0.75Q1f6 + @test_broken convert(Q0f7, Rational{Int16}(-3//4)) === -0.75Q0f7 + @test_broken convert(Q0f7, Rational{UInt8}(3//4)) === 0.75Q0f7 + + @test convert(Q0f7, Base.TwicePrecision(0.5)) === 0.5Q0f7 + @test_throws InexactError convert(Q7f8, Base.TwicePrecision(0x80, 0x01)) + tp = Base.TwicePrecision(0xFFFFFFFFp-32, 0xFFFFFFFEp-64) + @test convert(Q0f63, tp) === reinterpret(Q0f63, typemax(Int64)) end @testset "test_fixed" begin @@ -231,9 +241,26 @@ end end end +@testset "bool conversions" begin + @test convert(Bool, 0.0Q1f6) === false + @test convert(Bool, 1.0Q1f6) === true + @test_throws InexactError convert(Bool, 0.5Q1f6) + @test_throws InexactError convert(Bool, -1Q1f6) + @test_broken convert(Bool, Fixed{Int8,8}(0.2)) # TODO: remove this +end + @testset "Integer conversions" begin @test convert(Int, Q1f6(1)) === 1 @test convert(Integer, Q1f6(1)) === Int8(1) + @test convert(UInt, 1Q1f6) === UInt(1) + @test_throws InexactError convert(Integer, 0.5Q1f6) + @test_throws InexactError convert(Int8, 256Q9f6) +end + +@testset "rational conversions" begin + @test convert(Rational, -0.75Q1f6) === Rational{Int8}(-3//4) + @test convert(Rational, -0.75Q0f7) === Rational{Int16}(-3//4) + @test convert(Rational{Int}, -0.75Q0f7) === Rational(-3//4) end @testset "Floating-point conversions" begin diff --git a/test/normed.jl b/test/normed.jl index 084c18d3..090685c7 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -92,13 +92,19 @@ end @test convert(N0f8, 1.1f0/typemax(UInt8)) == eps(N0f8) + @test_broken convert(N0f8, 1//255) === eps(N0f8) + @test_broken convert(N0f8, Rational{Int8}(3//5)) === N0f8(3/5) + @test_broken convert(N0f8, Rational{UInt8}(3//5)) === N0f8(3/5) + + @test convert(N0f8, Base.TwicePrecision(1.0)) === 1N0f8 + @test convert(Float64, eps(N0f8)) == 1/typemax(UInt8) @test convert(Float32, eps(N0f8)) == 1.0f0/typemax(UInt8) @test convert(BigFloat, eps(N0f8)) == BigFloat(1)/typemax(UInt8) 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 + @test_throws InexactError convert(Bool, convert(T, 0.2)) @test convert(Int, one(T)) == 1 @test convert(Integer, one(T)) == 1 @test convert(Rational, one(T)) == 1 @@ -109,30 +115,38 @@ end @test convert(Normed{UInt16,7}, Normed{UInt8,7}(0.504)) === Normed{UInt16,7}(0.504) end +@testset "integer conversions" begin + @test convert(UInt, 1N1f7) === UInt(1) + @test convert(Integer, 1N1f7) === 0x01 + @test convert(Int, 1N1f7) === 1 + @test_throws InexactError convert(Integer, 0.5N1f7) + @test_throws InexactError convert(Int8, 256N8f8) +end + @testset "conversion from float" begin # issue 102 for T in (UInt8, UInt16, UInt32, UInt64, UInt128) for Tf in (Float16, Float32, Float64) @testset "Normed{$T,$f}(::$Tf)" for f = 1:bitwidth(T) - U = Normed{T,f} - r = FixedPointNumbers.rawone(U) + N = Normed{T,f} + r = FixedPointNumbers.rawone(N) - @test reinterpret(U(zero(Tf))) == 0x0 + @test reinterpret(N(zero(Tf))) == 0x0 - input_typemax = Tf(typemax(U)) + input_typemax = Tf(typemax(N)) if isinf(input_typemax) - @test reinterpret(U(floatmax(Tf))) >= round(T, floatmax(Tf)) + @test reinterpret(N(floatmax(Tf))) >= round(T, floatmax(Tf)) else - @test reinterpret(U(input_typemax)) >= (typemax(T)>>1) # overflow check + @test reinterpret(N(input_typemax)) >= (typemax(T)>>1) # overflow check end input_upper = Tf(BigFloat(typemax(T)) / r, RoundDown) isinf(input_upper) && continue # for Julia v0.7 - @test reinterpret(U(input_upper)) == T(min(round(BigFloat(input_upper) * r), typemax(T))) + @test reinterpret(N(input_upper)) == T(min(round(BigFloat(input_upper) * r), typemax(T))) input_exp2 = Tf(exp2(bitwidth(T) - f)) isinf(input_exp2) && continue - @test reinterpret(U(input_exp2)) == T(input_exp2) * r + @test reinterpret(N(input_exp2)) == T(input_exp2) * r end end end @@ -149,27 +163,27 @@ end end for Tf in (Float16, Float32, Float64) - @testset "$Tf(::Normed{$Ti})" for Ti in (UInt8, UInt16) - @testset "$Tf(::Normed{$Ti,$f})" for f = 1:bitwidth(Ti) - T = Normed{Ti,f} + @testset "$Tf(::Normed{$T})" for T in (UInt8, UInt16) + @testset "$Tf(::Normed{$T,$f})" for f = 1:bitwidth(T) + N = Normed{T,f} float_err = 0.0 - for i = typemin(Ti):typemax(Ti) - f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(T))) + for i = typemin(T):typemax(T) + f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(N))) isinf(f_expected) && break # for Float16(::Normed{UInt16,1}) - f_actual = Tf(reinterpret(T, i)) + f_actual = Tf(reinterpret(N, i)) float_err += abs(f_actual - f_expected) end @test float_err == 0.0 end end - @testset "$Tf(::Normed{$Ti})" for Ti in (UInt32, UInt64, UInt128) - @testset "$Tf(::Normed{$Ti,$f})" for f = 1:bitwidth(Ti) - T = Normed{Ti,f} + @testset "$Tf(::Normed{$T})" for T in (UInt32, UInt64, UInt128) + @testset "$Tf(::Normed{$T,$f})" for f = 1:bitwidth(T) + N = Normed{T,f} error_count = 0 - for i in vcat(Ti(0x00):Ti(0xFF), (typemax(Ti)-0xFF):typemax(Ti)) - f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(T))) + for i in vcat(T(0x00):T(0xFF), (typemax(T)-0xFF):typemax(T)) + f_expected = Tf(i / BigFloat(FixedPointNumbers.rawone(N))) isinf(f_expected) && break # for Float16() and Float32() - f_actual = Tf(reinterpret(T, i)) + f_actual = Tf(reinterpret(N, i)) f_actual == f_expected && continue f_actual == prevfloat(f_expected) && continue f_actual == nextfloat(f_expected) && continue