Search:

# Rationals

## Rational Numbers in ECLiPSe

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

### History

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.

### Objective

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

### Syntax

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

3_9.
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:

```    1_/3
1R3
0R1/3
```

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)
```

### Comparisons

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
No.

?- 3 = 3_1.
No
```

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

```    ?- 3 =:= 3_1.
Yes
```

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

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

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

### Computations

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.

### Shortcomings

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.