Skip to content

Commit c7602f4

Browse files
authored
Merge pull request #461 from lucaferranti/lf/invalid_as_empty
return empty interval and warning when an invalid input is given
2 parents 65ca77f + e24bf6e commit c7602f4

File tree

8 files changed

+79
-59
lines changed

8 files changed

+79
-59
lines changed

src/decorations/intervals.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ DecoratedInterval(I::Interval{T}, d::DECORATION) where T<:AbstractFloat =
4747
DecoratedInterval{T}(I, d)
4848

4949
function DecoratedInterval(a::T, b::T, d::DECORATION) where T<:Real
50-
a > b && return nai(T)
50+
is_valid_interval(a, b) || return nai(T)
5151
DecoratedInterval(Interval(a,b), d)
5252
end
5353

@@ -63,7 +63,7 @@ DecoratedInterval(a::T, b::S, d::DECORATION) where {T<:Real, S<:Real} =
6363
DecoratedInterval(I::Interval) = DecoratedInterval(I, decoration(I))
6464

6565
function DecoratedInterval(a::T, b::T) where T<:Real
66-
a > b && return nai(T)
66+
is_valid_interval(a, b) || return nai(T)
6767
DecoratedInterval(Interval(a,b))
6868
end
6969

src/intervals/intervals.jl

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ struct Interval{T<:Real} <: AbstractInterval{T}
2525
new(a, b)
2626

2727
else
28-
throw(ArgumentError("Interval of form [$a, $b] not allowed. Must have a ≤ b to construct interval(a, b)."))
28+
@warn "Invalid input, empty interval is returned"
29+
return new(T(Inf), T(-Inf))
2930
end
3031

3132
end
@@ -79,20 +80,10 @@ function is_valid_interval(a::Real, b::Real)
7980
# println("isvalid()")
8081

8182
if isnan(a) || isnan(b)
82-
if isnan(a) && isnan(b)
83-
return true
84-
else
85-
return false
86-
end
83+
return false
8784
end
8885

89-
if a > b
90-
if isinf(a) && isinf(b)
91-
return true # empty interval = [∞,-∞]
92-
else
93-
return false
94-
end
95-
end
86+
a > b && return false
9687

9788
if a == Inf || b == -Inf
9889
return false
@@ -104,11 +95,12 @@ end
10495
"""
10596
interval(a, b)
10697
107-
`interval(a, b)` checks whether [a, b] is a valid `Interval`, which is the case if `-∞ <= a <= b <= ∞`, using the (non-exported) `is_valid_interval` function. If so, then an `Interval(a, b)` object is returned; if not, then an error is thrown.
98+
`interval(a, b)` checks whether [a, b] is a valid `Interval`, using the (non-exported) `is_valid_interval` function. If so, then an `Interval(a, b)` object is returned; if not, a warning is printed and the empty interval is returned.
10899
"""
109-
function interval(a::Real, b::Real)
100+
function interval(a::T, b::S) where {T<:Real, S<:Real}
110101
if !is_valid_interval(a, b)
111-
throw(ArgumentError("`[$a, $b]` is not a valid interval. Need `a ≤ b` to construct `interval(a, b)`."))
102+
@warn "Invalid input, empty interval is returned"
103+
return emptyinterval(promote_type(T, S))
112104
end
113105

114106
return Interval(a, b)
@@ -141,21 +133,32 @@ include("complex.jl")
141133
# Syntax for intervals
142134

143135
function ..(a::T, b::S) where {T, S}
144-
interval(atomic(Interval{T}, a).lo, atomic(Interval{S}, b).hi)
136+
if !is_valid_interval(a, b)
137+
@warn "Invalid input, empty interval is returned"
138+
return emptyinterval(promote_type(T, S))
139+
end
140+
Interval(atomic(Interval{T}, a).lo, atomic(Interval{S}, b).hi)
145141
end
146142

147143
function ..(a::T, b::Irrational{S}) where {T, S}
144+
if !is_valid_interval(a, b)
145+
@warn "Invalid input, empty interval is returned"
146+
return emptyinterval(promote_type(T, Irrational{S}))
147+
end
148148
R = promote_type(T, Irrational{S})
149-
interval(atomic(Interval{R}, a).lo, R(b, RoundUp))
149+
Interval(atomic(Interval{R}, a).lo, R(b, RoundUp))
150150
end
151151

152152
function ..(a::Irrational{T}, b::S) where {T, S}
153+
if !is_valid_interval(a, b)
154+
@warn "Invalid input, empty interval is returned"
155+
return emptyinterval(promote_type(Irrational{T}, S))
156+
end
153157
R = promote_type(Irrational{T}, S)
154-
return interval(R(a, RoundDown), atomic(Interval{R}, b).hi)
158+
return Interval(R(a, RoundDown), atomic(Interval{R}, b).hi)
155159
end
156160

157161
function ..(a::Irrational{T}, b::Irrational{S}) where {T, S}
158-
R = promote_type(Irrational{T}, Irrational{S})
159162
return interval(a, b)
160163
end
161164

src/intervals/macros.jl

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
# This file is part of the IntervalArithmetic.jl package; MIT licensed
22

3-
"""The `@interval` macro is the main method to create an interval.
4-
It converts each expression into a narrow interval that is guaranteed to contain the true value passed by the user in the one or two expressions passed to it.
3+
"""The `@interval` macro converts an expression into a narrow interval that is guaranteed to contain the true value of the expression.
54
When passed two expressions, it takes the hull of the resulting intervals
65
to give a guaranteed containing interval.
76
87
Examples:
98
```
10-
@interval(0.1)
11-
12-
@interval(0.1, 0.2)
13-
14-
@interval(1/3, 1/6)
9+
@interval sin(0.1) + cos(0.2)
10+
```
1511
16-
@interval(1/3^2)
12+
is equivalent to
1713
```
14+
sin(0.1..0.1) + cos(0.2..0.2)
15+
```
16+
17+
NOTE! `@interval` should be used only to create intervals from an expression, as in the example
18+
before. To construct an interval from single numbers, the `..` is preferred, e.g. `0.1..0.2`
1819
"""
1920
macro interval(expr1)
2021
make_interval(:(parameters.precision_type), expr1, ())

src/intervals/special.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ larger than the upper one."""
99
emptyinterval(::Type{T}) where T<:Real = Interval{T}(Inf, -Inf)
1010
emptyinterval(x::Interval{T}) where T<:Real = emptyinterval(T)
1111
emptyinterval() = emptyinterval(precision(Interval)[1])
12+
emptyinterval(::Type{<:Integer}) = emptyinterval(Float64)
1213
const= emptyinterval(Float64)
1314

1415
isempty(x::Interval) = x.lo == Inf && x.hi == -Inf
@@ -20,6 +21,7 @@ const ∞ = Inf
2021
entireinterval(::Type{T}) where T<:Real = Interval{T}(-Inf, Inf)
2122
entireinterval(x::Interval{T}) where T<:Real = entireinterval(T)
2223
entireinterval() = entireinterval(precision(Interval)[1])
24+
entireinterval(::Type{<:Integer}) = entireinterval(Float64)
2325

2426
isentire(x::Interval) = x.lo == -Inf && x.hi == Inf
2527
isinf(x::Interval) = isentire(x)

test/decoration_tests/decoration_tests.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,15 @@ let b
5454
b = @decorated 3 4
5555

5656
@test dist(a, b) == 2.0
57+
58+
# invalid input
59+
@test isnai(@decorated(3, 1, com))
60+
@test isnai(@decorated(3, 1))
61+
@test isnai(@decorated(Inf, Inf))
62+
@test isnai(@decorated(-Inf, -Inf))
63+
@test isnai(@decorated(NaN, 3))
64+
@test isnai(@decorated(3, NaN))
65+
@test isnai(@decorated(NaN, NaN))
5766
end
5867

5968
@testset "Convert string to DecoratedInterval" begin

test/interval_tests/consistency.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ setprecision(Interval, Float64)
3939
@test @biginterval(1, Inf) == Interval{BigFloat}(1.0, Inf)
4040
@test @biginterval(-Inf, 1) == Interval{BigFloat}(-Inf, 1.0)
4141
@test @interval(-Inf, Inf) == entireinterval(Float64)
42+
@test entireinterval(Int) == entireinterval(Float64)
4243
@test emptyinterval(Rational{Int}) ==
44+
@test emptyinterval(Int) ==
4345

4446
@test 1 == zero(a)+one(b)
4547
@test Interval(0,1) + emptyinterval(a) == emptyinterval(a)
@@ -380,10 +382,10 @@ setprecision(Interval, Float64)
380382
@test interval(1, 2) == Interval(1, 2)
381383

382384
@test inf(Interval(3, 2)) == 3
383-
@test_throws ArgumentError interval(3, 2)
385+
@test_logs (:warn,) @test isempty(interval(3, 2))
384386

385387
@test sup(Interval(Inf, Inf)) == Inf
386-
@test_throws ArgumentError interval(Inf, Inf)
388+
@test_logs (:warn,) @test isempty(interval(Inf, Inf))
387389

388390
end
389391

test/interval_tests/construction.jl

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -69,20 +69,20 @@ const eeuler = Base.MathConstants.e
6969

7070
# Disallowed conversions with a > b
7171

72-
@test_throws ArgumentError interval(2, 1)
73-
@test_throws ArgumentError interval(big(2), big(1))
74-
@test_throws ArgumentError interval(BigInt(1), 1//10)
75-
@test_throws ArgumentError interval(1, 0.1)
76-
@test_throws ArgumentError interval(big(1), big(0.1))
77-
78-
@test_throws ArgumentError @interval(2, 1)
79-
@test_throws ArgumentError @interval(big(2), big(1))
80-
@test_throws ArgumentError @interval(big(1), 1//10)
81-
@test_throws ArgumentError @interval(1, 0.1)
82-
@test_throws ArgumentError @interval(big(1), big(0.1))
83-
@test_throws ArgumentError interval(Inf)
84-
@test_throws ArgumentError interval(-Inf, -Inf)
85-
@test_throws ArgumentError interval(Inf, Inf)
72+
@test_logs (:warn,) @test isempty(interval(2, 1))
73+
@test_logs (:warn,) @test isempty(interval(big(2), big(1)))
74+
@test_logs (:warn,) @test isempty(interval(BigInt(1), 1//10))
75+
@test_logs (:warn,) @test isempty(interval(1, 0.1))
76+
@test_logs (:warn,) @test isempty(interval(big(1), big(0.1)))
77+
78+
@test_logs (:warn,) @test isempty(@interval(2, 1))
79+
@test_logs (:warn,) @test isempty(@interval(big(2), big(1)))
80+
@test_logs (:warn,) @test isempty(@interval(big(1), 1//10))
81+
@test_logs (:warn,) @test isempty(@interval(1, 0.1))
82+
@test_logs (:warn,) @test isempty(@interval(big(1), big(0.1)))
83+
@test_logs (:warn,) @test isempty(interval(Inf))
84+
@test_logs (:warn,) @test isempty(interval(-Inf, -Inf))
85+
@test_logs (:warn,) @test isempty(interval(Inf, Inf))
8686

8787
# Conversion to Interval without type
8888
@test convert(Interval, 1) == Interval(1.0)
@@ -237,10 +237,12 @@ end
237237
a = big(0.1)..2
238238
@test typeof(a) == Interval{BigFloat}
239239

240-
@test_throws ArgumentError 2..1
241-
@test_throws ArgumentError π..1
242-
@test_throws ArgumentError π..eeuler
243-
@test_throws ArgumentError 4..π
240+
@test_logs (:warn, ) @test isempty(2..1)
241+
@test_logs (:warn, ) @test isempty..1)
242+
@test_logs (:warn, ) @test isempty..eeuler)
243+
@test_logs (:warn, ) @test isempty(4..π)
244+
@test_logs (:warn, ) @test isempty(NaN..3)
245+
@test_logs (:warn, ) @test isempty(3..NaN)
244246
@test 1..π == Interval(1, π)
245247
end
246248

@@ -292,9 +294,10 @@ end
292294
end
293295

294296
# issue 192:
295-
@testset "Disallow a single NaN in an interval" begin
296-
@test_throws ArgumentError interval(NaN, 2)
297-
@test_throws ArgumentError interval(Inf, NaN)
297+
@testset "Disallow NaN in an interval" begin
298+
@test_logs (:warn, ) @test isempty(interval(NaN, 2))
299+
@test_logs (:warn, ) @test isempty(interval(Inf, NaN))
300+
@test_logs (:warn, ) @test isempty(interval(NaN, NaN))
298301
end
299302

300303
# issue 206:
@@ -342,7 +345,7 @@ end
342345
end
343346

344347
@testset "a..b with a > b" begin
345-
@test_throws ArgumentError 3..2
348+
@test_logs (:warn,) @test isempty(3..2)
346349
end
347350

348351
@testset "Hashing of Intervals" begin
@@ -374,7 +377,7 @@ import IntervalArithmetic: force_interval
374377
@test force_interval(4, Inf) == Interval(4, Inf)
375378
@test force_interval(Inf, 4) == Interval(4, Inf)
376379
@test force_interval(Inf, -Inf) == Interval(-Inf, Inf)
377-
@test_throws ArgumentError force_interval(NaN, 3)
380+
@test_logs (:warn,) @test isempty(force_interval(NaN, 3))
378381
end
379382

380383
@testset "Zero interval" begin

test/interval_tests/numeric.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ setprecision(Interval, Float64)
1111
y = 4..5
1212
a = 3
1313
b = 12
14-
14+
1515
@test sqrt(sum(x.^2 .+ y.^2)) == 5..13
1616

1717
for i in 1:20
@@ -24,7 +24,7 @@ setprecision(Interval, Float64)
2424
end
2525

2626
a = 4
27-
b = 5
27+
b = 5
2828
for i in 1:20
2929
@test y.+i == (a+i)..(b+i)
3030
end
@@ -115,7 +115,7 @@ end
115115
@test Interval(1,2) ^ -3 == Interval(1/8, 1.0)
116116
@test Interval(0,3) ^ -3 == @interval(1/27, Inf)
117117
@test Interval(-1,2) ^ -3 == entireinterval()
118-
@test_throws ArgumentError interval(-1, -2) ^ -3 # wrong way round
118+
@test_logs (:warn, ) @test isempty(interval(-1, -2) ^ -3)
119119
@test Interval(-3,2) ^ (3//1) == Interval(-27, 8)
120120
@test Interval(0.0) ^ 1.1 == Interval(0, 0)
121121
@test Interval(0.0) ^ 0.0 == emptyinterval()
@@ -432,4 +432,4 @@ end
432432
@test nthroot(Interval{BigFloat}(-27, 27), -3) == Interval{BigFloat}(-Inf, Inf)
433433
@test nthroot(Interval{BigFloat}(-81, -16), -4) ==
434434
@test nthroot(Interval{BigFloat}(-81, -16), 1) == Interval{BigFloat}(-81, -16)
435-
end
435+
end

0 commit comments

Comments
 (0)