Skip to content
1 change: 1 addition & 0 deletions src/IntervalArithmetic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import Base:
in, zero, one, eps, typemin, typemax, abs, abs2, real, min, max,
sqrt, exp, log, sin, cos, tan, cot, inv, cbrt, csc, hypot, sec,
exp2, exp10, log2, log10,
mod,
asin, acos, atan,
sinh, cosh, tanh, coth, csch, sech, asinh, acosh, atanh, sinpi, cospi,
union, intersect, isempty,
Expand Down
10 changes: 10 additions & 0 deletions src/intervals/functions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,13 @@ function nthroot(a::Interval{T}, n::Integer) where T
b = nthroot(bigequiv(a), n)
return convert(Interval{T}, b)
end

"""
Calculate `x mod y` where `x` is an interval and `y` is a positive divisor.
"""
function mod(x::Interval, y::Real)
@assert y > zero(y) "modulo is currently implemented only for a positive divisor."
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to extend this to have strictly negative y? I understand that having 0 ∈ y complicates things....

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated for strictly negative divisor. Hope its correct.

Copy link
Member

Choose a reason for hiding this comment

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

The implementation seems to me correct if we have y::AbstractFloat (perhaps also including Rationals, but let me forget about them now), which corresponds to the tests.

However, if y::Interval (and we have Interval <: Real) then there this function throws errors: e.g., try mod(1..2, 1..2), which I think should return Interval(0.0, 2.0). In this case things are subtle, because y != zero(y) is true for [-1, 1], but that interval is not strictly positive or negative. Also, Interval(zero(y), y) causes the error mentioned above. I guess this is the reason that mod has two methods in #178.

My suggestion is either restrict y::AbstractFloat, or include a new method where y::Interval.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is my mistake. My intention was to constrain y NOT to be Interval. I've not realized 1..3 isa Real -> true. However, If I constraint it to y::AbstractFloat it does not accept integers for example because 1 isa AbstractFloat -> false, right?

Copy link
Member

Choose a reason for hiding this comment

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

you could do Union{AbstractFloat, Integer} to accept both integers and floats but no intervals (similar to add rationals and irrationals).

Can the method be generalized to the case of y interval?

Copy link
Member

Choose a reason for hiding this comment

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

actually forget about the union, a better suggestion would be to define

mod(x::Real, y::Interval) = throw(ArgumentError("mod not defined for second argument interval"))
mod(x::Interval, y::Interval) = throw(ArgumentError("mod not defined for second argument interval"))

Copy link
Member

Choose a reason for hiding this comment

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

mod can indeed be generalized for y::Interval. It's tricky with respect of having zero within the interval, which is part of the reason I was suggesting to have either a strictly positive or negative y. Actually, a motivating example would be to have y::Irrational, or actually any (mathematical) real number which is not exactly representable as a Float64. Note that #178 includes such an implementation, though it does not include the restriction of strictly positive/negative intervals. The subtleties related to zero at the end are related to the division: y appears in the denominator.

Copy link
Member

Choose a reason for hiding this comment

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

I unresolved this conversation, so it is easy to track the discussion...

Copy link
Member

Choose a reason for hiding this comment

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

fwiw in octave

>> mod(infsup(-3, 2), infsup(-1, 0.5))
ans = [-1, +0.5]

>> mod(infsup(-3, 2), infsup(0, 0))
ans = [Empty]

>> mod(infsup(-3, 2), infsup(0, 3))
ans = [0, 3]

>> mod(infsup(-3, 2), infsup(1, 3))
ans = [0, 3]

>> mod(infsup(-3, 2), infsup(-3, 2))
ans = [-3, +2]

division = x / y
fl = floor(division)
fl.lo < fl.hi ? Interval(zero(y), y) : y * (division - fl)
end
27 changes: 27 additions & 0 deletions test/interval_tests/numeric.jl
Original file line number Diff line number Diff line change
Expand Up @@ -434,3 +434,30 @@ end
@test nthroot(Interval{BigFloat}(-81, -16), -4) == ∅
@test nthroot(Interval{BigFloat}(-81, -16), 1) == Interval{BigFloat}(-81, -16)
end

# approximation used for testing (not to rely on ≈ for intervals)
# ⪆(x, y) = (x ≈ y) && (x ⊇ y)
⪆(x::Interval, y::Interval) = x.lo ≈ y.lo && x.hi ≈ y.hi && x ⊇ y

@testset "`mod`" begin
r = 0.0625
x = r..(1+r)
@test mod(x, 1) == mod(x, 1.0) == 0..1
@test mod(x, 2) == mod(x, 2.0) ⪆ x
@test mod(x, 2.5) ⪆ x
@test mod(x, 0.5) == 0..0.5

x = (-1+r) .. -r
@test mod(x, 1) == mod(x, 1.0) ⪆ 1+x
@test mod(x, 2) == mod(x, 2.0) ⪆ 2+x
@test mod(x, 2.5) ⪆ 2.5+x
@test mod(x, 0.5) == 0..0.5

x = -r .. 1-r
@test mod(x, 1) == mod(x, 1.0) == 0..1
@test mod(x, 2) == mod(x, 2.0) == 0..2
@test mod(x, 2.5) == 0..2.5
@test mod(x, 0.5) == 0..0.5

@test_throws AssertionError mod(x, -1)
end