Recent Changes - Search:


edit SideBar


Rational Numbers in ECLiPSe

Author: Joachim Schimpf, 2020, under Creative Commons Share-Alike License


Rational numbers were first integrated into ECLiPSe as a first class data type in 1993. Together with the previously existing standard numeric types (integer and float) and the later introduced breal type (floating-point intervals, 2001) they are one of four supported numeric data types.


Rational numbers implement the corresponding mathematical domain, i.e., ratios of two integers (numerator and denominator).


Rational constants are written as numerator and denominator separated by an underscore, e.g.,

    1_3  -30517578125_32768  0_1

ECLiPSe represents rationals in a canonical form where the greatest common divisor of numerator and denominator is 1 and the denominator is positive. The parser (or, more precisely the lexical analyzer) accepts non-canonical notation, but normalizes immediately. When printing, canonical form is always used:

    ?- X = 2_6.
    X = 1_3

    ?- read(X).
    X = 1_3

Discussion: At the time, the underscore-syntax was chosen because it did not conflict with other valid Prolog syntax. In the meantime, some Prolog systems have chosen to allow number notation with embedded underscores (such as 123_456_789) as a readability feature, which unfortunately conflicts with our rational-syntax.

Some suggestions for alternative, non-conflicting syntax:


Note that the following are undesirable, for the reason given:

    1/3          conflicts with compound term /(1,3) in infix notation
    1 rdiv 3     conflicts with compound term rdiv(1,3) in infix notation
    1r3          conflicts with compound term r3(1) in postfix notation
    1/3R         requires 3-token lookahead in lexer to disambiguate,
                 potentially conflicts with compound term /(1,3R)


Numbers of different types are never identical and never unify, e.g., 3, 3_1, 3.0 and 3.0__3.0 are all different:

    ?- 3 == 3_1.       % fails in the same way that 3==3.0 fails

    ?- 3 = 3_1.

Arithmetic comparison predicates must be used to their compare numeric values:

    ?- 3 =:= 3_1.

In the standard term ordering, rationals come between integers and floats:

    ?- compare(R, 3_1, 3).
    R = <

    ?- compare(R, 3_1, 3.0).
    R = >


Rational arithmetic is precise.

When numbers of different types occur as arguments of an arithmetic operation or comparison, the types are first made equal by converting to the more general of the two types, i.e., the rightmost one in the sequence

    integer → rational → float → bounded real 

The operation or comparison is then carried out with this type and the result is of this type as well, unless otherwise specified. There is potential loss of precision in the rational → float conversion! The system never does automatic conversions in the opposite direction. Such conversion must be programmed explicitly using the integer, rational, float and breal functions.

These arithmetic operations work on rational inputs:

   Operation         Meaning                    Argument type(s)    Result type

   + E               unary plus                 rational              rational
   - E               unary minus                rational              rational
   abs(E)            absolute value             rational              rational
   sgn(E)            sign value                 rational              integer
   floor(E)          round down                 rational              rational (ISO:integer)
   ceiling(E)        round up                   rational              rational (ISO:integer)
   round(E)          round to nearest           rational              rational (ISO:integer)
   truncate(E)       round towards zero         rational              rational (ISO:integer)

   E1 + E2           addition                   rational x rational   rational
   E1 - E2           subtraction                rational x rational   rational
   E1 * E2           multiplication             rational x rational   rational
   E1 / E2           division                   rational x rational   rational
   E1 ^ E2           power operation            rational x integer    rational
   min(E1,E2)        minimum of 2 values        rational x rational   rational
   max(E1,E2)        maximum of 2 values        rational x rational   rational

   fix(E)            truncate to integer        rational              integer
   integer(E)        convert to integer         rational              integer
   float(E)          convert to float           rational              float
   numerator(E)      numerator of rational      rational              integer
   denominator(E)    denominator of rational    rational              integer

In addition, these operations on non-rational arguments yield (may yield) rational results:

   E1 / E2           division                   integer x integer     see below
   E1 ^ E2           power operation            integer x integer     see below
   rational(E)       convert to rational        number                rational
   rationalize(E)    convert to rational        number                rational

Currently, there is a global flag prefer_rationals: when set, the system uses rational arithmetic wherever possible. In particular

  • dividing two integers with `/` or
  • raising an integer to a negative integral power

then yields a precise rational instead of a float result.

Discussion: changing the behaviour via a global flag is undesirable. On the other hand, always using rational results can be very inefficient if the precision is later lost anyway due to conversion to floats.

Both rational/1 and rationalize/1 produce a value that is exactly equal to the value of the floating-point input. The difference is that rationalize/2 tries to produce the most compact rational that still converts back into the original float, while rational/2 often produces unnecessarily large numerators and denominators but is more efficient. The naming convention is taken from LISP.


The strict type separation of integers and rationals may not be desirable. An alternative would be to automatically convert integral rationals (i.e. rationals with a normalized denominator of 1) to integers. SWI-Prolog does it this way already. Integers then become a subset of the rationals rather than a disjoint data type.

Edit - History - Print - Recent Changes - Search
Page last modified on January 26, 2020, at 05:29 PM