Compare/3 dubious logic

Hi, using SWI-Prolog version 8.5.7 - I think these two are bugs: Firstly:

?- compare(Comp, N1, N2).
Comp =  (<).

Rather than that wrongness, I’d like the same error as with:

?- N1 < N2.
ERROR: Arguments are not sufficiently instantiated

Secondly:

?- compare(C, 5, 5.0).
C =  (>).

The implementation should compare both values as floats, rather than give a mathematically-incorrect answer.

Posting here, for discussion (and perhaps I’m wrong), rather than going straight for a bug report :grinning:

compare/3 uses the standard ordering of terms: SWI-Prolog -- Manual
see also (@<)/2 and (=:=)/2.

That standard ordering was probably sensible around 1980, but surely it just creates a logical minefield now?

I would rather see a runtime exception, rather than a blatantly wrong answer, in a logical language.

In a similar vein, this is inconsistent:

?- compare(C, 5, 5.0).
C =  (>).

?- 5 > 5.0.
false.

?- 5 =:= 5.0.
true.

Python3 gives:

>>> 5 == 5.0
True

Is there scope for improvement here, or is it just that reality is strange? :grinning:

As said, compare/3 deals with the Prolog standard order of terms and not with numerical data. One of the points of the standard order is to reason efficiently about sets of terms. This implies that two terms that are different may never compare as equal. As integers and floats are distinct types, an integer and a float with the same value are not the same term and can therefore not compare equal. Note that the integer 5 is an exact object, but the float 5.0 merely represents some interval of real numbers that cannot be distinguished from 5.

For consistency it might have been better if compare/3 used the standard order of terms comparison operatrors, returning e.g. C = (@>). Alas. As all programming languages Prolog has some weird properties.

What you are looking for is numerical comparison and that is done using >/2, >=/2, =:=/2, (=<)/2 or (<)/2. There is no compare equivalent. It isn’t too hard to define that though.

As for floats, they follow the IEE754 standard. And yes, there are some curious corner cases in how floats behave. They are the same in most languages supporting floating point numbers.

As equivalence is an important property in logic (and Prolog), floats do not fit well as due to rounding and inability to represent the exact intended value floats that should mathematically be equal often are not. Prolog likes integers and rational numbers :slight_smile:

3 Likes

To be creative and to find solutions, you need to create a little bit of
chaos, constructively and not destructively. I can imagine that in Yield
Prolog we have indeed the following, but I didn’t check yet:

/* Yield Prolog Maybe? */
?- 5 == 5.0
true.

This could also be an option for SWI-Prolog without bignums i.e. without
GMP, everything is float. A similar path is taken in JavaScript, which
makes it tricky to recreate the float/integer disctinction in JavaScript:

/* Browser JavaScript */
>> 5 == 5.0
true
>> typeof 5
"number"
>> typeof 5.0
"number"

In TauProlog, to nevertheless create a float/integer distinction, there
is a quite heavy class Num, with one value field and one type field.
But you can also do it less heavily to create a float/integer

distinction in JavaScript.

Edit 04.03.2022:
What I don’t know whether it is possible to practically create a
Prolog profile, where float/1 and integer/1 are the same.
Such a small profile of a Programming language and runtime system

existed for example in the early days of Android, which did only have
32-bit floats intially and no 64-bit floats. Today Android has even 16-bit floats.
Thats why back then I had introduced 32-bit floats in formerly Jekejeke Prolog.

But this was rather conceptually an enlargement of a language profile,
and not a shrinking. When the 64-bit floats where not available I think
the Prolog systems didn’t react so gracefully, and the misery was

quickly ended when Android also provided 64-bit floats.

1 Like

Instead of compare/3, I think I’ll be happy with simply:

nums_sign(N1, N2, Sign) :-
    % Using round() to ensure that Sign is integer if N1 or N2 are float
    Sign is round(sign(N1 - N2)).

Thanks for the insightful comments, all :slightly_smiling_face:

Thanks, number_compare is faster than nums_sign:

?- time(findall(C, (between(1, 3000, N1), between(1, 3000, N2), number_compare(C, N1, N2)), Cs)), length(Cs, CsLen).
% 40,504,510 inferences, 5.026 CPU in 5.045 seconds (100% CPU, 8059066 Lips)

?- time(findall(C, (between(1, 3000, N1), between(1, 3000, N2), nums_sign(N1, N2, C)), Cs)), length(Cs, CsLen).
% 36,003,010 inferences, 5.326 CPU in 5.316 seconds (100% CPU, 6760393 Lips)

But native is still fastest:

?- time(findall(C, (between(1, 3000, N1), between(1, 3000, N2), compare(C, N1, N2)), Cs)), length(Cs, CsLen).
% 27,003,010 inferences, 3.095 CPU in 3.097 seconds (100% CPU, 8725684 Lips)

For increased performance:

number_compare(C, X, Y) :-
    (   X < Y -> C = <
    ;   X =:= Y -> C = =
    ;   C = > ).
?- time(findall(C, (between(1, 3000, N1), between(1, 3000, N2), number_compare(C, N1, N2)), Cs)), length(Cs, CsLen).
% 49,504,510 inferences, 4.517 CPU in 4.463 seconds (101% CPU, 10958589 Lips)