diff --git a/ThickNumbersInterfaceTests/src/ThickNumbersInterfaceTests.jl b/ThickNumbersInterfaceTests/src/ThickNumbersInterfaceTests.jl index dd04f7f..84bd08b 100644 --- a/ThickNumbersInterfaceTests/src/ThickNumbersInterfaceTests.jl +++ b/ThickNumbersInterfaceTests/src/ThickNumbersInterfaceTests.jl @@ -28,6 +28,7 @@ function test_required(f::Function, @nospecialize(TN), @nospecialize(Ts=nothing) lo, hi = 1/3, nextfloat(2/3) x = f(TN, lo, hi) @test typeof(x) <: TN + @test isthick(TN) @test lo ∈ x @test hi ∈ x @test lo ≈ loval(x) diff --git a/docs/src/developers.md b/docs/src/developers.md index 427f410..c6ac0a0 100644 --- a/docs/src/developers.md +++ b/docs/src/developers.md @@ -50,3 +50,21 @@ be sure to use `[compat]` bounds to specify the major version number of `ThickNu ## Features provided by subtyping ThickNumber See the [User API](@ref). + +## Implementing the interface without subtyping + +Subtype `ThickNumber` to inherit the generic methods in the [User API](@ref). + +A type with another supertype can implement the interface by defining [`isthick`](@ref): + +```julia +ThickNumbers.isthick(::Type{<:MyType}) = true +``` + +It must implement [`loval`](@ref), [`hival`](@ref), [`lohi`](@ref), [`basetype`](@ref), +supported arithmetic, and any needed derived methods. These may include [`mid`](@ref), +[`wid`](@ref), [`rad`](@ref), [`mag`](@ref), [`mig`](@ref), [`hull`](@ref), +[`emptyset`](@ref), [`isempty_tn`](@ref), [`isnan_tn`](@ref), [`isinf_tn`](@ref), +[`isfinite_tn`](@ref), and `iszero`. + +Use `isthick(x)` to recognize both kinds of implementation. diff --git a/docs/src/index.md b/docs/src/index.md index 59af697..27e3f70 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -64,6 +64,7 @@ You can also check a few basic properties, like whether the values contained in ### Type information - [`valuetype(X)`](@ref): return the type of numbers contained in `X` (e.g., `Float64`) +- [`isthick(X)`](@ref): return `true` if `X` (or its type) implements the ThickNumbers interface ### Generic constructors @@ -92,6 +93,7 @@ For an explanation of why these aren't just `==`, `<`, etc, read [The Fundamenta - [`issupset_tn`](@ref) replaces `⊇` (unicode: `⫄`) - [`is_strict_supset_tn`](@ref) replaces `⊃` (unicode: `⪾`) - [`hull`](@ref) creates a number that contains its arguments +- [`isempty_tn(X)`](@ref) replaces `isempty(X)` ### API reference diff --git a/docs/src/user_api.md b/docs/src/user_api.md index 870dcaa..9c44673 100644 --- a/docs/src/user_api.md +++ b/docs/src/user_api.md @@ -9,6 +9,7 @@ violate the interface requirements. ```@docs valuetype +isthick ``` ## Query functions @@ -54,6 +55,7 @@ See also [IntervalSets](https://github.com/JuliaMath/IntervalSets.jl) for a more in(::Real, ::ThickNumber) hull Base.isempty(::ThickNumber) +isempty_tn issubset_tn issupset_tn is_strict_subset_tn diff --git a/src/ThickNumbers.jl b/src/ThickNumbers.jl index 4bf100a..ece4093 100644 --- a/src/ThickNumbers.jl +++ b/src/ThickNumbers.jl @@ -5,7 +5,7 @@ using LinearAlgebra export ThickNumber, FPTNException # Traits -export valuetype, basetype +export valuetype, basetype, isthick # These mimic IEEE Std 1788-2015, Table 9.2, but with `inf` and `sup` # replaced by names that do not imply true bounds. @@ -16,7 +16,7 @@ export valuetype, basetype export lohi, midrad, loval, hival, mid, wid, rad, mag, mig # Set operations -export emptyset, hull, issubset_tn, ⫃, is_strict_subset_tn, ⪽, issupset_tn, ⫄, is_strict_supset_tn, ⪾ +export emptyset, hull, issubset_tn, ⫃, is_strict_subset_tn, ⪽, issupset_tn, ⫄, is_strict_supset_tn, ⪾, isempty_tn # Operators export isequal_tn, iseq_tn, ≐, isapprox_tn, ⩪, isless_tn, ≺, ≻, ⪯, ⪰ @@ -59,6 +59,26 @@ Float64 valuetype(::Type{TN}) where TN<:ThickNumber{T} where T = T valuetype(x::ThickNumber) = valuetype(typeof(x)) +""" + isthick(::Type{T}) where T + isthick(x) + +Return whether a type implements the ThickNumbers interface. + +# Default implementation + +Subtypes of `ThickNumber` return `true` by default: + + isthick(::Type{T}) where T = T <: ThickNumber + isthick(x) = isthick(typeof(x)) + +Types with another supertype can implement the interface by defining +`isthick(::Type{<:SomeType}) = true`. See [Implementing the interface without +subtyping](@ref). +""" +isthick(::Type{T}) where T = T <: ThickNumber +isthick(x) = isthick(typeof(x)) + # Functions that must be defined by subtypes """ @@ -403,12 +423,19 @@ The converse of [`is_strict_subset_tn`](@ref). is_strict_supset_tn(a::ThickNumber, b::ThickNumber) = is_strict_subset_tn(b, a) const ⪾ = is_strict_supset_tn +""" + isempty_tn(x::ThickNumber) + +Return whether the span of `x` is empty (`hival(x) < loval(x)`). +""" +isempty_tn(x::ThickNumber) = hival(x) < loval(x) + """ isempty(x::ThickNumber) Returns `true` if the span of `x` is empty (`hival(x) < loval(x)`), `false` otherwise. """ -Base.isempty(x::ThickNumber) = hival(x) < loval(x) +Base.isempty(x::ThickNumber) = isempty_tn(x) function Base.isdisjoint(a::ThickNumber, b::ThickNumber) (isempty(a) || isempty(b)) && return true diff --git a/test/runtests.jl b/test/runtests.jl index 6d035f7..36b0e2d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -10,6 +10,10 @@ using MidRadArith # Test all the operations defined in ThickNumbers x, y = Interval(1.0, 3.0), Interval(-1.0, 3.0) @test valuetype(x) === valuetype(typeof(x)) === Float64 + @test !isthick(Float64) + @test !isthick(3.0) + @test isthick(Interval{Float64}) + @test isthick(x) @test loval(x) === 1.0 @test hival(x) === 3.0 @test mid(x) === 2.0 @@ -105,6 +109,10 @@ using MidRadArith @test emptyset(Interval{Float32}) === Interval(Inf32, -Inf32) @test emptyset(Interval{Float64}) === Interval(Inf, -Inf) @test emptyset(Interval(1.0, 2.0)) === Interval(Inf, -Inf) + @test isempty_tn(emptyset(Interval{Float64})) + @test !isempty_tn(x) + @test isempty(x) == isempty_tn(x) + @test isempty(emptyset(Interval{Float64})) == isempty_tn(emptyset(Interval{Float64})) @test isfinite_tn(Interval(1, 2)) @test_throws FPTNException isfinite(Interval(1, 2)) @test !isfinite_tn(Interval(1, Inf))