Rival:   An Interval Arithmetic for Real Computation
1 Introduction
2 Intervals
ival
ival?
ival-lo
ival-hi
ival-pi
ival-e
3 Interval Operations
ival-add
ival-sub
ival-neg
ival-mul
ival-div
ival-fma
ival-fabs
ival-sqrt
ival-cbrt
ival-hypot
ival-exp
ival-exp2
ival-exp2m1
ival-log
ival-log2
ival-log10
ival-log1p
ival-log1b
ival-pow
ival-sin
ival-cos
ival-tan
ival-asin
ival-acos
ival-atan
ival-atan2
ival-sinh
ival-cosh
ival-tanh
ival-asinh
ival-acosh
ival-atanh
ival-erf
ival-erfc
ival-tgamma
ival-lgamma
ival-fmod
ival-remainder
ival-rint
ival-round
ival-ceil
ival-floor
ival-trunc
ival-fmin
ival-fmax
ival-copysign
ival-fdim
ival-sort
4 Interval Helper Functions
monotonic->ival
comonotonic->ival
ival-union
ival-split
5 Boolean Intervals
ival-true
ival-false
ival-uncertain
ival-<
ival-<=
ival->
ival->=
ival-==
ival-!=
ival-and
ival-or
ival-not
ival-if
6 Error Intervals
ival-error?
ival-illegal
ival-assert
ival-then
7 Movability flags
close-enough->ival
8.12

Rival: An Interval Arithmetic for Real Computation🔗ℹ

Pavel Panchekha

 (require rival) package: rival

1 Introduction🔗ℹ

Rival is an advanced interval arithmetic library for arbitrary-precision computation of complex mathematical expressions. Its interval arithmetic is sound and attempts to be weakly complete. Besides the standard intervals, Rival also supports boolean intervals, error intervals, and movability flags, as described in "An Interval Arithmetic for Robust Error Estimation".

Rival is a part of the Herbie project, and is developed on Github.

2 Intervals🔗ℹ

A standard Rival interval contains two bigfloat? values and includes both endpoints. Neither endpoint is allowed to be +nan.bf.

procedure

(ival lo [hi])  ival?

  lo : (or/c bigfloat? boolean?)
  hi : (or/c bigfloat? boolean?) = lo
Constructs an interval given two endpoints lo and hi. If hi isn’t given, the interval contains a single point, lo. Single-point intervals are considered immovable, while all other intervals are considered movable. If either endpoint is +nan.bf, an illegal interval is returned.

You can also use the ival form for matches:

> (define x (ival 0.bf 2.bf))
> x

(ival 0.bf (bf 2))

> (match-define (ival lo hi) (ival-sqrt x))
> lo

0.bf

> hi

(bf #e1.414213562373095048801688724209698078575)

This is the preferred way to access interval endpoints.

procedure

(ival? obj)  boolean?

  obj : any/c
Determines whether an object is an interval.

procedure

(ival-lo x)  (or/c bigfloat? boolean?)

  x : ival?
(ival-hi x)  (or/c bigfloat? boolean?)
  x : ival?
These accessor methods provide an alternative way of accessing the two endpoints of an interval when a match is not convenient.

procedure

(ival-pi)  ival?

(ival-e)  ival?
Rival also provides two interval constants. Since π and e are not exact bigfloat? values, these constants are implemented as functions, which return different intervals at different bf-precisions.

3 Interval Operations🔗ℹ

Rival provides a large set of interval operations. All of these operations are sound, meaning that the output interval always contains all valid outputs from points in the input intervals.

Most operations are also weakly complete, meaning that the endpoints of the output interval come from some point in the input intervals (rounded outwards). Not all operations are weakly complete, however.

These operations have their output precision determined by bf-precision.

procedure

(ival-add a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-sub a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-neg a)  ival?

  a : ival?

procedure

(ival-mul a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-div a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-fma a b c)  ival?

  a : ival?
  b : ival?
  c : ival?

procedure

(ival-fabs a)  ival?

  a : ival?

procedure

(ival-sqrt a)  ival?

  a : ival?

procedure

(ival-cbrt a)  ival?

  a : ival?

procedure

(ival-hypot a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-exp a)  ival?

  a : ival?

procedure

(ival-exp2 a)  ival?

  a : ival?

procedure

(ival-exp2m1 a)  ival?

  a : ival?

procedure

(ival-log a)  ival?

  a : ival?

procedure

(ival-log2 a)  ival?

  a : ival?

procedure

(ival-log10 a)  ival?

  a : ival?

procedure

(ival-log1p a)  ival?

  a : ival?

procedure

(ival-log1b a)  ival?

  a : ival?

procedure

(ival-pow a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-sin a)  ival?

  a : ival?

procedure

(ival-cos a)  ival?

  a : ival?

procedure

(ival-tan a)  ival?

  a : ival?

procedure

(ival-asin a)  ival?

  a : ival?

procedure

(ival-acos a)  ival?

  a : ival?

procedure

(ival-atan a)  ival?

  a : ival?

procedure

(ival-atan2 a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-sinh a)  ival?

  a : ival?

procedure

(ival-cosh a)  ival?

  a : ival?

procedure

(ival-tanh a)  ival?

  a : ival?

procedure

(ival-asinh a)  ival?

  a : ival?

procedure

(ival-acosh a)  ival?

  a : ival?

procedure

(ival-atanh a)  ival?

  a : ival?

procedure

(ival-erf a)  ival?

  a : ival?

procedure

(ival-erfc a)  ival?

  a : ival?

procedure

(ival-tgamma a)  ival?

  a : ival?

procedure

(ival-lgamma a)  ival?

  a : ival?

procedure

(ival-fmod a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-remainder a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-rint a)  ival?

  a : ival?

procedure

(ival-round a)  ival?

  a : ival?

procedure

(ival-ceil a)  ival?

  a : ival?

procedure

(ival-floor a)  ival?

  a : ival?

procedure

(ival-trunc a)  ival?

  a : ival?

procedure

(ival-fmin a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-fmax a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-copysign a b)  ival?

  a : ival?
  b : ival?

procedure

(ival-fdim a b)  ival?

  a : ival?
  b : ival?
These are all interval functions with arguments in the order corresponding to the same-name math.h functions. Barring bugs, all are sound. Most are weakly complete, though some more complex functions aren’t, including ival-pow, ival-fma, ival-fmod, and ival-atan2. Even these fuctions still make a best-effort attempt to produce relatively narrow intervals. For example, ival-fma is implemented via the formula (fma a b c) = (+ (* a b) c), which that it accumulates multiple rounding errors. The result is therefore not maximally tight, but typically still pretty close.

Changed in version 1.7 of package rival: Added ival-tgamma and ival-lgamma

procedure

(ival-sort lst <)  (listof ival?)

  lst : (listof ival?)
  < : 
(-> (or/c bigfloat? boolean?)
   (or/c bigfloat? boolean?)
   boolean?)
Sorts a list of intervals using a comparator function.

4 Interval Helper Functions🔗ℹ

Rival provides simple helper methods to define your own interval functions.

procedure

(monotonic->ival fn)  (-> ival? ival?)

  fn : (-> bigfloat? (or/c bigfloat? boolean?))

procedure

(comonotonic->ival fn)  (-> ival? ival?)

  fn : (-> bigfloat? (or/c bigfloat? boolean?))
These functions lift a (weakly) (co-)monotonic bigfloat function to a function on intervals. A weakly monotonic function is one where larger inputs produce larger (or equal) outputs; a weakly co-monotonic function is one where larger inputs produce smaller (or equal) outputs. For example:

> (define ival-cube (monotonic->ival (lambda (x) (bf* x x x))))
> (ival-cube (ival -1.bf 3.bf))

(ival (bf -1) (bf 27))

Note that if a non-(co-)monotonic function is passed, the results will not be sound.

procedure

(ival-union a b)  ival?

  a : ival?
  b : ival?
Computes the union of two intervals. Maintains error flags, and movability flags when possible.

procedure

(ival-split a x)  
(or/c ival? #f) (or/c ival? #f)
  a : ival?
  x : bigfloat?
Splits an interval at a point, returning the two halves of that interval on either side of the split point. If the point is not within the interval, one of the two output intervals will be #f instead. This can be used to define simple, non-monotonic interval functions. For example:

> (define (ival-fabs x)
    (match/values (ival-split x 0.bf)
      [(#f hi) hi]
      [(lo #f) (ival-neg lo)]
      [(lo hi) (ival-union (ival-neg lo) hi)]))
> (ival-fabs (ival -1.bf 1.bf))

(ival -0.bf (bf 1))

5 Boolean Intervals🔗ℹ

Rival supports boolean intervals, whose endpoints are boolean?.

Note that an ival? must have either two bigfloat? intervals, or two boolean? intervals, never one of each. However, this constraint is not enforced by contracts or the type system.

In a boolean interval, #f is considered less than #t, yielding three boolean interval values:

value

ival-true : ival?

value

ival-false : ival?

value

ival-uncertain : ival?

Shorthands for (ival #f) and (ival #t), representing true and false, and (ival #f #t), representing an uncertain value that might be true or false.

procedure

(ival-< a ...)  ival?

  a : ival?

procedure

(ival-<= a ...)  ival?

  a : ival?

procedure

(ival-> a ...)  ival?

  a : ival?

procedure

(ival->= a ...)  ival?

  a : ival?

procedure

(ival-== a ...)  ival?

  a : ival?
Comparison operations, where all input intervals must be real intervals while the output interval is boolean. The answer can be ival-true or ival-false when, for example, intervals do not overlap. However, in many cases ival-uncertain is the only possible answer:

> (ival-< (ival -1.bf 3.bf) (ival 1.bf 5.bf))

(ival #f #t)

With zero or one argument, these functions yield ival-true, while with more arguments they function as chained comparators much like the analogous Racket comparison functions like <. Their behavior with illegal intervals is undefined.

procedure

(ival-!= a ...)  ival?

  a : ival?
Returns (ival #t) only if all input intervals do not overlap. In this way it is like a distinct? method.

Boolean intervals can be combined logically.

procedure

(ival-and a ...)  ival?

  a : ival?

procedure

(ival-or a ...)  ival?

  a : ival?

procedure

(ival-not a)  ival?

  a : ival?
The expected logical operations extended to boolean intervals using a standard tripartite logic.

procedure

(ival-if cond if-true if-false)  ival?

  cond : ival?
  if-true : ival?
  if-false : ival?
Here, cond must be a boolean interval, while if-true and if-false should be intervals of the same type, either both real or both boolean. If cond is uncertain, the union of if-true and if-false is returned.

Note that typically, uses of ival-if are incomplete because they are not flow-sensitive. For example, (if (< x 0) (- x) x) is always non-negative, but:

> (define (bad-ival-fabs x)
    (ival-if (ival-< x (ival 0.bf)) (ival-neg x) x))
> (bad-ival-fabs (ival -1.bf 1.bf))

(ival (bf -1) (bf 1))

The reason for this is that both branches, (ival-neg x) and x, evaluate to (ival -1.bf 1.bf); the condition (ival-< x (ival 0.bf)) does not refine the value of x in either branch.

6 Error Intervals🔗ℹ

Sometimes an interval will contain invalid inputs to some function. For example, sqrt is undefined for negative inputs! In cases like this, Rival’s output interval will only consider valid inputs:

> (ival-sqrt (ival -1.bf 1.bf))

(ival 0.bf (bf 1))

If none of the values in the interval are valid, a special ival-illegal value will be returned:

> (ival-sqrt (ival (bf -4) (bf -2)))

ival-illegal

Moreover, when an interval is computed by discarding invalid inputs, special error flags are set that can be retrieved with ival-error?:

> (ival-error? (ival-sqrt (ival 1.bf 4.bf)))

(ival #f #f)

> (ival-error? (ival-sqrt (ival -1.bf 1.bf)))

(ival #f #t)

> (ival-error? (ival-sqrt (ival (bf -4) (bf -2))))

(ival #t #t)

procedure

(ival-error? a)  ival?

  a : ival?
Returns a boolean interval indicating whether any inputs were discarded when computing a. These flags are "sticky": further computations on a will maintain the already-set error flags.

A real interval containing no values, with error flags indicating that all valid values have been discarded. All operations on ival-illegal yield an illegal interval.

procedure

(ival-assert c [msg])  ival?

  c : ival?
  msg : identity = #t
Returns an illegal interval if c is false, a legal interval if c is true, and a partially-legal one if c’s truth value is unknown. The value of the output interval is always the constant #t.

If msg is passed, it can be any value except #f, and it is stored in the error flags instead of #t. This can be used to provide the user with details on what caused the error. We recommend using a symbol or string as the error message.

procedure

(ival-then a ... b)  ival?

  a : ival?
  b : ival?
In other words, it raises an error if any of the as did, and otherwise returns b.

7 Movability flags🔗ℹ

The typical use case for Rival is to recompute a certain expression at ever higher bf-precision, until the computed interval is narrow enough. However, interval arithmetic is not complete. For example, due to the limitations of math/bigfloat’s underlying MPFR library, it’s impossible to compute (/ (exp x) (exp x)) for large enough values of x:

> (define x (ival (bf 1e+100)))
> (ival-div (ival-exp x) (ival-exp x))

(ival 0.bf +inf.bf)

The same result will hold for any bf-precision. While it’s impossible to detect this in all cases, Rival provides support for movability flags that can detect many such instances automatically. Movability flags are correctly propagated by all of Rival’s supported libraries, and are set by a couple of functions such as ival-exp.

The only access to movability flags is via close-enough->ival.

procedure

(close-enough->ival fn)  (-> ival? ival?)

  fn : 
(-> (or/c bigfloat? boolean?)
(or/c bigfloat? boolean?) boolean?)
The argument to close-enough is a function that determines whether two interval endpoints are close enough. The returned function can be used to determine if an interval will ever be close enough. The result will be ival-true if it is already close enough; ival-uncertain in most cases; and ival-false if Rival can prove that no evaluation at a higher precision can yield a close enough interval:

> (define (close-enough x y) (bf< (bf- y x) 1.bf))
> (define ival-close-enough? (close-enough->ival close-enough))
> (ival-close-enough? (ival 1.bf))

(ival #t #t)

> (ival-close-enough? (ival 1.bf 5.bf))

(ival #f #t)

> (define x (ival (bf 1e+100)))
> (ival-close-enough? (ival-div (ival-exp x) (ival-exp x)))

(ival #f #f)

Using movability flags via close-enough->ival can often cut short tedious timeout loops on impossible examples.

Note that, because of movability flags, there are actually many functionally different intervals with the same endpoints. For example, there are actually four different uncertain boolean intervals: regular uncertain intervals, which might become certain at higher precision; biased-true intervals, which are uncertain at this precision but which can’t be disproven true at any precision; biased-false intervals; and the provably uncertain intervals, which can’t be made certain at any higher precision. Movability flags at a ton of complexity to interval arithmetic, but Rival hides all of that from you inside close-enough->ival.