Date

This is a quick and dirty post attempting rational (fraction) math with Prolog's integer constraint library. CLPFD works well for integer math, but it is not built to handle decimal values. This limits where you can use CLPFD, if a domain has even a single non-integer value, you'll have to work around it.

As a challenge, I attempted to represent fractions in CLPFD. It's not too hard, just store a numerator and denominator. Pass them around together.

I started off with a failed attempt to write a predicate to reduce the fraction: I reproduce it here out of humbleness.

:- use_module(library(clpfd)).

% The fraction C/D is the reduction of A / B
% This does not work.
reduce(A, B, C, D) :-
  % If there is some X that is in both the numerator and denominator...
  [A1, B1] ins 1..100,
  X in 2..10,
  XExists #<==> (
    A #= A1 * X #/\ B #= B1 * X),
  write(XExists),
  % XExists #==> (
  % reduce(A1, B1, C, D),
  % ),
  (#\ XExists) #==> (
    A #= C #/\ B #= D
  ).

After 30 minutes I started searching the web. I found this helpful SO post which implemented Euclid's algorithm in CLPFD.

% From: https://stackoverflow.com/questions/66016948/inverting-a-function-via-clpfd-in-pure-prolog
euclid(A,A,A).
euclid(A,B,R) :- A #< B, C #= B-A, euclid(A,C,R).
euclid(A,B,R) :- A #> B, C #= A-B, euclid(C,B,R).

Bested by an ancient Greek, I returned to my task

The rest was simple:

reduce2(A, B, C, D) :-
  euclid(A, B, R),
  C #= A // R,
  D #= B // R.

multiply_rational(A, B, C, D, E, F) :-
  E0 #= A * C,
  F0 #= B * D,
  reduce2(E0, F0, E, F).

add_rational(A, B, C, D, E, F) :-
  A1 #= A * D,
  C1 #= C * B,
  F0 #= B * D,
  E0 #= A1 + C1,
  reduce2(E0, F0, E, F).

invert(A, B, B, A).

divide_rational(A, B, C, D, E, F) :-
  invert(C, D, C0, D0),
  multiply_rational(A, B, C0, D0, E, F).

greater_than_rational(A, B, C, D, T) :-
  % Generate same denominator
  A1 #= A * D,
  C1 #= C * B,
  T #<==> A1 #> C1.

equal_rational(A, B, C, D, T) :-
  reduce2(A, B, E, F),
  reduce2(C, D, E2, F2),
  T #<==> E #= E2 #/\ F #= F2.

This is pretty scrappy, but it's a starting point for expanding CLPFD to more domains.