diff --git a/Project.toml b/Project.toml index d377770..a4ba07d 100644 --- a/Project.toml +++ b/Project.toml @@ -1,13 +1,15 @@ name = "TinyHugeNumbers" uuid = "783c9a47-75a3-44ac-a16b-f1ab7b3acf04" authors = ["Bagaev Dmitry and contributors"] -version = "1.0.1" +version = "1.0.2" [compat] julia = "1" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Test"] +test = ["Aqua", "ForwardDiff", "Test"] diff --git a/src/TinyHugeNumbers.jl b/src/TinyHugeNumbers.jl index 3ebc6c4..5418ae0 100644 --- a/src/TinyHugeNumbers.jl +++ b/src/TinyHugeNumbers.jl @@ -120,8 +120,24 @@ const huge = HugeNumber() ## ------------------------------------------------------------------------------------ ## -Base.promote_rule(::Type{Union{TinyNumber, HugeNumber}}, ::Type{T}) where {T} = T -Base.promote_rule(::Type{TinyNumber}, ::Type{HugeNumber}) = Union{TinyNumber, HugeNumber} -Base.promote_rule(::Type{HugeNumber}, ::Type{TinyNumber}) = Union{TinyNumber, HugeNumber} +# A special structure that is used to promote `TinyNumber` and `HugeNumber` to the same type +# but it cannot be instantiated, this might be useful in situations like `clamp(value, tiny, huge)` +# in this case Julia attempts first to promote `tiny` and `huge` to the same type and then +# uses the result to promote `value` to the resulting type. However, there is no "common" type for +# both `tiny` and `huge` (except for the `Union` but we can't use it either since it introduces ambiguities) +# so we introduce a special structure that will accomodate that +# see also: https://github.com/ReactiveBayes/TinyHugeNumbers.jl/issues/3 +# note: as a result, we cannot store `tiny` and `huge` in the same container (e.g. `Array`), +# but `[ 1.0, tiny, huge ]` will work just fine +struct PromoteTinyOrHuge + PromoteTinyOrHuge() = error("Cannot instantiate an internal structure for promotion.") +end + +Base.promote_rule(::Type{T}, ::Type{PromoteTinyOrHuge}) where {T<:Real} = T +Base.promote_rule(::Type{PromoteTinyOrHuge}, ::Type{PromoteTinyOrHuge}) = PromoteTinyOrHuge +Base.promote_rule(::Type{TinyNumber}, ::Type{HugeNumber}) = PromoteTinyOrHuge + +Base.convert(::Type{PromoteTinyOrHuge}, ::TinyNumber) = error("Cannot convert `tiny` to `huge`. Are you trying to put `tiny` and `huge` in the same container (e.g. `Array`)?") +Base.convert(::Type{PromoteTinyOrHuge}, ::HugeNumber) = error("Cannot convert `huge` to `tiny`. Are you trying to put `tiny` and `huge` in the same container (e.g. `Array`)?") end diff --git a/test/runtests.jl b/test/runtests.jl index 2f08d04..624f02e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,18 +1,19 @@ -using TinyHugeNumbers -using Test +using TinyHugeNumbers, Aqua, Test + +Aqua.test_all(TinyHugeNumbers, deps_compat=(; check_extras=false, check_weakdeps=true)) import TinyHugeNumbers: TinyNumber, HugeNumber -struct ArbitraryFloatType <: AbstractFloat end +struct ArbitraryFloatType <: AbstractFloat end @testset "TinyHugeNumbers.jl" begin - - Base.eps(::Type{ ArbitraryFloatType }) = 0.1 - Base.convert(::Type{ ArbitraryFloatType }, ::Integer) = ArbitraryFloatType() # for testing + + Base.eps(::Type{ArbitraryFloatType}) = 0.1 + Base.convert(::Type{ArbitraryFloatType}, ::Integer) = ArbitraryFloatType() # for testing @test repr(tiny) == "tiny" @test repr(huge) == "huge" - + @test typeof(tiny) === TinyNumber @test typeof(huge) === HugeNumber @@ -77,7 +78,7 @@ struct ArbitraryFloatType <: AbstractFloat end for a in (1, 1.0, 0, 0.0, 1.0f0, 0.0f0, Int32(0), Int32(1), big"1", big"1.0", big"0", big"0.0") T = typeof(a) - for v in [tiny, huge] + for v in Real[tiny, huge] V = typeof(v) for op in [+, -, *, /, >, >=, <, <=] @@ -109,3 +110,43 @@ struct ArbitraryFloatType <: AbstractFloat end end end + +@testset "ForwardDiff.jl compatibility" begin + import ForwardDiff + + f(x) = clamp(x, tiny, huge) + + @test @inferred(ForwardDiff.derivative(f, 1.0)) === 1.0 + @test @inferred(ForwardDiff.derivative(f, 2.0)) === 1.0 + @test @inferred(ForwardDiff.derivative(f, 0.0)) === 0.0 + @test @inferred(ForwardDiff.derivative(f, huge + 1.0)) === 0.0 + @test @inferred(ForwardDiff.derivative(f, tiny - 1.0)) === 0.0 + + g(x) = clamp(x^2, tiny, huge) + + @test @inferred(ForwardDiff.derivative(g, 1.0)) === 2.0 + @test @inferred(ForwardDiff.derivative(g, 2.0)) === 4.0 + @test @inferred(ForwardDiff.derivative(g, 0.0)) === 0.0 + @test @inferred(ForwardDiff.derivative(g, huge + 1.0)) === 0.0 + @test @inferred(ForwardDiff.derivative(g, tiny - 1.0)) === 2(tiny - 1.0) +end + +@testset "Storing `tiny` and `huge` in arrays" begin + @static if VERSION >= v"1.10" + @test_throws "Cannot convert `tiny` to `huge`" [tiny, huge] + @test_throws "Cannot convert `huge` to `tiny`" [huge, tiny] + else + @test_throws ErrorException [tiny, huge] + @test_throws ErrorException [huge, tiny] + end + + for a in (1.0, 0.0, 1.0f0, 0.0f0, big"1.0", big"0.0") + @test [a, tiny, huge] == [a, tiny(a), huge(a)] + @test [tiny, a, huge] == [tiny(a), a, huge(a)] + @test [tiny, huge, a] == [tiny(a), huge(a), a] + + @test [a, huge, tiny] == [a, huge(a), tiny(a)] + @test [huge, a, tiny] == [huge(a), a, tiny(a)] + @test [huge, tiny, a] == [huge(a), tiny(a), a] + end +end