From f79606aea565b4e9834e528dcdf95b9532a768cc Mon Sep 17 00:00:00 2001 From: kimikage Date: Wed, 25 Dec 2019 20:54:04 +0900 Subject: [PATCH 1/3] Commonize `Bool` and `Integer` conversions This includes following breaking changes: - `Bool` throws the InexactError for inputs other than zero/one. - The return type of `Integer(::Normed{T})` is now `T`. - `Integer(::Fixed)` throws the InexactError instead of MethodError. --- src/FixedPointNumbers.jl | 9 +++++++++ src/fixed.jl | 10 ---------- src/normed.jl | 3 --- test/fixed.jl | 11 +++++++++++ test/normed.jl | 10 +++++++++- 5 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index ffb9962f..092930d8 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -47,6 +47,15 @@ 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) +# 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 + """ 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..829ef020 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -81,16 +81,6 @@ Base.BigFloat(x::Fixed{T,f}) where {T,f} = (::Type{TF})(x::Fixed{T,f}) where {TF <: AbstractFloat,T,f} = TF(x.i>>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) -end - (::Type{TR})(x::Fixed{T,f}) where {TR <: Rational,T,f} = TR(x.i>>f + (x.i&(1< Date: Thu, 26 Dec 2019 01:32:24 +0900 Subject: [PATCH 2/3] Commonize `Rational` conversions This includes a breaking change in the return type of `Rational(::Fixed)`. --- src/FixedPointNumbers.jl | 1 + src/fixed.jl | 5 +++-- src/normed.jl | 1 - test/fixed.jl | 11 +++++++++++ test/normed.jl | 4 ++++ 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index 092930d8..cab126f4 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -55,6 +55,7 @@ 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 829ef020..9c50c3ec 100644 --- a/src/fixed.jl +++ b/src/fixed.jl @@ -81,8 +81,9 @@ Base.BigFloat(x::Fixed{T,f}) where {T,f} = (::Type{TF})(x::Fixed{T,f}) where {TF <: AbstractFloat,T,f} = TF(x.i>>f) + TF(x.i&(one(widen1(T))<>f + (x.i&(1< Date: Sat, 28 Dec 2019 12:37:57 +0900 Subject: [PATCH 3/3] Commonize constructor-style conversions --- src/FixedPointNumbers.jl | 13 ++++++++++- src/fixed.jl | 35 ++++++++++++++++-------------- src/normed.jl | 47 ++++++++++++++++++++-------------------- test/fixed.jl | 5 +++++ test/normed.jl | 42 ++++++++++++++++++----------------- 5 files changed, 81 insertions(+), 61 deletions(-) diff --git a/src/FixedPointNumbers.jl b/src/FixedPointNumbers.jl index cab126f4..160c1331 100644 --- a/src/FixedPointNumbers.jl +++ b/src/FixedPointNumbers.jl @@ -45,7 +45,18 @@ 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) diff --git a/src/fixed.jl b/src/fixed.jl index 9c50c3ec..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)<> (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 diff --git a/test/fixed.jl b/test/fixed.jl index 21f5cec6..033909e4 100644 --- a/test/fixed.jl +++ b/test/fixed.jl @@ -102,6 +102,11 @@ end @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 diff --git a/test/normed.jl b/test/normed.jl index 6f9bdfd5..090685c7 100644 --- a/test/normed.jl +++ b/test/normed.jl @@ -96,6 +96,8 @@ end @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) @@ -126,25 +128,25 @@ end 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 @@ -161,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