Issues to be aware of when using bounded reals

## 9.2 Issues to be aware of when using bounded reals

When working with bounded reals, some of the usual rules of arithmetic no longer hold. In particular, it is not always possible to determine whether one bounded real is larger, smaller, or the same as another. This is because, if the intervals overlap, it is not possible to know the relationship between the true values.

An example of this can be seen in Figure 9.2. If the true value of X is X1, then depending upon whether the true value of Y is (say) Y1, Y2 or Y3, we have X > Y, X =:= Y or X < Y, respectively.

> Figure 9.2: Comparing two bounded reals

Different classes of predicate deal with the undecidable cases in different ways:

Arithmetic comparison
  (</2, =:=/2, etc.) If the comparison cannot be determined definitively, the comparison succeeds but a delayed goal is left behind, indicating that the result of the computation is contingent on the relationship actually being true. Examples:

  >     ?- X = 0.2__0.3, Y = 0.0__0.1, X > Y.
  >     X = 0.2__0.3
  >     Y = 0.0__0.1
  >     Yes

  >     ?- X = 0.2__0.3, Y = 0.0__0.1, X < Y.
  >     No

  >     ?- X = 0.0__0.1, Y = 0.0__0.1, X < Y.
  >     X = 0.0__0.1
  >     Y = 0.0__0.1
  >     Delayed goals:
  >             0.0__0.1 < 0.0__0.1
  >     Yes

  >     ?- X = Y, X = 0.0__0.1, X < Y.
  >     No

Term equality or comparison
  (=/2, ==/2, compare/3, @</2, etc.) These predicates consider bounded reals from a purely syntactic point of view: they determine how the bounded reals compare syntactically, without taking into account their meaning. Two bounded reals are considered equal if and only if their bounds are syntactically the same (note that the floating point numbers 0.0 and -0.0 are considered to be syntactically different). A unique ordering is also defined between bounded reals which do not have identical bounds; see the documentation for compare/3 for details. This is important as it means predicates such as sort/2 behave in a sensible fashion when they encounter bounded reals (in particular, they do not throw exceptions or leave behind large numbers of meaningless delayed goals) — though one does need to be careful when comparing or sorting things of different types. Examples:

  >     ?- X = 0.2__0.3, Y = 0.0__0.1, X == Y.
  >     No

  >     ?- X = 0.0__0.1, Y = 0.0__0.1, X == Y.
  >     X = 0.0__0.1
  >     Y = 0.0__0.1
  >     Yes

  >     ?- X = 0.2__0.3, Y = 0.0__0.1, compare(R, X, Y).
  >     R = >
  >     X = 0.2__0.3
  >     Y = 0.0__0.1
  >     Yes

  >     ?- X = 0.1__3.0, Y = 0.2__0.3, compare(R, X, Y).
  >     R = <
  >     X = 0.1__3.0
  >     Y = 0.2__0.3
  >     Yes

  >     ?- X = 0.0__0.1, Y = 0.0__0.1, compare(R, X, Y).
  >     R = =
  >     X = 0.0__0.1
  >     Y = 0.0__0.1
  >     Yes

  >     ?- sort([-5.0, 1.0__1.0], Sorted).
  >     Sorted = [1.0__1.0, -5.0]       % 1.0__1.0 > -5.0, but 1.0__1.0 @< -5.0
  >     Yes

Note that the potential undecidability of arithmetic comparisons has implications when writing general code. For example, a common thing to do is test the value of a number, with different code being executed depending on whether or not it is above a certain threshold; e.g.

>     ( X >= 0 ->
>         % Code A
>     ;
>         % Code B
>     )

When writing code such as the above, if X could be a bounded real, one ought to decide what should happen if X’s bounds span the threshold value. In the above example, if X = -0.1__0.1 then a delayed goal -0.1__0.1 >= 0 will be left behind and Code A executed. If one does not want the delayed goal, one can instead write:

>     ( not X >= 0 ->
>         % Code B
>     ;
>         % Code A
>     )

The use of not ensures that any actions performed during the test (in particular the set up of any delayed goals) are backtracked, regardless of the outcome of the test.

Finally, if one wishes Code B to be executed instead of Code A in the case of an overlap, one can reverse the sense of the test:

>     ( not X < 0 ->
>         % Code A
>     ;
>         % Code B
>     )