Philosophy

The goal of the Interval type is to be directly used to replace floating point number in arbitrary julia code, such that in any calculation, the resulting intervals are guaranteed to bound the true image of the starting intervals.

So, essentially, we would like Interval to act as numbers and the julia ecosystem has evolved to use Real as the default supertype for numerical types that are not complex. Therefore, to ensure the widest compatiblity, our Interval type must be a subtype of Real.

Then, for any function f(x::Real), we want the following to hold for all real x in the interval X (note that it holds for all real numbers in X, even those that can not be represented as floating point numbers):

\[f(x) \in f(X), \qquad \forall x \in X.\]

At first glance, this is reasonable: all arithmetic operations are well-defined for both real numbers and intervals, therefore we can use multiple dispatch to define the interval behavior of operations such has +, /, sin or log. Then a code written for Reals can be used as is with Intervals.

However, being a Real means way more than just being compatible with arithmetic operations. Reals are also expected to

  1. Be compatible with any other Number through promotion.
  2. Support comparison operations, such as == or <.
  3. Act as a container of a single element, e.g. collect(x) returns a 0-dimensional array containing x.

Each of those points lead to specific design choice for IntervalArithmetic.jl, choices that we detail below.

Compatibility with other Numbers

In julia it is expected that 1 + 2.2 silently promoted the integer 1 to a Float64 to be able to perform the addition. Following this logic, it means that 0.1 + interval(2.2, 2.3) should silently promote 0.1 to an interval.

However, in this case we can not guarantee that 0.1 is known exactly, because we do not know how it was produced in the first place. Following the julia convention is thus in contradiction with providing guaranteed result.

In this case, we choose to be mostly silent, the information that a non-interval of unknown origin is recorded in the NG flag, but the calculation is not interrupted, and no warning is printed.

For convenience, we provide the ExactReal and @exact macro to allow to explicitly mark a number as being exact, and not produce the NG flag when mixed with intervals.

Comparison operators

We can extend our above definition of the desired behavior for two real numbers x and y, and their respective intervals X and Y. With this, we want to have, for any functionf, for all x in X and all y in Y, $math f(x, y) \in f(X, Y), \qquad \forall x \in X, y \in Y.$

With this in mind, an operation such as == can easily be defined for intervals

  1. If the intervals are disjoints (X ∩ Y === ∅), then X == Y is [false].
  2. If the intervals both contain a single element, and that element is the same for both, X == Y is [true].
  3. Otherwise, we can not conclude anything, and X == Y must be [false, true].

Not that we use intervals in all case, because, according to our definition, the true result must be contained in the returned interval. However, this is not convenient, as any if statement would error when used with an interval. Instead, we have opted to return respectively false and true for cases 1 and 2, and to immediately error otherwise.

In this way, we can return a more informative error, but we only do it when the result is ambiguous.

This has a clear cost, however, in that some expected behaviors do not hold. For example, an Interval is not equal to itself.

[1.0, 2.0]_com

julia> X == X
ERROR: ArgumentError: `==` is purposely not supported when the intervals are overlapping. See instead `isequal_interval`
Stacktrace:
 [1] ==(x::Interval{Float64}, y::Interval{Float64})
   @ IntervalArithmetic C:\Users\Kolaru\.julia\packages\IntervalArithmetic\XjBhk\src\intervals\real_interface.jl:86
 [2] top-level scope
   @ REPL[6]:1.

Intervals as sets

We have taken the perspective to always let Intervals act as if they were numbers.

But they are also sets of numbers, and it would be nice to use all set operations defined in julia on them.

However, Real are also sets. For example, the following is valid

julia> 3 in 3
true

Then what should 3 in interval(2, 6) do?

For interval as a set, it is clearly true. But for intervals as a subtype of Real this is equivalent to

3 == interval(2, 6)

which must either be false (they are not the same things), or error as the result can not be established.

To be safe, we decided to go one step further and disable all set operations from julia Base on intervals. These operations can instead be performed with the specific *_interval function, for example in_interval as a replacement for in, except for setdiff. We can not meaningfully define the set difference of two intervals, because our intervals are always closed, while the result of setdiff can be open.

Summary

FunctionsBehaviorNote
Arithmetic operations+, -, *, /, ^Interval extensionProduce the NG flag when mixed with non-interval
Other numeric functionsin, exp, sqrt, etc.Interval extension
Boolean operations==, <, <=, iszero, isnan, isinteger, isfiniteError if the result can not be guaranteed to be either true or falseSee isequal_interval to test equality of intervals, and isbounded to test the finiteness of the elements
Set operationsin, issubset, isdisjoint, issetequal, isempty, union, intersectAlways errorUse the *_interval function instead (e.g. in_interval)
Exceptions, setdiffAlways errorNo meaningful interval extension