% Reversible factorial relation
% fact(N, F) is true when F = N!
% Works when N is given, when F is given, or when both are variables.
% public entry
fact(N, F) :-
( nonvar(N) -> % N is known: compute F directly
integer(N), N >= 0,
fact_from_n(N, F)
; nonvar(F) -> % F is known: search N by growing factorial
integer(F), F >= 0,
fact_from_f(F, N)
; % both variables: enumerate N = 0,1,2,...
nat(N),
fact_from_n(N, F)
).
% compute factorial when N is known
fact_from_n(0, 1) :- !.
fact_from_n(N, F) :-
N > 0,
N1 is N - 1,
fact_from_n(N1, F1),
F is F1 * N.
% find N given F by iterative search
% handles F = 0 only if you want to treat 0 specially (0! = 1), so we require F >= 0
fact_from_f(F, N) :-
% start search from 0 with accumulator 1
fact_search(0, 1, F, N).
% fact_search(CurrentN, Acc, TargetF, ResultN)
fact_search(N, Acc, F, N) :-
Acc =:= F, !.
fact_search(N, Acc, F, Res) :-
Acc < F,
N1 is N + 1,
Acc1 is Acc * N1,
fact_search(N1, Acc1, F, Res).
% If Acc > F the search fails (no integer N with N! = F).
% nat/1 enumerates natural numbers 0,1,2,...
nat(0).
nat(N) :- nat(M), N is M + 1.