The library provides two interval types. The first one is BareInterval, corresponding to a basic implementation of intervals, stored by their infimum and supremum. The second type is Interval and builds on top of bare intervals, with the additional fields decoration and isguaranteed. See the sections below.

julia> using IntervalArithmetic
julia> setdisplay(:full) # print the interval in fullDisplay options: - format: full - decorations: false - significant digits: 3 (ignored)
julia> bareinterval(1, π) # `bareinterval(Float64, 1, π)`BareInterval{Float64}(1.0, 3.1415926535897936)
julia> interval(1, π) # `interval(Float64, 1, π)`, interval decorated with `com` (common)Interval{Float64}(1.0, 3.1415926535897936, com)

Therefore, we strongly recommend the use of Interval over BareInterval to better track the effect of functions according to the IEEE Standard 1788-2015 specifications. For instance, taking the square root of an interval discards the negative part of the interval, without any notice for bare intervals:

julia> sqrt(bareinterval(-1, 1)) # `sqrt(bareinterval(0, 1))`BareInterval{Float64}(0.0, 1.0)
julia> sqrt(interval(-1, 1)) # interval decorated with `trv` (trivial)Interval{Float64}(0.0, 1.0, trv)

Decorations

A decoration is a label that indicates the status of a given interval. Decorated intervals provide valuable information on the result of evaluating a function on an initial interval.

Upon the application of a function $f$ on an interval $x$, the resulting interval $f(x)$ has either one of the following decorations:

  • com (common): $x$ is a closed, bounded, non-empty subset of the domain of $f$, $f$ is continuous on the interval $x$, and $f(x)$ is bounded.

  • dac (defined and continuous): $x$ is a non-empty subset of the domain of $f$, and $f$ is continuous on $x$.

  • def (defined): $x$ is a non-empty subset of the domain of $f$; in other words, $f$ is defined at each point of $x$.

  • trv (trivial): $f(x)$ carries no meaningful information.

  • ill (ill-formed): $f(x)$ is Not an Interval (NaI).

Each decoration is paired with an integer as follows: ill = 0, trv = 1, def = 2, dac = 3 and com = 4. Then, decorations degrade according to the propagation order com > dac > def > trv > ill.

One can specify a decoration when constructing intervals. Otherwise, the interval is initialised with a decoration according to the underlying bare interval:

  • com: non-empty and bounded.

  • dac: unbounded.

  • trv: empty.

  • ill: NaI.

Examples

Common
julia> x = interval(0.5, 3)Interval{Float64}(0.5, 3.0, com)
julia> sqrt(x)Interval{Float64}(0.7071067811865475, 1.7320508075688774, com)

Both input x and output sqrt(x) are common intervals since they are closed, bounded, non-empty and the square root is continuous over $[1/2, 3]$.

Observe that these decorations, together with the fact that any element of the interval sqrt(x) is also in the interval x, imply that the Schauder Fixed-Point Theorem is satisfied. More precisely, this computation proves the existence of a fixed-point of the square root in $[1/2, 3]$ (in this simple example, $\sqrt(1) = 1$).

Defined and continuous
julia> x = interval(3, Inf)Interval{Float64}(3.0, Inf, dac)
julia> sqrt(x)Interval{Float64}(1.7320508075688772, Inf, dac)

Both the intervals are unbounded, hence the maximum possible decoration is dac.

Note that overflows can also produce the decoration dac:

julia> x = interval(floatmax(Float64))Interval{Float64}(1.7976931348623157e308, 1.7976931348623157e308, com)
julia> x + interval(1)Interval{Float64}(1.7976931348623157e308, Inf, dac)
Defined and continuous
julia> x = interval(-3, 4)Interval{Float64}(-3.0, 4.0, com)
julia> sign(x)Interval{Float64}(-1.0, 1.0, def)

The $\sign$ function is discontinuous at $0$, but is defined everywhere on the input interval, so the decoration of the result is def.

Trivial
julia> x = interval(-3.5, 4)Interval{Float64}(-3.5, 4.0, com)
julia> sqrt(x)Interval{Float64}(0.0, 2.0, trv)

The negative part of x is discarded before evaluating the square root since its domain is $[0, \infty)$. The process of discarding parts of an interval that are not in the domain of a function is called loose evaluation. This event has been recorded by degrading the decoration of the resulting interval to trv, indicating that nothing is known about the relationship between x and sqrt(x).

In this case, we know why the decoration was reduced to trv. Generally, if this were just a single step in a longer calculation, a resulting decoration trv shows only that something like this occured at some step.

For instance,

julia> f = asin ∘ sqrtasin ∘ sqrt
julia> x = interval(-3, 3)Interval{Float64}(-3.0, 3.0, com)
julia> f(x)Interval{Float64}(0.0, 1.5707963267948968, trv)
julia> y = interval(0, 3)Interval{Float64}(0.0, 3.0, com)
julia> f(y)Interval{Float64}(0.0, 1.5707963267948968, trv)

In both cases, asin(sqrt(X)) gives a result with the decoration trv; to find out where things went wrong, the function must be analyzed.

julia> sqrt(x) # `f(x)` has the decoration is `trv` since `x` contains negative valuesInterval{Float64}(0.0, 1.7320508075688774, trv)
julia> sqrt(y) # the decoration is `com`Interval{Float64}(0.0, 1.7320508075688774, com)
julia> asin(sqrt(y)) # `f(x)` has the decoration is `trv` since `sqrt(y)` contains values stricly greater than `1`Interval{Float64}(0.0, 1.5707963267948968, trv)

This shows that loose evaluation occurred in different parts of f for x and y.

Danger

The decoration trv is an indicator of information loss. Often this also reveals that something unexpected occured. Therefore, any interval marked by this decoration may not be trusted and the code may need to be revised.

Ill-formed
julia> interval(2, 1)┌ Warning: invalid interval, NaI is returned
└ @ IntervalArithmetic ~/work/IntervalArithmetic.jl/IntervalArithmetic.jl/src/intervals/construction.jl:444
Interval{Float64}(∅, ill)
julia> interval(NaN)┌ Warning: invalid interval, NaI is returned └ @ IntervalArithmetic ~/work/IntervalArithmetic.jl/IntervalArithmetic.jl/src/intervals/construction.jl:444 Interval{Float64}(∅, ill)

These are all examples of ill-formed intervals, resulting in the decoration ill.

Danger

The decoration ill is an indicator that an error has occured. Therefore, any interval marked by this decoration cannot be trusted and the code needs to be debugged.

Guarantee

A guarantee is yet another label, independent of decorations, and not described by the IEEE Standard 1788-2015 specifications. Its purpose is to accomodate for Julia's extensive conversion and promotion system, while retaining reliability in computations. Specifically, an interval x constructed via interval satisfies isguaranteed(x) == true. However, if a call to convert(::Type{<:Interval}, ::Real) occurs, then the resulting interval x satisfies isguaranteed(x) == false, receiving the "NG" (not guaranteed) label. For instance, consider the following examples:

julia>  convert(Interval{Float64}, 1.) # considered "not guaranteed" as this call can be done implicitlyInterval{Float64}(1.0, 1.0, com, NG)
julia> interval(1) # considered "guaranteed" as the user explicitly constructed the intervalInterval{Float64}(1.0, 1.0, com)

In contrast, a BareInterval can only be constructed via bareinterval, it is not a subtype of Real, and there are no allowed conversion with Number. Thus, this interval type is always guaranteed.

Danger

A user interested in validated numerics should always have a resulting interval for which isguaranteed is true.

More constructors

The submodule IntervalArithmetic.Symbols exports the infix operator .. and ± as an alias for interval; this submodule must be explicitly imported.

julia> using IntervalArithmetic.Symbols
julia> 0.1 .. 0.2 # interval(0.1, 0.2; format = :infsup)Interval{Float64}(0.1, 0.2, com)
julia> 0.1 ± 0.2 # interval(0.1, 0.2; format = :midpoint)Interval{Float64}(-0.1, 0.30000000000000004, com)

Moreover, one can parse strings into intervals. The various string formats are the following:

  • "[m]" is equivalent to interval(m, m).

  • "[l, r]" is equivalent to interval(l, r).

  • "m?r" is equivalent to interval(m-r, m+r).

  • "m?ren" is equivalent to interval((m-r)*1en, (m+r)*1en).

  • "m?rd" is equivalent to interval(m-r, m).

  • "m?ru" is equivalent to interval(m, m+r).

  • "m?" is equivalent to interval(m + 5 precision units, m - 5 precision units).

  • "m??" is equivalent to interval(-Inf, +Inf).

  • "m??d" is equivalent to interval(-Inf, m).

  • "m??u" is equivalent to interval(m, +Inf).

  • "[Entire]", "[entire]" and "[,]" are equivalent to entireinterval().

  • "[Empty]", "[empty]" and "[]" are equivalent to emptyinterval().

  • "[nai]" and any other unsupported string formats are equivalent to nai().

To add a specific decoration, add "_com", "_dac", "_dec", "_trv" and "_ill" at the end of the string.

Danger

Most real numbers cannot be exactly represented by floating-points. In such cases, the literal expression is rounded at parse time. To construct an interval enclosing the true real number, one must rely on the string constructor mentioned above.

For instance, consider

julia> x = 0.10.1

This appears to store the real number $1/10$ in a variable x of type Float64. Yet,

julia> x > 1//10true

Hence, the floating-point 0.1 is (slightly) greater than the real number $1/10$ since $1/10$ cannot be represented exactly in binary floating-point arithmetic, at any precision. The true value must be approximated by a floating-point number with fixed precision – this procedure is called rounding.

In particular, this implies that interval(0.1) does not contain the real number $1/10$. A valid interval containing the real number $1/10$ can be constructed by

julia> I"0.1"Interval{Float64}(0.09999999999999999, 0.1, com)