Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions src/IntervalSets.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module IntervalSets
using Base: @pure
import Base: eltype, convert, show, in, length, isempty, isequal, issubset, ==, hash,
union, intersect, minimum, maximum, extrema, range, ⊇
import Base.Broadcast: broadcasted

using Statistics
import Statistics: mean
Expand Down
57 changes: 57 additions & 0 deletions src/interval.jl
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,63 @@ end
ClosedInterval{T}(i::AbstractUnitRange{I}) where {T,I<:Integer} = ClosedInterval{T}(minimum(i), maximum(i))
ClosedInterval(i::AbstractUnitRange{I}) where {I<:Integer} = ClosedInterval{I}(minimum(i), maximum(i))

for op in (+, -, *)
@eval begin
broadcasted(::typeof($op), i::AbstractInterval, x) = $op.(i, (x..x))
broadcasted(::typeof($op), x, i::AbstractInterval) = $op.((x..x), i)
end
end

broadcasted(::typeof(+), d1::AbstractInterval, d2::AbstractInterval) =
Interval{
isleftclosed(d1) && isleftclosed(d2) ? :closed : :open,
isrightclosed(d1) && isrightclosed(d2) ? :closed : :open,
}((endpoints(d1) .+ endpoints(d2))...)

broadcasted(::typeof(-), d1::AbstractInterval, d2::AbstractInterval) =
Interval{
isleftclosed(d1) && isrightclosed(d2) ? :closed : :open,
isrightclosed(d1) && isleftclosed(d2) ? :closed : :open,
}((endpoints(d1) .- reverse(endpoints(d2)))...)

@inline foldlargs(op, x) = x
@inline foldlargs(op, x1, x2, xs...) = foldlargs(op, op(x1, x2), xs...)
@inline extremaby(f, x, xs...) =
foldlargs((x, x), xs...) do (min, max), x
if f(min) > f(x)
(x, max)
elseif f(max) < f(x)
(min, x)
else
(min, max)
end
end

_value(::Val{x}) where x = x

function broadcasted(::typeof(*), d1::AbstractInterval, d2::AbstractInterval)
l1, r1 = endpoints(d1)
l2, r2 = endpoints(d2)
candidates = (
(l1 * l2, Val(isleftclosed(d1) && isleftclosed(d2) ? :closed : :open)),
(l1 * r2, Val(isleftclosed(d1) && isrightclosed(d2) ? :closed : :open)),
(r1 * l2, Val(isrightclosed(d1) && isleftclosed(d2) ? :closed : :open)),
(r1 * r2, Val(isrightclosed(d1) && isrightclosed(d2) ? :closed : :open)),
)
(left, L), (right, R) = extremaby(first, candidates...)
return Interval{_value(L), _value(R)}(left, right)
end

broadcasted(::typeof(/), d1::AbstractInterval, d2::AbstractInterval) =
MethodError(broadcasted, (/, d1, d2))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

throw?

# Defining this to be a method error so that the `x` below is not of
# type `AbstractInterval`.

broadcasted(::typeof(/), i::AbstractInterval, x) =
Interval{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps we want

coretype(::Interval{L,R}) where {L,R} = Interval{L,R}

Then this could just be

broadcasted(::typeof(/), i::AbstractInterval, x) = coretype(i)(endpoints(i) ./ x)...)

isleftclosed(i) ? :closed : :open,
isrightclosed(i) ? :closed : :open,
}((endpoints(i) ./ x)...)


Base.promote_rule(::Type{Interval{L,R,T1}}, ::Type{Interval{L,R,T2}}) where {L,R,T1,T2} = Interval{L,R,promote_type(T1, T2)}
Expand Down
29 changes: 29 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -693,4 +693,33 @@ struct IncompleteInterval <: AbstractInterval{Int} end
@test_throws ErrorException endpoints(I)
@test_throws ErrorException closedendpoints(I)
end

@testset "Interval arithmetic" begin
@test (1..2) .+ 1 == 2..3
@test 1 .+ (1..2) == 2..3
@test (1..2) .+ (3..4) == 4..6
@test (1..2) .+ (3..5) == 4..7
@test Interval{:open,:closed}(1, 2) .+ Interval{:closed,:open}(3, 4) ==
Interval{:open,:open}(4, 6)

@test (1..2) .- 1 == 0..1
@test 1 .- (1..2) == -1 .. 0
@test (1..2) .- (3..4) == -3 .. -1
@test (1..2) .- (3..5) == -4 .. -1
@test Interval{:open,:closed}(1, 2) .- Interval{:closed,:open}(3, 4) ==
Interval{:open,:closed}(-3, -1)

@test 3 .* (1..2) == 3..6
@test (1..2) .* 3 == 3..6
@test (1 .. 2) .* (3 .. 4) == 3 .. 8
@test (-1 .. 2) .* (3 .. 4) == -4 .. 8
@test (1 .. -2) .* (3 .. 4) == -8 .. 4
@test (1 .. 2) .* (-3 .. 4) == -6 .. 8
@test (1 .. 2) .* (3 .. -4) == -8 .. 6
@test Interval{:open,:closed}(1, 2) .* Interval{:closed,:open}(3, 4) ==
Interval{:open,:open}(3, 8)

@test (2..4) ./ 2 == 1..2
@test Interval{:open,:closed}(2, 4) ./ 2 == Interval{:open,:closed}(1, 2)
end
end