Fix integer.mul_by_digit producing denormalized zeros. (#141)

mul_by_digit(0, Y) produced i(Len, [0, 0, ...]) instead of the
canonical integer.zero = i(0, []). Since is_zero/1 only matched
the canonical form, denormalized zeros were silently treated as
nonzero, causing incorrect results in rational.m (e.g., gcd_2
non-termination, division-by-zero crashes in big_quot_rem).

Three fixes applied:
- Guard mul_by_digit and printbase_mul_by_digit to return
  integer.zero when the digit is 0 (root cause fix).
- Make is_zero/1 recognize denormalized all-zero digit lists
  as defense in depth.
- Replace structural `= integer.zero` checks in rational.m
  with integer.is_zero/1 calls for robustness.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nadia Yvette Chambers
2026-03-13 23:35:20 +01:00
committed by GitHub
parent b2f1ff63df
commit fa271a0ec8
2 changed files with 27 additions and 16 deletions

View File

@@ -642,6 +642,9 @@ sixteen = i(1, [16]).
abs(N) = big_abs(N).
is_zero(i(0, [])).
is_zero(i(_, [D | Ds])) :-
D = 0,
list.all_true(pred(0::in) is semidet, Ds).
%---------------------%
@@ -896,11 +899,15 @@ mul_base_2([H | T]) = [H | mul_base_2(T)].
:- func mul_by_digit(digit, integer) = integer.
mul_by_digit(Digit, i(Len, Digits0)) = Out :-
mul_by_digit_2(Digit, Mod, Digits0, Digits),
( if Mod = 0 then
Out = i(Len, Digits)
( if Digit = 0 then
Out = integer.zero
else
Out = i(Len + 1, [Mod | Digits])
mul_by_digit_2(Digit, Mod, Digits0, Digits),
( if Mod = 0 then
Out = i(Len, Digits)
else
Out = i(Len + 1, [Mod | Digits])
)
).
:- pred mul_by_digit_2(digit::in, digit::out, list(digit)::in,
@@ -1758,11 +1765,15 @@ printbase_chop(printbase(Base), N, Div, Mod) :-
:- func printbase_mul_by_digit(printbase, digit, integer) = integer.
printbase_mul_by_digit(Base, D, i(Len, Ds)) = Out :-
printbase_mul_by_digit_2(Base, D, Div, Ds, DsOut),
( if Div = 0 then
Out = i(Len, DsOut)
( if D = 0 then
Out = integer.zero
else
Out = i(Len + 1, [Div | DsOut])
printbase_mul_by_digit_2(Base, D, Div, Ds, DsOut),
( if Div = 0 then
Out = i(Len, DsOut)
else
Out = i(Len + 1, [Div | DsOut])
)
).
:- pred printbase_mul_by_digit_2(printbase::in, digit::in, digit::out,

View File

@@ -153,7 +153,7 @@ r(An, Ad) * r(Bn, Bd) = rational_norm(Numer, Denom) :-
R1 / R2 = R1 * reciprocal(R2).
reciprocal(r(Num, Den)) =
( if Num = integer.zero then
( if integer.is_zero(Num) then
func_error($pred, "division by zero")
else
r(signum(Num) * Den, integer.abs(Num))
@@ -164,9 +164,9 @@ abs(r(Num, Den)) = r(integer.abs(Num), Den).
:- func rational_norm(integer, integer) = rational.
rational_norm(Num, Den) = Rat :-
( if Den = integer.zero then
( if integer.is_zero(Den) then
error("rational.rational_norm: division by zero")
else if Num = integer.zero then
else if integer.is_zero(Num) then
Rat = r(integer.zero, integer.one)
else
G = gcd(Num, Den),
@@ -181,14 +181,14 @@ gcd(A, B) = gcd_2(integer.abs(A), integer.abs(B)).
:- func gcd_2(integer, integer) = integer.
gcd_2(A, B) = ( if B = integer.zero then A else gcd_2(B, A rem B) ).
gcd_2(A, B) = ( if integer.is_zero(B) then A else gcd_2(B, A rem B) ).
:- func lcm(integer, integer) = integer.
lcm(A, B) =
( if A = integer.zero then
( if integer.is_zero(A) then
integer.zero
else if B = integer.zero then
else if integer.is_zero(B) then
integer.zero
else
integer.abs((A // gcd(A, B)) * B)
@@ -197,7 +197,7 @@ lcm(A, B) =
:- func signum(integer) = integer.
signum(N) =
( if N = integer.zero then
( if integer.is_zero(N) then
integer.zero
else if N < integer.zero then
-integer.one
@@ -219,7 +219,7 @@ cmp(R1, R2) = Cmp :-
:- pred is_zero(rational::in) is semidet.
is_zero(r(integer.zero, _)).
is_zero(r(Num, _)) :- integer.is_zero(Num).
:- pred is_negative(rational::in) is semidet.