diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index e6e164ba..160590f6 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -11,7 +11,7 @@ jobs: fail-fast: false matrix: version: - - '1.9' + - '1.10' - '1' - 'nightly' os: diff --git a/Project.toml b/Project.toml index eebe3dc7..9be31ca4 100644 --- a/Project.toml +++ b/Project.toml @@ -8,6 +8,7 @@ CRlibm = "96374032-68de-5a5b-8d9e-752f78720389" MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09" OpenBLASConsistentFPCSR_jll = "6cdc7f73-28fd-5e50-80fb-958a8875b1af" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" RoundingEmulator = "5eaf0fd0-dfba-4ccb-bf02-d820a40db705" [weakdeps] @@ -32,13 +33,14 @@ IntervalArithmeticSparseArraysExt = "SparseArrays" Arblib = "1.3.0" CRlibm = "1.0.2" DiffRules = "1" -ForwardDiff = "0.10, 1" +ForwardDiff = "1" IntervalSets = "0.7" -LinearAlgebra = "1.9" +LinearAlgebra = "1.10" MacroTools = "0.5" OpenBLASConsistentFPCSR_jll = "0.3.29" -Random = "1.9" +Random = "1.10" +Printf = "1.10" RecipesBase = "1" RoundingEmulator = "0.2" -SparseArrays = "1.9.0" -julia = "1.9" +SparseArrays = "1.10" +julia = "1.10" diff --git a/README.md b/README.md index c1430053..20b40e79 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ The official documentation is available online: https://juliaintervals.github.io ## Installation -The IntervalArithmetic.jl package requires to [install Julia](https://julialang.org/downloads/) (v1.9 or above). +The IntervalArithmetic.jl package requires to [install Julia](https://julialang.org/downloads/) (v1.10 or above). Then, start Julia and execute the following command in the REPL: diff --git a/docs/src/index.md b/docs/src/index.md index 3a60a15f..418728c6 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -7,7 +7,7 @@ IntervalArithmetic.jl is a Julia package for validated numerics in Julia. All ca ## Installation ```@repl -using Pkg # Julia v1.9 or above +using Pkg # Julia v1.10 or above redirect_stderr(devnull) do # hide Pkg.add("IntervalArithmetic") end # hide diff --git a/ext/IntervalArithmeticForwardDiffExt.jl b/ext/IntervalArithmeticForwardDiffExt.jl index de64f6d1..a789cc21 100644 --- a/ext/IntervalArithmeticForwardDiffExt.jl +++ b/ext/IntervalArithmeticForwardDiffExt.jl @@ -23,8 +23,10 @@ Base.promote_rule(::Type{ExactReal{S}}, ::Type{Dual{T, V, N}}) where {S<:Real, T Base.promote_rule(::Type{Dual{T, V, N}}, ::Type{ExactReal{S}}) where {S<:Real, T, V, N} = Dual{T,ExactReal{IntervalArithmetic.promote_numtype(V, S)},N} -Base.:(==)(x::Union{BareInterval,Interval}, y::Dual) = x == value(y) -Base.:(==)(x::Dual, y::Union{BareInterval,Interval}) = value(x) == y +Base.:(==)(x::Interval, y::Dual) = x == value(y) +Base.:(==)(x::Dual, y::Interval) = value(x) == y +Base.:<(x::Interval, y::Dual) = x < value(y) +Base.:<(x::Dual, y::Interval) = value(x) < y function Base.:(^)(x::Dual{Txy,<:Interval}, y::Dual{Txy,<:Interval}) where {Txy} vx, vy = value(x), value(y) @@ -91,10 +93,9 @@ function Base.:(^)(x::ExactReal, y::Dual{<:Ty}) where {Ty} end end - # Piecewise functions -function (constant::Constant)(::Dual{T, Interval{S}}) where {T, S} +function (constant::Constant)(::Dual{T,Interval{S}}) where {T, S} return Dual{T}(interval(S, constant.value), interval(S, 0.0)) end diff --git a/src/IntervalArithmetic.jl b/src/IntervalArithmetic.jl index c5cca463..8c20a39a 100644 --- a/src/IntervalArithmetic.jl +++ b/src/IntervalArithmetic.jl @@ -77,6 +77,8 @@ include("piecewise.jl") # +import Printf + include("display.jl") export setdisplay @@ -228,7 +230,7 @@ Configure the default behavior for: [`IntervalArithmetic.MatMulMode`](@ref). Keyword: `matmul`. Available options: `:fast` (default), `:slow`. """ -function configure(; numtype::Type{<:NumTypes}=Float64, flavor::Symbol=:set_based, rounding::Symbol=:correct, power::Symbol=:fast, matmul::Symbol=:slow) +function configure(; numtype::Type{<:NumTypes}=Float64, flavor::Symbol=:set_based, rounding::Symbol=:correct, power::Symbol=:fast, matmul::Symbol=:fast) configure_numtype(numtype) configure_flavor(flavor) configure_rounding(rounding) diff --git a/src/display.jl b/src/display.jl index 5928eac4..9f0f0173 100644 --- a/src/display.jl +++ b/src/display.jl @@ -49,7 +49,7 @@ Display options: - significant digits: 6 julia> x = interval(0.1, 0.3) -[0.0999999, 0.300001]_com +[0.1, 0.3]_com julia> setdisplay(:full) Display options: @@ -69,7 +69,7 @@ Display options: - significant digits: 3 julia> x -[0.0999, 0.301]_com +[0.1, 0.3]_com julia> setdisplay(; decorations = false) Display options: @@ -79,7 +79,7 @@ Display options: - significant digits: 3 julia> x -[0.0999, 0.301] +[0.1, 0.3] ``` """ function setdisplay(format::Symbol = display_options.format; @@ -196,14 +196,14 @@ function _str_basic_repr(a::BareInterval{<:AbstractFloat}, format::Symbol) return string(str_lo, ", ", str_hi) elseif format === :midpoint m = mid(a) - str_m = _round_string(m, sigdigits, RoundNearest) + str_m = _round_string(m, sigdigits) # str_m = ifelse(m ≥ 0, string('+', str_m), str_m) - output = string(str_m, " ± ", _round_string(radius(a), sigdigits, RoundUp)) + output = string(str_m, " ± ", _round_string(radius(a), sigdigits)) return replace(output, "Inf" => '∞') else - str_lo = _round_string(lo, sigdigits, RoundDown) + str_lo = _round_string(lo, sigdigits) # str_lo = ifelse(lo ≥ 0, string('+', str_lo), str_lo) - str_hi = _round_string(hi, sigdigits, RoundUp) + str_hi = _round_string(hi, sigdigits) # str_hi = ifelse(hi ≥ 0, string('+', str_hi), str_hi) output = string('[', str_lo, ", ", str_hi, ']') return replace(output, "Inf]" => "∞)", "[-Inf" => "(-∞") @@ -231,26 +231,26 @@ function _str_basic_repr(a::BareInterval{Float32}, format::Symbol) return string(str_lo, ", ", str_hi) elseif format === :midpoint m = mid(a) - str_m = _round_string(m, sigdigits, RoundNearest) + str_m = _round_string(m, sigdigits) str_m = replace(string(str_m, "f0"), "NaNf0" => "NaN32", "Inff0" => "Inf32") if contains(str_m, 'e') str_m = replace(str_m, 'e' => 'f', "f0" => "") end # str_m = ifelse(m ≥ 0, string('+', str_m), str_m) - str_r = _round_string(radius(a), sigdigits, RoundUp) + str_r = _round_string(radius(a), sigdigits) str_r = replace(string(str_r, "f0"), "NaNf0" => "NaN32", "Inff0" => "Inf32") if contains(str_r, 'e') str_r = replace(str_r, 'e' => 'f', "f0" => "") end return string(str_m, " ± ", str_r) else - str_lo = _round_string(lo, sigdigits, RoundDown) + str_lo = _round_string(lo, sigdigits) str_lo = replace(string('[', str_lo, "f0"), "NaNf0" => "NaN32", "[-Inff0" => "(-∞") if contains(str_lo, 'e') str_lo = replace(str_lo, 'e' => 'f', "f0" => "") end # str_lo = ifelse(lo ≥ 0, string('+', str_lo), str_lo) - str_hi = _round_string(hi, sigdigits, RoundUp) + str_hi = _round_string(hi, sigdigits) str_hi = replace(string(str_hi, "f0]"), "NaNf0" => "NaN32", "Inff0]" => "∞)") if contains(str_hi, 'e') str_hi = replace(str_hi, 'e' => 'f', "f0" => "") @@ -274,14 +274,14 @@ function _str_basic_repr(a::BareInterval{Float16}, format::Symbol) return replace(output, "Float16(NaN)" => "NaN16", "Float16(-Inf)" => "-Inf16", "Float16(Inf)" => "Inf16") elseif format === :midpoint m = mid(a) - str_m = _round_string(m, sigdigits, RoundNearest) + str_m = _round_string(m, sigdigits) # str_m = ifelse(m ≥ 0, string('+', str_m), str_m) - output = string("Float16(", str_m, ") ± Float16(", _round_string(radius(a), sigdigits, RoundUp), ')') + output = string("Float16(", str_m, ") ± Float16(", _round_string(radius(a), sigdigits), ')') return replace(output, "Float16(NaN)" => "NaN16", "Float16(Inf)" => '∞') else - str_lo = _round_string(lo, sigdigits, RoundDown) + str_lo = _round_string(lo, sigdigits) # str_lo = ifelse(lo ≥ 0, string('+', str_lo), str_lo) - str_hi = _round_string(sup(a), sigdigits, RoundUp) + str_hi = _round_string(sup(a), sigdigits) # str_hi = ifelse(hi ≥ 0, string('+', str_hi), str_hi) output = string("[Float16(", str_lo, "), Float16(", str_hi, ")]") return replace(output, "Float16(NaN)" => "NaN16", "[Float16(-Inf)" => "(-∞", "Float16(Inf)]" => "∞)") @@ -308,102 +308,19 @@ function _str_basic_repr(a::BareInterval{<:Rational}, format::Symbol) end end -# round to the prescribed significant digits -# code inspired by `_string(x::BigFloat, k::Integer)` in base/mpfr.jl - -function _round_string(x::T, sigdigits::Int, r::RoundingMode) where {T<:AbstractFloat} - str_x = string(x) - str_digits = split(contains(str_x, '.') ? split(str_x, '.'; limit = 2)[2] : str_x, 'e'; limit = 2)[1] - len = length(str_digits) - if isinteger(x) && sigdigits ≥ len # `x` is exactly representable - return replace(_round_string(big(x), length(str_x), RoundNearest), "e-0" => "e-") - elseif ispow2(abs(x)) && sigdigits ≥ len # `x` is exactly representable - return replace(_round_string(big(x), len + 1, RoundNearest), "e-0" => "e-") - else - return _round_string(big(x), sigdigits, r) - end -end +# truncate to the prescribed significant digits -_round_string(x::BigFloat, sigdigits::Int, ::RoundingMode{:Nearest}) = - Base.MPFR._string(x, sigdigits-1) # `sigdigits-1` digits after the decimal - -function _round_string(x::BigFloat, sigdigits::Int, r::RoundingMode) - if !isfinite(x) - return string(Float64(x)) - else - str_x = string(x) - str_digits = split(split(str_x, '.'; limit = 2)[2], 'e'; limit = 2)[1] - len = length(str_digits) - if isinteger(x) && sigdigits ≥ len # `x` is exactly representable - return _round_string(big(x), length(str_x), RoundNearest) - elseif ispow2(abs(x)) && sigdigits ≥ len # `x` is exactly representable - return _round_string(big(x), len + 1, RoundNearest) - else - # `sigdigits` digits after the decimal - str = Base.MPFR.string_mpfr(x, "%.$(sigdigits)Re") - rounded_str = _round_string(str, r) - return Base.MPFR._prettify_bigfloat(rounded_str) - end - end +function _round_string(x::AbstractFloat, sigdigits::Int) + !isfinite(x) && return string(x) + max_sig_digits = _count_sigdigits(string(x)) + ndigits = min(sigdigits, max_sig_digits) + str = Printf.@sprintf("%.*g", ndigits, x) + occursin(r"[eE]", str) && return replace(str, r"^([+-]?\d+)(?=[eE])" => s"\1.0", r"([eE][+-])0+(\d+)" => s"\1\2") + !occursin(r"\.", str) && return str * ".0" + return str end -_round_string(s::String, ::RoundingMode{:Up}) = - startswith(s, '-') ? string('-', _round_string_down(s[2:end])) : _round_string_up(s) - -_round_string(s::String, ::RoundingMode{:Down}) = - startswith(s, '-') ? string('-', _round_string_up(s[2:end])) : _round_string_down(s) - -function _round_string_up(s::String) - # `s` has one extra significant digit to control the rounding - mantissa, exponent = eachsplit(s, 'e') - mantissa = mantissa[1:end-1] - len = length(mantissa) - idx = findlast(d -> (d !== '9') & (d !== '.'), mantissa) - if idx == len # last significant digit is not `9` - d = parse(Int, mantissa[len]) + 1 # increase the last significant digit - return string(view(mantissa, 1:len-1), d, 'e', exponent) - else - if isnothing(idx) # all significant digits are `9` - expo = parse(Int, exponent) + 1 # increase the exponent by `1` - expo_str = string(expo; pad = 2) - exponent = expo < 0 ? expo_str : string('+', expo_str) - return string("1.", '0'^(len - 2), 'e', exponent) - else - new_mantissa = string( - view(mantissa, 1:idx-1), - parse(Int, mantissa[idx]) + 1, - # add `"."` if the last significant digit not equal to `9` is before the decimal point - idx == 1 ? "." : "", - '0'^(len - idx)) - return string(new_mantissa, 'e', exponent) - end - end -end - -function _round_string_down(s::String) - # `s` has one extra significant digit to control the rounding - mantissa, exponent = eachsplit(s, 'e') - len = length(mantissa) - idx = findlast(d -> (d !== '0') & (d !== '.'), mantissa) - if idx == len # last significant digit is not `0` - return string(view(mantissa, 1:len-1), 'e', exponent) # truncate - else - if isnothing(idx) # all significant digits are `0` - expo = parse(Int, exponent) - 1 # decrease the exponent by `1` - expo_str = string(expo; pad = 2) - exponent = expo < 0 ? expo_str : string('+', expo_str) - return string("9.", '9'^(len - 3), 'e', exponent) - else - new_mantissa = string( - view(mantissa, 1:idx-1), - parse(Int, mantissa[idx]) - 1, - # add `"."` if the last significant digit not equal to `0` is before the decimal point - idx == 1 ? "." : "", - '9'^(len - (idx + 1))) - return string(new_mantissa, 'e', exponent) - end - end -end +_count_sigdigits(s::AbstractString) = length(replace(split(s, r"[eE]")[1], '-' => "", '.' => "", r"^0+" => "")) # diff --git a/src/intervals/real_interface.jl b/src/intervals/real_interface.jl index 6b776e26..fcf8ddb7 100644 --- a/src/intervals/real_interface.jl +++ b/src/intervals/real_interface.jl @@ -78,100 +78,62 @@ Base.hash(x::Interval, h::UInt) = hash(sup(x), hash(inf(x), hash(Interval, h))) # -for T ∈ (:BareInterval, :Interval) - @eval begin - function Base.:(==)(x::$T, y::$T) # also returned when calling `≤`, `≥`, `isequal` - isthin(x) && return sup(x) == y - isthin(y) && return x == sup(y) - return throw(ArgumentError("`==` is purposely not supported when the intervals are overlapping. See instead `isequal_interval`")) - end - - Base.:<(::$T, ::$T) = # also returned when calling `isless`, `>` - throw(ArgumentError("`<` is purposely not supported for intervals. See instead `isstrictless`, `strictprecedes`")) - - Base.isdisjoint(::$T, ::$T) = - throw(ArgumentError("`isdisjoint` is purposely not supported for intervals. See instead `isdisjoint_interval`")) - - Base.issubset(::$T, ::$T) = - throw(ArgumentError("`issubset` is purposely not supported for intervals. See instead `issubset_interval`")) - - Base.issetequal(::$T, ::$T) = - throw(ArgumentError("`issetequal` is purposely not supported for intervals. See instead `isequal_interval`")) - - Base.in(::$T, ::$T) = - throw(ArgumentError("`in` is purposely not supported for intervals. See instead `in_interval`")) - Base.in(::Real, ::$T) = - throw(ArgumentError("`in` is purposely not supported for intervals. See instead `in_interval`")) - Base.in(::$T, ::Real) = - throw(ArgumentError("`in` is purposely not supported for intervals. See instead `in_interval`")) - - Base.isempty(::$T) = - throw(ArgumentError("`isempty` is purposely not supported for intervals. See instead `isempty_interval`")) - - Base.isfinite(::$T) = # also returned when calling `isinf` - throw(ArgumentError("`isfinite` is purposely not supported for intervals. See instead `isbounded`")) - - Base.isnan(::$T) = - throw(ArgumentError("`isnan` is purposely not supported for intervals. See instead `isnai`")) - - Base.intersect(::$T) = - throw(ArgumentError("`intersect` is purposely not supported for intervals. See instead `intersect_interval`")) - - Base.union!(::BitSet, ::$T) = # needed to resolve ambiguity - throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) - Base.union!(::AbstractSet, ::$T) = # also returned when calling `intersect`, `symdiff` with intervals - throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) - Base.union!(::AbstractVector{S}, ::$T) where {S} = - throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) - Base.union!(::AbstractVector{S}, ::$T, ::Any, ::Any...) where {S} = - throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) - Base.union!(::AbstractVector{S}, ::$T, ::$T, ::Any...) where {S} = - throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) - Base.union!(::AbstractVector{S}, ::Any, ::$T, ::Any...) where {S} = - throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) - - Base.setdiff(::$T) = - throw(ArgumentError("`setdiff` is purposely not supported for intervals. See instead `interiordiff`")) - Base.setdiff!(::AbstractSet, ::$T) = - throw(ArgumentError("`setdiff!` is purposely not supported for intervals. See instead `interiordiff`")) - end +function Base.:(==)(x::Interval, y::Interval) # also returned when calling `≤`, `≥`, `isequal` + isthin(x) & isthin(y) && return isequal_interval(x, y) + isdisjoint_interval(x, y) && return false + return throw(ArgumentError("`==` is purposely not supported for overlapping non-thin intervals. See instead `isequal_interval`")) end -Base.union!(::AbstractVector{S}, ::BareInterval, ::Interval, ::Any...) where {S} = - throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) -Base.union!(::AbstractVector{S}, ::Interval, ::BareInterval, ::Any...) where {S} = - throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) +function Base.:<(x::Interval, y::Interval) + strictprecedes(x, y) && return true + strictprecedes(y, x) && return false + isthin(x) & isthin(y) && return !isequal_interval(x, y) + return throw(ArgumentError("`<` is purposely not supported for overlapping intervals. See instead `strictprecedes`")) +end -# pointwise equality +function Base.isfinite(x::Interval) # also returned when calling `isinf` + isbounded(x) && return true + return throw(ArgumentError("`isfinite` is purposely not supported for intervals containing infinite bounds. See instead `isbounded`")) +end -""" - ==(::BareInterval, ::Number) - ==(::Number, ::BareInterval) - ==(::Interval, ::Number) - ==(::Number, ::Interval) - -Test whether an interval is the singleton of a given number. In other words, the -result is true if and only if the interval contains only that number. - -!!! note - Comparison between intervals is purposely disallowed. Indeed, equality - between non-singleton intervals has distinct properties, notably ``x = y`` - does not imply ``x - y = 0``. See instead [`isequal_interval`](@ref). -""" -Base.:(==)(x::Union{BareInterval,Interval}, y::Number) = isthin(x, y) -Base.:(==)(x::Number, y::Union{BareInterval,Interval}) = y == x -# needed to resolve ambiguity from irrationals.jl -Base.:(==)(x::Interval, y::AbstractIrrational) = isthin(x, y) -Base.:(==)(x::AbstractIrrational, y::Interval) = y == x -# needed to resolve ambiguity from complex.jl -Base.:(==)(x::Interval, y::Complex) = isreal(y) & (real(y) == x) -Base.:(==)(x::Complex, y::Interval) = y == x - -# follows docstring of `Base.iszero` -Base.iszero(x::Union{BareInterval,Interval}) = isthinzero(x) - -# follows docstring of `Base.isone` -Base.isone(x::Union{BareInterval,Interval}) = isthinone(x) - -# follows docstring of `Base.isinteger` -Base.isinteger(x::Union{BareInterval,Interval}) = isthininteger(x) +Base.isnan(x::Interval) = isnai(x) + +Base.isinteger(x::Interval) = !isthin(x) & !isdisjoint_interval(x, floor(x), ceil(x)) ? throw(ArgumentError("`isinteger` is purposely not supported for a non-thin interval containing at least one integer. See instead `isthininteger`")) : isthininteger(x) + +# disallowed + +Base.in(::Interval, ::Interval) = + throw(ArgumentError("`in` is purposely not supported for intervals. See instead `in_interval`")) + +Base.isempty(::Interval) = + throw(ArgumentError("`isempty` is purposely not supported for intervals. See instead `isempty_interval`")) + +Base.isapprox(::Interval, ::Interval) = + throw(ArgumentError("`isapprox` is purposely not supported for intervals")) + +Base.isdisjoint(::Interval, ::Interval) = + throw(ArgumentError("`isdisjoint` is purposely not supported for intervals. See instead `isdisjoint_interval`")) +Base.issubset(::Interval, ::Interval) = + throw(ArgumentError("`issubset` is purposely not supported for intervals. See instead `issubset_interval`")) +Base.issetequal(::Interval, ::Interval) = + throw(ArgumentError("`issetequal` is purposely not supported for intervals. See instead `isequal_interval`")) +Base.intersect(::Interval, ::Interval...) = + throw(ArgumentError("`intersect` is purposely not supported for intervals. See instead `intersect_interval`")) + +Base.union!(::BitSet, ::Interval) = # needed to resolve ambiguity + throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) +Base.union!(::AbstractSet, ::Interval) = # also returned when calling `intersect`, `symdiff` with intervals + throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) +Base.union!(::AbstractVector{S}, ::Interval) where {S} = + throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) +Base.union!(::AbstractVector{S}, ::Interval, ::Any, ::Any...) where {S} = + throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) +Base.union!(::AbstractVector{S}, ::Interval, ::Interval, ::Any...) where {S} = + throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) +Base.union!(::AbstractVector{S}, ::Any, ::Interval, ::Any...) where {S} = + throw(ArgumentError("`union!` is purposely not supported for intervals. See instead `hull`")) + +Base.setdiff(::Interval) = + throw(ArgumentError("`setdiff` is purposely not supported for intervals. See instead `interiordiff`")) +Base.setdiff!(::AbstractSet, ::Interval) = + throw(ArgumentError("`setdiff!` is purposely not supported for intervals. See instead `interiordiff`")) diff --git a/test/aqua.jl b/test/aqua.jl index 88c10f32..3c585886 100644 --- a/test/aqua.jl +++ b/test/aqua.jl @@ -2,27 +2,25 @@ using Test using IntervalArithmetic using Aqua -if VERSION ≥ v"1.10" - @testset "Aqua tests (performance)" begin - # This tests that we don't accidentally run into - # https://github.com/JuliaLang/julia/issues/29393 - # Aqua.test_unbound_args(IntervalArithmetic) - ua = Aqua.detect_unbound_args_recursively(IntervalArithmetic) - @test length(ua) == 0 +@testset "Aqua tests (performance)" begin + # This tests that we don't accidentally run into + # https://github.com/JuliaLang/julia/issues/29393 + # Aqua.test_unbound_args(IntervalArithmetic) + ua = Aqua.detect_unbound_args_recursively(IntervalArithmetic) + @test length(ua) == 0 - # See: https://github.com/SciML/OrdinaryDiffEq.jl/issues/1750 - # Test that we're not introducing method ambiguities across deps - ambs = Aqua.detect_ambiguities(IntervalArithmetic; recursive = true) - pkg_match(pkgname, pkdir::Nothing) = false - pkg_match(pkgname, pkdir::AbstractString) = occursin(pkgname, pkdir) - filter!(x -> pkg_match("IntervalArithmetic", pkgdir(last(x).module)), ambs) - for method_ambiguity ∈ ambs - @show method_ambiguity - end - @test length(ambs) == 0 + # See: https://github.com/SciML/OrdinaryDiffEq.jl/issues/1750 + # Test that we're not introducing method ambiguities across deps + ambs = Aqua.detect_ambiguities(IntervalArithmetic; recursive = true) + pkg_match(pkgname, pkdir::Nothing) = false + pkg_match(pkgname, pkdir::AbstractString) = occursin(pkgname, pkdir) + filter!(x -> pkg_match("IntervalArithmetic", pkgdir(last(x).module)), ambs) + for method_ambiguity ∈ ambs + @show method_ambiguity end + @test length(ambs) == 0 +end - @testset "Aqua tests (additional)" begin - Aqua.test_all(IntervalArithmetic) - end +@testset "Aqua tests (additional)" begin + Aqua.test_all(IntervalArithmetic; ambiguities = VERSION ≥ v"1.11") end diff --git a/test/interval_tests/consistency.jl b/test/interval_tests/consistency.jl index eca6bd4f..159d1460 100644 --- a/test/interval_tests/consistency.jl +++ b/test/interval_tests/consistency.jl @@ -361,23 +361,46 @@ end end - @testset "Disallowed `Real` functionalities" begin + @testset "`Real` functionalities" begin x, y = interval(1), interval(2) + + @test !isnan(x) + + @test isone(x) + @test !iszero(x) + @test_throws ArgumentError iszero(interval(0, 1)) @test x != y - @test (interval(1, 2) != y) & (y != interval(1, 2)) + @test x == 1 + @test_throws ArgumentError interval(1, 2) != 2 + @test_throws ArgumentError interval(1, 2) != y + @test_throws ArgumentError y != interval(1, 2) @test_throws ArgumentError interval(1, 2) == interval(1, 2) - @test_throws ArgumentError x < y - @test_throws ArgumentError isdisjoint(x, y) - @test_throws ArgumentError issubset(x, y) - @test_throws ArgumentError issetequal(x, y) - @test_throws ArgumentError x ∈ y - @test_throws ArgumentError isempty(x) - @test_throws ArgumentError isfinite(x) - @test_throws ArgumentError isnan(x) + + @test x < y + @test x < 2 + @test !(x > y) + @test !(x < x) + @test !(x < 1) + @test_throws ArgumentError x < interval(1, 2) + + @test isfinite(x) + @test_throws ArgumentError isfinite(interval(1, Inf)) + @test isinteger(x) - @test x == 1 - @test isone(x) - @test !iszero(x) + @test !isinteger(interval(1.2, 1.9)) + @test_throws ArgumentError isinteger(interval(1.5, 2.5)) + + # + + @test_throws ArgumentError intersect(x, x) + @test_throws ArgumentError isapprox(x, x) + @test_throws ArgumentError isdisjoint(x, x) + @test_throws ArgumentError issubset(x, x) + @test_throws ArgumentError issetequal(x, x) + @test_throws ArgumentError x ∈ x + @test_throws ArgumentError isempty(x) + @test_throws ArgumentError union(x, x) + @test_throws ArgumentError setdiff(x, x) end end diff --git a/test/interval_tests/display.jl b/test/interval_tests/display.jl index bc1d7156..b713e45c 100644 --- a/test/interval_tests/display.jl +++ b/test/interval_tests/display.jl @@ -1,9 +1,3 @@ -if VERSION < v"1.13.0-DEV" - const BIGFLOAT_SIGN_STR = "+" -else - const BIGFLOAT_SIGN_STR = "" -end - setprecision(BigFloat, 256) do @testset "BareInterval" begin a = bareinterval(-floatmin(Float64), 1.3) @@ -19,18 +13,18 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), emptyinterval(BareInterval{Float64})) == "∅" - @test sprint(show, MIME("text/plain"), a) == "[-2.22508e-308, 1.30001]" + @test sprint(show, MIME("text/plain"), a) == "[-2.22507e-308, 1.3]" @test sprint(show, MIME("text/plain"), large_expo) == - "[0.0, 1.00001e$(BIGFLOAT_SIGN_STR)123456789]₂₅₆" + "[0.0, 1.0e+123456789]₂₅₆" end @testset "20 significant digits" begin # `decorations` keyword has no impact for `BareInterval` setdisplay(; sigdigits = 20, decorations = true) - @test sprint(show, MIME("text/plain"), a) == "[-2.2250738585072014e-308, 1.3000000000000000445]" + @test sprint(show, MIME("text/plain"), a) == "[-2.2250738585072014e-308, 1.3]" @test sprint(show, MIME("text/plain"), large_expo) == - "[0.0, 1.0000000000000000001e$(BIGFLOAT_SIGN_STR)123456789]₂₅₆" + "[0.0, 1.0e+123456789]₂₅₆" end end @@ -43,7 +37,7 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), a) == "BareInterval{Float64}(-2.2250738585072014e-308, 1.3)" @test sprint(show, MIME("text/plain"), large_expo) == - "BareInterval{BigFloat}(0.0, 1.000000000000000000000000000000000000000000000000000000000000000000000000000004e$(BIGFLOAT_SIGN_STR)123456789)" + "BareInterval{BigFloat}(0.0, $(sup(large_expo)))" end @testset "Midpoint format" begin @@ -52,9 +46,9 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), emptyinterval(BareInterval{Float64})) == "∅" - @test sprint(show, MIME("text/plain"), a) == "0.65 ± 0.650001" + @test sprint(show, MIME("text/plain"), a) == "0.65 ± 0.65" @test sprint(show, MIME("text/plain"), large_expo) == - "(5.0e$(BIGFLOAT_SIGN_STR)123456788 ± 5.00001e$(BIGFLOAT_SIGN_STR)123456788)₂₅₆" + "(5.0e+123456788 ± 5.0e+123456788)₂₅₆" end end @@ -82,13 +76,13 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), a) == "[1.0, 2.0]_com" @test sprint(show, MIME("text/plain"), a_NG) == "[1.0, 2.0]_com_NG" - @test sprint(show, MIME("text/plain"), b) == "[-2.22508e-308, 1.30001]_com" - @test sprint(show, MIME("text/plain"), b32) == "[-1.1755f-38, 1.30001f0]_com" - @test sprint(show, MIME("text/plain"), b16) == "[Float16(-6.104e-5), Float16(1.29981)]_com" + @test sprint(show, MIME("text/plain"), b) == "[-2.22507e-308, 1.3]_com" + @test sprint(show, MIME("text/plain"), b32) == "[-1.17549f-38, 1.3f0]_com" + @test sprint(show, MIME("text/plain"), b16) == "[Float16(-6.104e-5), Float16(1.3)]_com" @test sprint(show, MIME("text/plain"), br) == "[-11//10, 13//10]_com" @test sprint(show, MIME("text/plain"), c) == "[-1.0, ∞)_dac" @test sprint(show, MIME("text/plain"), large_expo) == - "[0.0, 1.00001e$(BIGFLOAT_SIGN_STR)123456789]₂₅₆_com" + "[0.0, 1.0e+123456789]₂₅₆_com" end @testset "No decorations" begin @@ -99,13 +93,13 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), a) == "[1.0, 2.0]" @test sprint(show, MIME("text/plain"), a_NG) == "[1.0, 2.0]_NG" - @test sprint(show, MIME("text/plain"), b) == "[-2.22508e-308, 1.30001]" - @test sprint(show, MIME("text/plain"), b32) == "[-1.1755f-38, 1.30001f0]" - @test sprint(show, MIME("text/plain"), b16) == "[Float16(-6.104e-5), Float16(1.29981)]" + @test sprint(show, MIME("text/plain"), b) == "[-2.22507e-308, 1.3]" + @test sprint(show, MIME("text/plain"), b32) == "[-1.17549f-38, 1.3f0]" + @test sprint(show, MIME("text/plain"), b16) == "[Float16(-6.104e-5), Float16(1.3)]" @test sprint(show, MIME("text/plain"), br) == "[-11//10, 13//10]" @test sprint(show, MIME("text/plain"), c) == "[-1.0, ∞)" @test sprint(show, MIME("text/plain"), large_expo) == - "[0.0, 1.00001e$(BIGFLOAT_SIGN_STR)123456789]₂₅₆" + "[0.0, 1.0e+123456789]₂₅₆" end end @@ -114,13 +108,13 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), a) == "[1.0, 2.0]_com" @test sprint(show, MIME("text/plain"), a_NG) == "[1.0, 2.0]_com_NG" - @test sprint(show, MIME("text/plain"), b) == "[-2.2250738585072014e-308, 1.3000000000000000445]_com" - @test sprint(show, MIME("text/plain"), b32) == "[-1.1754944f-38, 1.2999999523162841797f0]_com" - @test sprint(show, MIME("text/plain"), b16) == "[Float16(-6.104e-5), Float16(1.2998046875000000001)]_com" + @test sprint(show, MIME("text/plain"), b) == "[-2.2250738585072014e-308, 1.3]_com" + @test sprint(show, MIME("text/plain"), b32) == "[-1.1754944f-38, 1.3f0]_com" + @test sprint(show, MIME("text/plain"), b16) == "[Float16(-6.104e-5), Float16(1.3)]_com" @test sprint(show, MIME("text/plain"), br) == "[-11//10, 13//10]_com" @test sprint(show, MIME("text/plain"), c) == "[-1.0, ∞)_dac" @test sprint(show, MIME("text/plain"), large_expo) == - "[0.0, 1.0000000000000000001e$(BIGFLOAT_SIGN_STR)123456789]₂₅₆_com" + "[0.0, 1.0e+123456789]₂₅₆_com" end end @@ -139,7 +133,7 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), br) == "Interval{Rational{Int64}}(-11//10, 13//10, com)" @test sprint(show, MIME("text/plain"), c) == "Interval{Float64}(-1.0, Inf, dac)" @test sprint(show, MIME("text/plain"), large_expo) == - "Interval{BigFloat}(0.0, 1.000000000000000000000000000000000000000000000000000000000000000000000000000004e$(BIGFLOAT_SIGN_STR)123456789, com)" + "Interval{BigFloat}(0.0, $(sup(large_expo)), com)" end @testset "Midpoint format" begin @@ -153,13 +147,13 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), a) == "(1.5 ± 0.5)_com" @test sprint(show, MIME("text/plain"), a_NG) == "(1.5 ± 0.5)_com_NG" - @test sprint(show, MIME("text/plain"), b) == "(0.65 ± 0.650001)_com" - @test sprint(show, MIME("text/plain"), b32) == "(0.65f0 ± 0.650001f0)_com" - @test sprint(show, MIME("text/plain"), b16) == "(Float16(0.649902) ± Float16(0.649903))_com" + @test sprint(show, MIME("text/plain"), b) == "(0.65 ± 0.65)_com" + @test sprint(show, MIME("text/plain"), b32) == "(0.65f0 ± 0.65f0)_com" + @test sprint(show, MIME("text/plain"), b16) == "(Float16(0.65) ± Float16(0.65))_com" @test sprint(show, MIME("text/plain"), br) == "(1//10 ± 6//5)_com" - @test sprint(show, MIME("text/plain"), c) == "(1.79769e$(BIGFLOAT_SIGN_STR)308 ± ∞)_dac" + @test sprint(show, MIME("text/plain"), c) == "(1.79769e+308 ± ∞)_dac" @test sprint(show, MIME("text/plain"), large_expo) == - "(5.0e$(BIGFLOAT_SIGN_STR)123456788 ± 5.00001e$(BIGFLOAT_SIGN_STR)123456788)₂₅₆_com" + "(5.0e+123456788 ± 5.0e+123456788)₂₅₆_com" end @testset "No decorations" begin @@ -170,13 +164,13 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), a) == "1.5 ± 0.5" @test sprint(show, MIME("text/plain"), a_NG) == "(1.5 ± 0.5)_NG" - @test sprint(show, MIME("text/plain"), b) == "0.65 ± 0.650001" - @test sprint(show, MIME("text/plain"), b32) == "0.65f0 ± 0.650001f0" - @test sprint(show, MIME("text/plain"), b16) == "Float16(0.649902) ± Float16(0.649903)" + @test sprint(show, MIME("text/plain"), b) == "0.65 ± 0.65" + @test sprint(show, MIME("text/plain"), b32) == "0.65f0 ± 0.65f0" + @test sprint(show, MIME("text/plain"), b16) == "Float16(0.65) ± Float16(0.65)" @test sprint(show, MIME("text/plain"), br) == "1//10 ± 6//5" - @test sprint(show, MIME("text/plain"), c) == "1.79769e$(BIGFLOAT_SIGN_STR)308 ± ∞" + @test sprint(show, MIME("text/plain"), c) == "1.79769e+308 ± ∞" @test sprint(show, MIME("text/plain"), large_expo) == - "(5.0e$(BIGFLOAT_SIGN_STR)123456788 ± 5.00001e$(BIGFLOAT_SIGN_STR)123456788)₂₅₆" + "(5.0e+123456788 ± 5.0e+123456788)₂₅₆" end end end @@ -197,7 +191,7 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), a) == "[0.0, 2.0]_com + im*[1.0, 1.0]_com" @test sprint(show, MIME("text/plain"), b) == "[0.0, 2.0]_com - im*[1.0, 1.0]_com" - @test sprint(show, MIME("text/plain"), c) == "[0.0, 1.00001e-70]_com - im*[0.999999e-70, 1.00001e-70]_com" + @test sprint(show, MIME("text/plain"), c) == "[0.0, 1.0e-70]_com - im*[1.0e-70, 1.0e-70]_com" end @testset "No decorations" begin @@ -205,7 +199,7 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), a) == "[0.0, 2.0] + im*[1.0, 1.0]" @test sprint(show, MIME("text/plain"), b) == "[0.0, 2.0] - im*[1.0, 1.0]" - @test sprint(show, MIME("text/plain"), c) == "[0.0, 1.00001e-70] - im*[0.999999e-70, 1.00001e-70]" + @test sprint(show, MIME("text/plain"), c) == "[0.0, 1.0e-70] - im*[1.0e-70, 1.0e-70]" end end end @@ -216,7 +210,7 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), a) == "Interval{Float64}(0.0, 2.0, com) + im*Interval{Float64}(1.0, 1.0, com)" @test sprint(show, MIME("text/plain"), b) == "Interval{Float64}(0.0, 2.0, com) - im*Interval{Float64}(1.0, 1.0, com)" - # @test sprint(show, MIME("text/plain"), c) == "Interval{Float64}(0.0, 2.0, com) + im*Interval{Float64}(1.0, 1.0, com)" + @test sprint(show, MIME("text/plain"), c) == "Interval{Float64}(0.0, 1.0e-70, com) - im*Interval{Float64}(1.0e-70, 1.0e-70, com)" end @testset "Midpoint format" begin @@ -227,7 +221,7 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), a) == "(1.0 ± 1.0)_com + im*(1.0 ± 0.0)_com" @test sprint(show, MIME("text/plain"), b) == "(1.0 ± 1.0)_com - im*(1.0 ± 0.0)_com" - @test sprint(show, MIME("text/plain"), c) == "(5.0e-71 ± 5.00001e-71)_com - im*(1.0e-70 ± 0.0)_com" + @test sprint(show, MIME("text/plain"), c) == "(5.0e-71 ± 5.0e-71)_com - im*(1.0e-70 ± 0.0)_com" end @testset "No decorations" begin @@ -235,7 +229,7 @@ setprecision(BigFloat, 256) do @test sprint(show, MIME("text/plain"), a) == "(1.0 ± 1.0) + im*(1.0 ± 0.0)" @test sprint(show, MIME("text/plain"), b) == "(1.0 ± 1.0) - im*(1.0 ± 0.0)" - @test sprint(show, MIME("text/plain"), c) == "(5.0e-71 ± 5.00001e-71) - im*(1.0e-70 ± 0.0)" + @test sprint(show, MIME("text/plain"), c) == "(5.0e-71 ± 5.0e-71) - im*(1.0e-70 ± 0.0)" end end end diff --git a/test/interval_tests/set_operations.jl b/test/interval_tests/set_operations.jl index eed5e45a..37648024 100644 --- a/test/interval_tests/set_operations.jl +++ b/test/interval_tests/set_operations.jl @@ -57,4 +57,4 @@ end @test interval_diff(interval(1, 10), interval(-1, 14)) == [] @test interval_diff(interval(1, 10), interval(1, 10)) == [] -end \ No newline at end of file +end