Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions src/intervals/exact_literals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,57 @@ for f ∈ (:+, :-, :*, :/, :\, :^)
@eval Base.$f(x::ExactReal, y::BareInterval) = $f(promote(x, y)...)
end

# These specializations improve efficiency, but always agree precisely with
# the promoting fallbacks above. Several circumstances fall back to promotion: a
# non-finite point (whose thin interval is not even valid), an unbounded interval
# times a point (`0 * ∞` is flavor dependent), and division by zero (likewise).
# The type parameter is bounded so that these methods are more specific than the
# generic `Base.$f(::ExactReal, ::BareInterval)`, whose own parameters are bounded
# by `Real` and `NumTypes`.

function Base.:+(x::BareInterval{T}, y::ExactReal{T}) where {T<:NumTypes}
v = y.value
isfinite(v) || return +(promote(x, y)...)
isempty_interval(x) && return x
return @round(T, inf(x) + v, sup(x) + v)
end
Base.:+(x::ExactReal{T}, y::BareInterval{T}) where {T<:NumTypes} = y + x

function Base.:-(x::BareInterval{T}, y::ExactReal{T}) where {T<:NumTypes}
v = y.value
isfinite(v) || return -(promote(x, y)...)
isempty_interval(x) && return x
return @round(T, inf(x) - v, sup(x) - v)
end

function Base.:-(x::ExactReal{T}, y::BareInterval{T}) where {T<:NumTypes}
v = x.value
isfinite(v) || return -(promote(x, y)...)
isempty_interval(y) && return y
return @round(T, v - sup(y), v - inf(y))
end

function Base.:*(x::BareInterval{T}, y::ExactReal{T}) where {T<:NumTypes}
v = y.value
isempty_interval(x) && return x # `isbounded` is true for the empty interval
(isfinite(v) & isbounded(x)) || return *(promote(x, y)...)
signbit(v) && return @round(T, v * sup(x), v * inf(x))
return @round(T, v * inf(x), v * sup(x))
end
Base.:*(x::ExactReal{T}, y::BareInterval{T}) where {T<:NumTypes} = y * x

function Base.:/(x::BareInterval{T}, y::ExactReal{T}) where {T<:NumTypes}
v = y.value
isempty_interval(x) && return x
(isfinite(v) & !iszero(v)) || return /(promote(x, y)...)
signbit(v) && return @round(T, sup(x) / v, inf(x) / v)
return @round(T, inf(x) / v, sup(x) / v)
end

# `x \ y` is `y / x`; the reverse case is a point divided by an interval, which
# has no shortcut — the zero-crossing tree of `/` is the whole computation.
Base.:\(x::ExactReal{T}, y::BareInterval{T}) where {T<:NumTypes} = y / x

"""
has_exact_display(x::Real)

Expand Down
7 changes: 5 additions & 2 deletions src/intervals/intervals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ include("construction.jl")
include("parsing.jl")
include("real_interface.jl")
export numtype
include("exact_literals.jl")
export ExactReal, exact, @exact, has_exact_display

# Rounding
include("rounding.jl")

# after `rounding.jl`: the interval operations on `ExactReal` round their
# endpoints with `@round`
include("exact_literals.jl")
export ExactReal, exact, @exact, has_exact_display

# Flavor
include("flavor.jl")

Expand Down
56 changes: 56 additions & 0 deletions test/interval_tests/exact_literals.jl
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,59 @@
@test val2 == 3.0
end
end

@testset "Exact literals with bare intervals" begin
# `+`, `-`, `*`, `/` and `\` on a matching number type bypass the promotion to
# a thin interval, so they must be more specific than the generic methods and
# must return exactly what those return.
specialized(f, S, R) = which(f, Tuple{S,R}).sig isa UnionAll
@test all(f -> specialized(f, BareInterval{Float64}, ExactReal{Float64}), (+, -, *, /))
@test all(f -> specialized(f, ExactReal{Float64}, BareInterval{Float64}), (+, -, *, \))

# ... and the mixed number types must not, since the promotion is where the
# `ExactReal` gets rounded to the interval's number type.
@test !specialized(*, BareInterval{Float64}, ExactReal{Int})

viapromotion(f, x, y) = f(promote(x, y)...)
samebits(x::BareInterval, y::BareInterval) = (inf(x) === inf(y)) & (sup(x) === sup(y))

for T ∈ (Float64, Float32, Rational{Int})
vals = T <: Rational ?
(zero(T), one(T), -one(T), T(1//2), T(-5//3)) :
(zero(T), -zero(T), one(T), -one(T), T(0.5), T(-2.5), T(0.1), T(-0.1),
floatmax(T), floatmin(T))
ivs = (bareinterval(T, 1, 2), bareinterval(T, -2, -1), bareinterval(T, -1, 2),
bareinterval(T, 0, 1), bareinterval(T, -1, 0), bareinterval(T, 0, 0),
bareinterval(T(1//10), T(1//10)), bareinterval(T, -Inf, 2),
bareinterval(T, 3, Inf), entireinterval(BareInterval{T}),
emptyinterval(BareInterval{T}))
for v ∈ vals, x ∈ ivs
k = exact(v)
@test samebits(x + k, viapromotion(+, x, k))
@test samebits(k + x, viapromotion(+, k, x))
@test samebits(x - k, viapromotion(-, x, k))
@test samebits(k - x, viapromotion(-, k, x))
@test samebits(x * k, viapromotion(*, x, k))
@test samebits(k * x, viapromotion(*, k, x))
@test samebits(x / k, viapromotion(/, x, k))
@test samebits(k \ x, viapromotion(\, k, x))
end
end

# A non-finite `ExactReal` has no thin interval; both routes warn and return
# the empty interval.
let x = bareinterval(1.0, 2.0)
for v ∈ (Inf, -Inf, NaN)
@test isempty_interval(@test_logs (:warn,) x + exact(v))
@test isempty_interval(@test_logs (:warn,) x - exact(v))
@test isempty_interval(@test_logs (:warn,) exact(v) - x)
@test isempty_interval(@test_logs (:warn,) x * exact(v))
@test isempty_interval(@test_logs (:warn,) x / exact(v))
end
end

# Mixed number types still round through the promotion.
@test isequal_interval(bareinterval(1.0, 2.0) * exact(2), bareinterval(2.0, 4.0))
@test isequal_interval(bareinterval(1.0, 2.0) + exact(1//3),
bareinterval(1.0, 2.0) + bareinterval(Float64, 1//3))
end
Loading